diff options
362 files changed, 5987 insertions, 15478 deletions
diff --git a/Android.bp b/Android.bp index ee5db70b3ce1..f8059475f6f4 100644 --- a/Android.bp +++ b/Android.bp @@ -110,7 +110,7 @@ filegroup { // AIDL sources from external directories ":android.hardware.graphics.common-V3-java-source", - ":android.hardware.security.keymint-V1-java-source", + ":android.hardware.security.keymint-V2-java-source", ":android.hardware.security.secureclock-V1-java-source", ":android.hardware.tv.tuner-V1-java-source", ":android.security.apc-java-source", @@ -191,7 +191,6 @@ java_defaults { "sax/java", "telecomm/java", - "apex/media/aidl/stable", // TODO(b/147699819): remove this "telephony/java", ], @@ -289,6 +288,7 @@ java_defaults { // TODO: remove when moved to the below package "frameworks/base/packages/ConnectivityT/framework-t/aidl-export", "packages/modules/Connectivity/framework/aidl-export", + "packages/modules/Media/apex/aidl/stable", "hardware/interfaces/graphics/common/aidl", ], }, @@ -538,6 +538,7 @@ stubs_defaults { "frameworks/native/libs/permission/aidl", // TODO: remove when moved to the below package "frameworks/base/packages/ConnectivityT/framework-t/aidl-export", + "packages/modules/Media/apex/aidl/stable", "packages/modules/Connectivity/framework/aidl-export", "hardware/interfaces/graphics/common/aidl", ], @@ -575,11 +576,9 @@ stubs_defaults { stubs_defaults { name: "module-classpath-stubs-defaults", aidl: { - local_include_dirs: [ - "apex/media/aidl/stable", - ], include_dirs: [ "packages/modules/Connectivity/framework/aidl-export", + "packages/modules/Media/apex/aidl/stable", ], }, libs: [ diff --git a/ApiDocs.bp b/ApiDocs.bp index 996cdc9a2b8f..ba31161009b6 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -145,11 +145,9 @@ droidstubs { "api-versions-jars-dir", ], aidl: { - local_include_dirs: [ - "apex/media/aidl/stable", - ], include_dirs: [ "packages/modules/Connectivity/framework/aidl-export", + "packages/modules/Media/apex/aidl/stable", ], }, } diff --git a/apex/media/Android.bp b/apex/media/Android.bp deleted file mode 100644 index 96e88dd236db..000000000000 --- a/apex/media/Android.bp +++ /dev/null @@ -1,31 +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 { - default_visibility: [ - ":__subpackages__", - "//frameworks/av/apex", - "//frameworks/av/apex/testing", - ], - // See: http://go/android-license-faq - default_applicable_licenses: ["Android-Apache-2.0"], -} - -sdk { - name: "media-module-sdk", - bootclasspath_fragments: ["com.android.media-bootclasspath-fragment"], - systemserverclasspath_fragments: ["com.android.media-systemserverclasspath-fragment"], - java_sdk_libs: [ - "framework-media", - ], -} diff --git a/apex/media/OWNERS b/apex/media/OWNERS deleted file mode 100644 index 2c5965c300e3..000000000000 --- a/apex/media/OWNERS +++ /dev/null @@ -1,12 +0,0 @@ -# Bug component: 1344 -hdmoon@google.com -jinpark@google.com -klhyun@google.com -lnilsson@google.com -sungsoo@google.com - -# go/android-fwk-media-solutions for info on areas of ownership. -include platform/frameworks/av:/media/janitors/media_solutions_OWNERS - -# media reliability team packages/delivers the media mainline builds. -include platform/frameworks/av:/media/janitors/reliability_mainline_OWNERS diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp deleted file mode 100644 index 4ba0d9b03b28..000000000000 --- a/apex/media/aidl/Android.bp +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright 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 { - // See: http://go/android-license-faq - default_applicable_licenses: ["Android-Apache-2.0"], -} - -filegroup { - name: "stable-media-aidl-srcs", - srcs: ["stable/**/*.aidl"], - path: "stable", -} - -filegroup { - name: "private-media-aidl-srcs", - srcs: ["private/**/I*.aidl"], - path: "private", -} - -filegroup { - name: "media-aidl-srcs", - srcs: [ - ":private-media-aidl-srcs", - ":stable-media-aidl-srcs", - ], -} diff --git a/apex/media/aidl/private/android/media/Controller2Link.aidl b/apex/media/aidl/private/android/media/Controller2Link.aidl deleted file mode 100644 index 64edafcb11fc..000000000000 --- a/apex/media/aidl/private/android/media/Controller2Link.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -parcelable Controller2Link; diff --git a/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl b/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl deleted file mode 100644 index e1c89e90b0f3..000000000000 --- a/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 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 android.media; - -import android.media.Session2Token; -import android.media.IMediaCommunicationServiceCallback; -import android.media.MediaParceledListSlice; -import android.view.KeyEvent; - -/** {@hide} */ -interface IMediaCommunicationService { - void notifySession2Created(in Session2Token sessionToken); - boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid); - MediaParceledListSlice getSession2Tokens(int userId); - - void dispatchMediaKeyEvent(String packageName, in KeyEvent keyEvent, boolean asSystemService); - - void registerCallback(IMediaCommunicationServiceCallback callback, String packageName); - void unregisterCallback(IMediaCommunicationServiceCallback callback); -} - diff --git a/apex/media/aidl/private/android/media/IMediaCommunicationServiceCallback.aidl b/apex/media/aidl/private/android/media/IMediaCommunicationServiceCallback.aidl deleted file mode 100644 index e347ebf4a983..000000000000 --- a/apex/media/aidl/private/android/media/IMediaCommunicationServiceCallback.aidl +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 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.media; - -import android.media.Session2Token; -import android.media.MediaParceledListSlice; - -/** {@hide} */ -oneway interface IMediaCommunicationServiceCallback { - void onSession2Created(in Session2Token token); - void onSession2Changed(in MediaParceledListSlice tokens); -} - diff --git a/apex/media/aidl/private/android/media/IMediaController2.aidl b/apex/media/aidl/private/android/media/IMediaController2.aidl deleted file mode 100644 index 42c6e70529ec..000000000000 --- a/apex/media/aidl/private/android/media/IMediaController2.aidl +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.os.Bundle; -import android.os.ResultReceiver; -import android.media.Session2Command; - -/** - * Interface from MediaSession2 to MediaController2. - * <p> - * Keep this interface oneway. Otherwise a malicious app may implement fake version of this, - * and holds calls from session to make session owner(s) frozen. - * @hide - */ - // Code for AML only -oneway interface IMediaController2 { - void notifyConnected(int seq, in Bundle connectionResult) = 0; - void notifyDisconnected(int seq) = 1; - void notifyPlaybackActiveChanged(int seq, boolean playbackActive) = 2; - void sendSessionCommand(int seq, in Session2Command command, in Bundle args, - in ResultReceiver resultReceiver) = 3; - void cancelSessionCommand(int seq) = 4; - // Next Id : 5 -} diff --git a/apex/media/aidl/private/android/media/IMediaSession2.aidl b/apex/media/aidl/private/android/media/IMediaSession2.aidl deleted file mode 100644 index 26e717b39afc..000000000000 --- a/apex/media/aidl/private/android/media/IMediaSession2.aidl +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.os.Bundle; -import android.os.ResultReceiver; -import android.media.Controller2Link; -import android.media.Session2Command; - -/** - * Interface from MediaController2 to MediaSession2. - * <p> - * Keep this interface oneway. Otherwise a malicious app may implement fake version of this, - * and holds calls from session to make session owner(s) frozen. - * @hide - */ - // Code for AML only -oneway interface IMediaSession2 { - void connect(in Controller2Link caller, int seq, in Bundle connectionRequest) = 0; - void disconnect(in Controller2Link caller, int seq) = 1; - void sendSessionCommand(in Controller2Link caller, int seq, in Session2Command sessionCommand, - in Bundle args, in ResultReceiver resultReceiver) = 2; - void cancelSessionCommand(in Controller2Link caller, int seq) = 3; - // Next Id : 4 -} diff --git a/apex/media/aidl/private/android/media/IMediaSession2Service.aidl b/apex/media/aidl/private/android/media/IMediaSession2Service.aidl deleted file mode 100644 index 10ac1be0a36e..000000000000 --- a/apex/media/aidl/private/android/media/IMediaSession2Service.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.os.Bundle; -import android.media.Controller2Link; - -/** - * Interface from MediaController2 to MediaSession2Service. - * <p> - * Keep this interface oneway. Otherwise a malicious app may implement fake version of this, - * and holds calls from controller to make controller owner(s) frozen. - * @hide - */ -oneway interface IMediaSession2Service { - void connect(in Controller2Link caller, int seq, in Bundle connectionRequest) = 0; - // Next Id : 1 -} diff --git a/apex/media/aidl/private/android/media/Session2Command.aidl b/apex/media/aidl/private/android/media/Session2Command.aidl deleted file mode 100644 index 43a7b123ed29..000000000000 --- a/apex/media/aidl/private/android/media/Session2Command.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -parcelable Session2Command; diff --git a/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl b/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl deleted file mode 100644 index 92d673fd25cb..000000000000 --- a/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 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 android.media; - -parcelable MediaParceledListSlice<T>; diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp deleted file mode 100644 index e38488d7a5a8..000000000000 --- a/apex/media/framework/Android.bp +++ /dev/null @@ -1,164 +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 { - // See: http://go/android-license-faq - default_applicable_licenses: ["Android-Apache-2.0"], -} - -java_library { - name: "updatable-media", - - srcs: [ - ":updatable-media-srcs", - ], - - permitted_packages: [ - "android.media", - ], - - optimize: { - enabled: true, - shrink: true, - proguard_flags_files: ["updatable-media-proguard.flags"], - }, - - installable: true, - - sdk_version: "module_current", - libs: [ - "androidx.annotation_annotation", - "framework-annotations-lib", - ], - static_libs: [ - "exoplayer2-extractor", - "mediatranscoding_aidl_interface-java", - "modules-annotation-minsdk", - "modules-utils-build", - ], - jarjar_rules: "jarjar_rules.txt", - - plugins: ["java_api_finder"], - - hostdex: true, // for hiddenapi check - apex_available: [ - "com.android.media", - "test_com.android.media", - ], - min_sdk_version: "29", - lint: { - strict_updatability_linting: true, - }, - visibility: [ - "//frameworks/av/apex:__subpackages__", - "//frameworks/base/apex/media/service", - "//frameworks/base/api", // For framework-all - "//packages/modules/Media/apex/service", - ], -} - -filegroup { - name: "updatable-media-srcs", - srcs: [ - "java/android/media/MediaFrameworkInitializer.java", - ":media-aidl-srcs", - ":mediaparceledlistslice-java-srcs", - ":mediaparser-srcs", - ":mediasession2-java-srcs", - ":mediatranscoding-srcs", - ], - visibility: ["//frameworks/base"], -} - -filegroup { - name: "mediasession2-java-srcs", - srcs: [ - "java/android/media/Controller2Link.java", - "java/android/media/MediaConstants.java", - "java/android/media/MediaController2.java", - "java/android/media/MediaSession2.java", - "java/android/media/MediaSession2Service.java", - "java/android/media/Session2Command.java", - "java/android/media/Session2CommandGroup.java", - "java/android/media/Session2Link.java", - "java/android/media/Session2Token.java", - "java/android/media/MediaCommunicationManager.java", - ], - path: "java", -} - -filegroup { - name: "mediaparceledlistslice-java-srcs", - srcs: [ - "java/android/media/MediaParceledListSlice.java", - "java/android/media/BaseMediaParceledListSlice.java", - ], - path: "java", -} - -filegroup { - name: "mediaparser-srcs", - srcs: [ - "java/android/media/MediaParser.java", - ], - path: "java", -} - -filegroup { - name: "mediatranscoding-srcs", - srcs: [ - "java/android/media/ApplicationMediaCapabilities.java", - "java/android/media/MediaFeature.java", - "java/android/media/MediaTranscodingManager.java", - ], - path: "java", -} - -java_sdk_library { - name: "framework-media", - defaults: ["framework-module-defaults"], - - // This is only used to define the APIs for updatable-media. - api_only: true, - - srcs: [ - ":updatable-media-srcs", - ], - - impl_library_visibility: ["//frameworks/av/apex:__subpackages__"], -} - -cc_library_shared { - name: "libmediaparser-jni", - srcs: [ - "jni/android_media_MediaParserJNI.cpp", - ], - header_libs: ["jni_headers"], - shared_libs: [ - "libandroid", - "liblog", - "libmediametrics", - ], - cflags: [ - "-Wall", - "-Werror", - "-Wno-unused-parameter", - "-Wunreachable-code", - "-Wunused", - ], - apex_available: [ - "com.android.media", - ], - min_sdk_version: "29", -} diff --git a/apex/media/framework/TEST_MAPPING b/apex/media/framework/TEST_MAPPING deleted file mode 100644 index 3d2191410413..000000000000 --- a/apex/media/framework/TEST_MAPPING +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presubmit": [ - { - "name": "CtsMediaParserTestCases" - }, - { - "name": "CtsMediaParserHostTestCases" - } - ] -} diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt deleted file mode 100644 index b7d7ed866c89..000000000000 --- a/apex/media/framework/api/current.txt +++ /dev/null @@ -1,267 +0,0 @@ -// Signature format: 2.0 -package android.media { - - public final class ApplicationMediaCapabilities implements android.os.Parcelable { - method @NonNull public static android.media.ApplicationMediaCapabilities createFromXml(@NonNull org.xmlpull.v1.XmlPullParser); - method public int describeContents(); - method @NonNull public java.util.List<java.lang.String> getSupportedHdrTypes(); - method @NonNull public java.util.List<java.lang.String> getSupportedVideoMimeTypes(); - method @NonNull public java.util.List<java.lang.String> getUnsupportedHdrTypes(); - method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes(); - method public boolean isFormatSpecified(@NonNull String); - method public boolean isHdrTypeSupported(@NonNull String); - method public boolean isVideoMimeTypeSupported(@NonNull String); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR; - } - - public static final class ApplicationMediaCapabilities.Builder { - ctor public ApplicationMediaCapabilities.Builder(); - method @NonNull public android.media.ApplicationMediaCapabilities.Builder addSupportedHdrType(@NonNull String); - method @NonNull public android.media.ApplicationMediaCapabilities.Builder addSupportedVideoMimeType(@NonNull String); - method @NonNull public android.media.ApplicationMediaCapabilities.Builder addUnsupportedHdrType(@NonNull String); - method @NonNull public android.media.ApplicationMediaCapabilities.Builder addUnsupportedVideoMimeType(@NonNull String); - method @NonNull public android.media.ApplicationMediaCapabilities build(); - } - - public class MediaCommunicationManager { - method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens(); - method @IntRange(from=1) public int getVersion(); - } - - public class MediaController2 implements java.lang.AutoCloseable { - method public void cancelSessionCommand(@NonNull Object); - method public void close(); - method @Nullable public android.media.Session2Token getConnectedToken(); - method public boolean isPlaybackActive(); - method @NonNull public Object sendSessionCommand(@NonNull android.media.Session2Command, @Nullable android.os.Bundle); - } - - public static final class MediaController2.Builder { - ctor public MediaController2.Builder(@NonNull android.content.Context, @NonNull android.media.Session2Token); - method @NonNull public android.media.MediaController2 build(); - method @NonNull public android.media.MediaController2.Builder setConnectionHints(@NonNull android.os.Bundle); - method @NonNull public android.media.MediaController2.Builder setControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaController2.ControllerCallback); - } - - public abstract static class MediaController2.ControllerCallback { - ctor public MediaController2.ControllerCallback(); - method public void onCommandResult(@NonNull android.media.MediaController2, @NonNull Object, @NonNull android.media.Session2Command, @NonNull android.media.Session2Command.Result); - method public void onConnected(@NonNull android.media.MediaController2, @NonNull android.media.Session2CommandGroup); - method public void onDisconnected(@NonNull android.media.MediaController2); - method public void onPlaybackActiveChanged(@NonNull android.media.MediaController2, boolean); - method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaController2, @NonNull android.media.Session2Command, @Nullable android.os.Bundle); - } - - public final class MediaFeature { - ctor public MediaFeature(); - } - - public static final class MediaFeature.HdrType { - field public static final String DOLBY_VISION = "android.media.feature.hdr.dolby_vision"; - field public static final String HDR10 = "android.media.feature.hdr.hdr10"; - field public static final String HDR10_PLUS = "android.media.feature.hdr.hdr10_plus"; - field public static final String HLG = "android.media.feature.hdr.hlg"; - } - - public final class MediaParser { - method public boolean advance(@NonNull android.media.MediaParser.SeekableInputReader) throws java.io.IOException; - method @NonNull public static android.media.MediaParser create(@NonNull android.media.MediaParser.OutputConsumer, @NonNull java.lang.String...); - method @NonNull public static android.media.MediaParser createByName(@NonNull String, @NonNull android.media.MediaParser.OutputConsumer); - method @NonNull public android.media.metrics.LogSessionId getLogSessionId(); - method @NonNull public String getParserName(); - method @NonNull public static java.util.List<java.lang.String> getParserNames(@NonNull android.media.MediaFormat); - method public void release(); - method public void seek(@NonNull android.media.MediaParser.SeekPoint); - method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId); - method @NonNull public android.media.MediaParser setParameter(@NonNull String, @NonNull Object); - method public boolean supportsParameter(@NonNull String); - field public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING = "android.media.mediaparser.adts.enableCbrSeeking"; - field public static final String PARAMETER_AMR_ENABLE_CBR_SEEKING = "android.media.mediaparser.amr.enableCbrSeeking"; - field public static final String PARAMETER_FLAC_DISABLE_ID3 = "android.media.mediaparser.flac.disableId3"; - field public static final String PARAMETER_MATROSKA_DISABLE_CUES_SEEKING = "android.media.mediaparser.matroska.disableCuesSeeking"; - field public static final String PARAMETER_MP3_DISABLE_ID3 = "android.media.mediaparser.mp3.disableId3"; - field public static final String PARAMETER_MP3_ENABLE_CBR_SEEKING = "android.media.mediaparser.mp3.enableCbrSeeking"; - field public static final String PARAMETER_MP3_ENABLE_INDEX_SEEKING = "android.media.mediaparser.mp3.enableIndexSeeking"; - field public static final String PARAMETER_MP4_IGNORE_EDIT_LISTS = "android.media.mediaparser.mp4.ignoreEditLists"; - field public static final String PARAMETER_MP4_IGNORE_TFDT_BOX = "android.media.mediaparser.mp4.ignoreTfdtBox"; - field public static final String PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES = "android.media.mediaparser.mp4.treatVideoFramesAsKeyframes"; - field public static final String PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES = "android.media.mediaparser.ts.allowNonIdrAvcKeyframes"; - field public static final String PARAMETER_TS_DETECT_ACCESS_UNITS = "android.media.mediaparser.ts.ignoreDetectAccessUnits"; - field public static final String PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS = "android.media.mediaparser.ts.enableHdmvDtsAudioStreams"; - field public static final String PARAMETER_TS_IGNORE_AAC_STREAM = "android.media.mediaparser.ts.ignoreAacStream"; - field public static final String PARAMETER_TS_IGNORE_AVC_STREAM = "android.media.mediaparser.ts.ignoreAvcStream"; - field public static final String PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM = "android.media.mediaparser.ts.ignoreSpliceInfoStream"; - field public static final String PARAMETER_TS_MODE = "android.media.mediaparser.ts.mode"; - field public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser"; - field public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser"; - field public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser"; - field public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser"; - field public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser"; - field public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser"; - field public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser"; - field public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser"; - field public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser"; - field public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser"; - field public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser"; - field public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser"; - field public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser"; - field public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN"; - field public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser"; - field public static final int SAMPLE_FLAG_DECODE_ONLY = -2147483648; // 0x80000000 - field public static final int SAMPLE_FLAG_ENCRYPTED = 1073741824; // 0x40000000 - field public static final int SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA = 268435456; // 0x10000000 - field public static final int SAMPLE_FLAG_KEY_FRAME = 1; // 0x1 - field public static final int SAMPLE_FLAG_LAST_SAMPLE = 536870912; // 0x20000000 - } - - public static interface MediaParser.InputReader { - method public long getLength(); - method public long getPosition(); - method public int read(@NonNull byte[], int, int) throws java.io.IOException; - } - - public static interface MediaParser.OutputConsumer { - method public void onSampleCompleted(int, long, int, int, int, @Nullable android.media.MediaCodec.CryptoInfo); - method public void onSampleDataFound(int, @NonNull android.media.MediaParser.InputReader) throws java.io.IOException; - method public void onSeekMapFound(@NonNull android.media.MediaParser.SeekMap); - method public void onTrackCountFound(int); - method public void onTrackDataFound(int, @NonNull android.media.MediaParser.TrackData); - } - - public static final class MediaParser.ParsingException extends java.io.IOException { - } - - public static final class MediaParser.SeekMap { - method public long getDurationMicros(); - method @NonNull public android.util.Pair<android.media.MediaParser.SeekPoint,android.media.MediaParser.SeekPoint> getSeekPoints(long); - method public boolean isSeekable(); - field public static final int UNKNOWN_DURATION = -2147483648; // 0x80000000 - } - - public static final class MediaParser.SeekPoint { - field @NonNull public static final android.media.MediaParser.SeekPoint START; - field public final long position; - field public final long timeMicros; - } - - public static interface MediaParser.SeekableInputReader extends android.media.MediaParser.InputReader { - method public void seekToPosition(long); - } - - public static final class MediaParser.TrackData { - field @Nullable public final android.media.DrmInitData drmInitData; - field @NonNull public final android.media.MediaFormat mediaFormat; - } - - public static final class MediaParser.UnrecognizedInputFormatException extends java.io.IOException { - } - - public class MediaSession2 implements java.lang.AutoCloseable { - method public void broadcastSessionCommand(@NonNull android.media.Session2Command, @Nullable android.os.Bundle); - method public void cancelSessionCommand(@NonNull android.media.MediaSession2.ControllerInfo, @NonNull Object); - method public void close(); - method @NonNull public java.util.List<android.media.MediaSession2.ControllerInfo> getConnectedControllers(); - method @NonNull public String getId(); - method @NonNull public android.media.Session2Token getToken(); - method public boolean isPlaybackActive(); - method @NonNull public Object sendSessionCommand(@NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle); - method public void setPlaybackActive(boolean); - } - - public static final class MediaSession2.Builder { - ctor public MediaSession2.Builder(@NonNull android.content.Context); - method @NonNull public android.media.MediaSession2 build(); - method @NonNull public android.media.MediaSession2.Builder setExtras(@NonNull android.os.Bundle); - method @NonNull public android.media.MediaSession2.Builder setId(@NonNull String); - method @NonNull public android.media.MediaSession2.Builder setSessionActivity(@Nullable android.app.PendingIntent); - method @NonNull public android.media.MediaSession2.Builder setSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaSession2.SessionCallback); - } - - public static final class MediaSession2.ControllerInfo { - method @NonNull public android.os.Bundle getConnectionHints(); - method @NonNull public String getPackageName(); - method @NonNull public android.media.session.MediaSessionManager.RemoteUserInfo getRemoteUserInfo(); - method public int getUid(); - } - - public abstract static class MediaSession2.SessionCallback { - ctor public MediaSession2.SessionCallback(); - method public void onCommandResult(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull Object, @NonNull android.media.Session2Command, @NonNull android.media.Session2Command.Result); - method @Nullable public android.media.Session2CommandGroup onConnect(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo); - method public void onDisconnected(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo); - method public void onPostConnect(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo); - method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle); - } - - public abstract class MediaSession2Service extends android.app.Service { - ctor public MediaSession2Service(); - method public final void addSession(@NonNull android.media.MediaSession2); - method @NonNull public final java.util.List<android.media.MediaSession2> getSessions(); - method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); - method @Nullable public abstract android.media.MediaSession2 onGetSession(@NonNull android.media.MediaSession2.ControllerInfo); - method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2); - method public final void removeSession(@NonNull android.media.MediaSession2); - field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; - } - - public static class MediaSession2Service.MediaNotification { - ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification); - method @NonNull public android.app.Notification getNotification(); - method public int getNotificationId(); - } - - public final class Session2Command implements android.os.Parcelable { - ctor public Session2Command(int); - ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle); - method public int describeContents(); - method public int getCommandCode(); - method @Nullable public String getCustomAction(); - method @Nullable public android.os.Bundle getCustomExtras(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0 - field @NonNull public static final android.os.Parcelable.Creator<android.media.Session2Command> CREATOR; - } - - public static final class Session2Command.Result { - ctor public Session2Command.Result(int, @Nullable android.os.Bundle); - method public int getResultCode(); - method @Nullable public android.os.Bundle getResultData(); - field public static final int RESULT_ERROR_UNKNOWN_ERROR = -1; // 0xffffffff - field public static final int RESULT_INFO_SKIPPED = 1; // 0x1 - field public static final int RESULT_SUCCESS = 0; // 0x0 - } - - public final class Session2CommandGroup implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.Set<android.media.Session2Command> getCommands(); - method public boolean hasCommand(@NonNull android.media.Session2Command); - method public boolean hasCommand(int); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.media.Session2CommandGroup> CREATOR; - } - - public static final class Session2CommandGroup.Builder { - ctor public Session2CommandGroup.Builder(); - ctor public Session2CommandGroup.Builder(@NonNull android.media.Session2CommandGroup); - method @NonNull public android.media.Session2CommandGroup.Builder addCommand(@NonNull android.media.Session2Command); - method @NonNull public android.media.Session2CommandGroup build(); - method @NonNull public android.media.Session2CommandGroup.Builder removeCommand(@NonNull android.media.Session2Command); - } - - public final class Session2Token implements android.os.Parcelable { - ctor public Session2Token(@NonNull android.content.Context, @NonNull android.content.ComponentName); - method public int describeContents(); - method @NonNull public android.os.Bundle getExtras(); - method @NonNull public String getPackageName(); - method @Nullable public String getServiceName(); - method public int getType(); - method public int getUid(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.media.Session2Token> CREATOR; - field public static final int TYPE_SESSION = 0; // 0x0 - field public static final int TYPE_SESSION_SERVICE = 1; // 0x1 - } - -} - diff --git a/apex/media/framework/api/module-lib-current.txt b/apex/media/framework/api/module-lib-current.txt deleted file mode 100644 index 7317f148b17b..000000000000 --- a/apex/media/framework/api/module-lib-current.txt +++ /dev/null @@ -1,31 +0,0 @@ -// Signature format: 2.0 -package android.media { - - public class MediaCommunicationManager { - method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent, boolean); - method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaCommunicationManager.SessionCallback); - method public void unregisterSessionCallback(@NonNull android.media.MediaCommunicationManager.SessionCallback); - } - - public static interface MediaCommunicationManager.SessionCallback { - method public default void onSession2TokenCreated(@NonNull android.media.Session2Token); - method public default void onSession2TokensChanged(@NonNull java.util.List<android.media.Session2Token>); - } - - public class MediaFrameworkInitializer { - method public static void registerServiceWrappers(); - method public static void setMediaServiceManager(@NonNull android.media.MediaServiceManager); - } - - @Deprecated public final class MediaParceledListSlice<T extends android.os.Parcelable> implements android.os.Parcelable { - ctor @Deprecated public MediaParceledListSlice(@NonNull java.util.List<T>); - method @Deprecated public int describeContents(); - method @Deprecated @NonNull public static <T extends android.os.Parcelable> android.media.MediaParceledListSlice<T> emptyList(); - method @Deprecated public java.util.List<T> getList(); - method @Deprecated public void setInlineCountLimit(int); - method @Deprecated public void writeToParcel(android.os.Parcel, int); - field @Deprecated @NonNull public static final android.os.Parcelable.ClassLoaderCreator<android.media.MediaParceledListSlice> CREATOR; - } - -} - diff --git a/apex/media/framework/api/module-lib-removed.txt b/apex/media/framework/api/module-lib-removed.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/apex/media/framework/api/module-lib-removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/apex/media/framework/api/removed.txt b/apex/media/framework/api/removed.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/apex/media/framework/api/removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/apex/media/framework/api/system-current.txt b/apex/media/framework/api/system-current.txt deleted file mode 100644 index 6eea769d9f57..000000000000 --- a/apex/media/framework/api/system-current.txt +++ /dev/null @@ -1,68 +0,0 @@ -// Signature format: 2.0 -package android.media { - - public final class MediaTranscodingManager { - method @Nullable public android.media.MediaTranscodingManager.TranscodingSession enqueueRequest(@NonNull android.media.MediaTranscodingManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodingManager.OnTranscodingFinishedListener); - } - - @java.lang.FunctionalInterface public static interface MediaTranscodingManager.OnTranscodingFinishedListener { - method public void onTranscodingFinished(@NonNull android.media.MediaTranscodingManager.TranscodingSession); - } - - public abstract static class MediaTranscodingManager.TranscodingRequest { - method public int getClientPid(); - method public int getClientUid(); - method @Nullable public android.os.ParcelFileDescriptor getDestinationFileDescriptor(); - method @NonNull public android.net.Uri getDestinationUri(); - method @Nullable public android.os.ParcelFileDescriptor getSourceFileDescriptor(); - method @NonNull public android.net.Uri getSourceUri(); - } - - public static class MediaTranscodingManager.TranscodingRequest.VideoFormatResolver { - ctor public MediaTranscodingManager.TranscodingRequest.VideoFormatResolver(@NonNull android.media.ApplicationMediaCapabilities, @NonNull android.media.MediaFormat); - method @Nullable public android.media.MediaFormat resolveVideoFormat(); - method public boolean shouldTranscode(); - } - - public static final class MediaTranscodingManager.TranscodingSession { - method public boolean addClientUid(int); - method public void cancel(); - method @NonNull public java.util.List<java.lang.Integer> getClientUids(); - method public int getErrorCode(); - method @IntRange(from=0, to=100) public int getProgress(); - method public int getResult(); - method public int getSessionId(); - method public int getStatus(); - method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodingManager.TranscodingSession.OnProgressUpdateListener); - field public static final int ERROR_DROPPED_BY_SERVICE = 1; // 0x1 - field public static final int ERROR_NONE = 0; // 0x0 - field public static final int ERROR_SERVICE_DIED = 2; // 0x2 - field public static final int RESULT_CANCELED = 4; // 0x4 - field public static final int RESULT_ERROR = 3; // 0x3 - field public static final int RESULT_NONE = 1; // 0x1 - field public static final int RESULT_SUCCESS = 2; // 0x2 - field public static final int STATUS_FINISHED = 3; // 0x3 - field public static final int STATUS_PAUSED = 4; // 0x4 - field public static final int STATUS_PENDING = 1; // 0x1 - field public static final int STATUS_RUNNING = 2; // 0x2 - } - - @java.lang.FunctionalInterface public static interface MediaTranscodingManager.TranscodingSession.OnProgressUpdateListener { - method public void onProgressUpdate(@NonNull android.media.MediaTranscodingManager.TranscodingSession, @IntRange(from=0, to=100) int); - } - - public static final class MediaTranscodingManager.VideoTranscodingRequest extends android.media.MediaTranscodingManager.TranscodingRequest { - method @NonNull public android.media.MediaFormat getVideoTrackFormat(); - } - - public static final class MediaTranscodingManager.VideoTranscodingRequest.Builder { - ctor public MediaTranscodingManager.VideoTranscodingRequest.Builder(@NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.media.MediaFormat); - method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest build(); - method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest.Builder setClientPid(int); - method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest.Builder setClientUid(int); - method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest.Builder setDestinationFileDescriptor(@NonNull android.os.ParcelFileDescriptor); - method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest.Builder setSourceFileDescriptor(@NonNull android.os.ParcelFileDescriptor); - } - -} - diff --git a/apex/media/framework/api/system-removed.txt b/apex/media/framework/api/system-removed.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/apex/media/framework/api/system-removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/apex/media/framework/jarjar_rules.txt b/apex/media/framework/jarjar_rules.txt deleted file mode 100644 index 91489dcee0a1..000000000000 --- a/apex/media/framework/jarjar_rules.txt +++ /dev/null @@ -1,2 +0,0 @@ -rule com.android.modules.** android.media.internal.@1 -rule com.google.android.exoplayer2.** android.media.internal.exo.@1 diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java deleted file mode 100644 index 97fa0eca0862..000000000000 --- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java +++ /dev/null @@ -1,626 +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 android.media; - -import android.annotation.NonNull; -import android.content.ContentResolver; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import com.android.modules.annotation.MinSdk; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - ApplicationMediaCapabilities is an immutable class that encapsulates an application's capabilities - for handling newer video codec format and media features. - - <p> - 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>. By default, Android assumes apps can support playback of all - media formats. Apps that would like to request that media be transcoded into a more compatible - format should declare their media capabilities in a media_capabilities.xml resource file and add it - as a property tag in the AndroidManifest.xml file. Here is a example: - <pre> - {@code - <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> - <format android:name="HEVC" supported="true"/> - <format android:name="HDR10" supported="false"/> - <format android:name="HDR10Plus" supported="false"/> - </media-capabilities> - } - </pre> - The ApplicationMediaCapabilities class is generated from this xml and used by the platform to - represent an application's media capabilities in order to determine whether modern media files need - to be transcoded for that application. - </p> - - <p> - ApplicationMediaCapabilities objects can also be built by applications at runtime for use with - {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)} to provide more - control over the transcoding that is built into the platform. ApplicationMediaCapabilities - provided by applications at runtime like this override the default manifest capabilities for that - media access.The object could be build either through {@link #createFromXml(XmlPullParser)} or - through the builder class {@link ApplicationMediaCapabilities.Builder} - - <h3> Video Codec Support</h3> - <p> - Newer video codes include HEVC, VP9 and AV1. Application only needs to indicate their support - for newer format with this class as they are assumed to support older format like h.264. - - <h3>Capability of handling HDR(high dynamic range) video</h3> - <p> - There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform, - application will only need to specify individual types they supported. - */ -@MinSdk(Build.VERSION_CODES.S) -public final class ApplicationMediaCapabilities implements Parcelable { - private static final String TAG = "ApplicationMediaCapabilities"; - - /** List of supported video codec mime types. */ - private Set<String> mSupportedVideoMimeTypes = new HashSet<>(); - - /** List of unsupported video codec mime types. */ - private Set<String> mUnsupportedVideoMimeTypes = new HashSet<>(); - - /** List of supported hdr types. */ - private Set<String> mSupportedHdrTypes = new HashSet<>(); - - /** List of unsupported hdr types. */ - private Set<String> mUnsupportedHdrTypes = new HashSet<>(); - - private boolean mIsSlowMotionSupported = false; - - private ApplicationMediaCapabilities(Builder b) { - mSupportedVideoMimeTypes.addAll(b.getSupportedVideoMimeTypes()); - mUnsupportedVideoMimeTypes.addAll(b.getUnsupportedVideoMimeTypes()); - mSupportedHdrTypes.addAll(b.getSupportedHdrTypes()); - mUnsupportedHdrTypes.addAll(b.getUnsupportedHdrTypes()); - mIsSlowMotionSupported = b.mIsSlowMotionSupported; - } - - /** - * Query if a video codec format is supported by the application. - * <p> - * If the application has not specified supporting the format or not, this will return false. - * Use {@link #isFormatSpecified(String)} to query if a format is specified or not. - * - * @param videoMime The mime type of the video codec format. Must be the one used in - * {@link MediaFormat#KEY_MIME}. - * @return true if application supports the video codec format, false otherwise. - */ - public boolean isVideoMimeTypeSupported( - @NonNull String videoMime) { - if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) { - return true; - } - return false; - } - - /** - * Query if a HDR type is supported by the application. - * <p> - * If the application has not specified supporting the format or not, this will return false. - * Use {@link #isFormatSpecified(String)} to query if a format is specified or not. - * - * @param hdrType The type of the HDR format. - * @return true if application supports the HDR format, false otherwise. - */ - public boolean isHdrTypeSupported( - @NonNull @MediaFeature.MediaHdrType String hdrType) { - if (mSupportedHdrTypes.contains(hdrType)) { - return true; - } - return false; - } - - /** - * Query if a format is specified by the application. - * <p> - * The format could be either the video format or the hdr format. - * - * @param format The name of the format. - * @return true if application specifies the format, false otherwise. - */ - public boolean isFormatSpecified(@NonNull String format) { - if (mSupportedVideoMimeTypes.contains(format) || mUnsupportedVideoMimeTypes.contains(format) - || mSupportedHdrTypes.contains(format) || mUnsupportedHdrTypes.contains(format)) { - return true; - - } - return false; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - // Write out the supported video mime types. - dest.writeInt(mSupportedVideoMimeTypes.size()); - for (String cap : mSupportedVideoMimeTypes) { - dest.writeString(cap); - } - // Write out the unsupported video mime types. - dest.writeInt(mUnsupportedVideoMimeTypes.size()); - for (String cap : mUnsupportedVideoMimeTypes) { - dest.writeString(cap); - } - // Write out the supported hdr types. - dest.writeInt(mSupportedHdrTypes.size()); - for (String cap : mSupportedHdrTypes) { - dest.writeString(cap); - } - // Write out the unsupported hdr types. - dest.writeInt(mUnsupportedHdrTypes.size()); - for (String cap : mUnsupportedHdrTypes) { - dest.writeString(cap); - } - // Write out the supported slow motion. - dest.writeBoolean(mIsSlowMotionSupported); - } - - @Override - public String toString() { - String caps = new String( - "Supported Video MimeTypes: " + mSupportedVideoMimeTypes.toString()); - caps += "Unsupported Video MimeTypes: " + mUnsupportedVideoMimeTypes.toString(); - caps += "Supported HDR types: " + mSupportedHdrTypes.toString(); - caps += "Unsupported HDR types: " + mUnsupportedHdrTypes.toString(); - caps += "Supported slow motion: " + mIsSlowMotionSupported; - return caps; - } - - @NonNull - public static final Creator<ApplicationMediaCapabilities> CREATOR = - new Creator<ApplicationMediaCapabilities>() { - public ApplicationMediaCapabilities createFromParcel(Parcel in) { - ApplicationMediaCapabilities.Builder builder = - new ApplicationMediaCapabilities.Builder(); - - // Parse supported video codec mime types. - int count = in.readInt(); - for (int readCount = 0; readCount < count; ++readCount) { - builder.addSupportedVideoMimeType(in.readString()); - } - - // Parse unsupported video codec mime types. - count = in.readInt(); - for (int readCount = 0; readCount < count; ++readCount) { - builder.addUnsupportedVideoMimeType(in.readString()); - } - - // Parse supported hdr types. - count = in.readInt(); - for (int readCount = 0; readCount < count; ++readCount) { - builder.addSupportedHdrType(in.readString()); - } - - // Parse unsupported hdr types. - count = in.readInt(); - for (int readCount = 0; readCount < count; ++readCount) { - builder.addUnsupportedHdrType(in.readString()); - } - - boolean supported = in.readBoolean(); - builder.setSlowMotionSupported(supported); - - return builder.build(); - } - - public ApplicationMediaCapabilities[] newArray(int size) { - return new ApplicationMediaCapabilities[size]; - } - }; - - /** - * Query the video codec mime types supported by the application. - * @return List of supported video codec mime types. The list will be empty if there are none. - */ - @NonNull - public List<String> getSupportedVideoMimeTypes() { - return new ArrayList<>(mSupportedVideoMimeTypes); - } - - /** - * Query the video codec mime types that are not supported by the application. - * @return List of unsupported video codec mime types. The list will be empty if there are none. - */ - @NonNull - public List<String> getUnsupportedVideoMimeTypes() { - return new ArrayList<>(mUnsupportedVideoMimeTypes); - } - - /** - * Query all hdr types that are supported by the application. - * @return List of supported hdr types. The list will be empty if there are none. - */ - @NonNull - public List<String> getSupportedHdrTypes() { - return new ArrayList<>(mSupportedHdrTypes); - } - - /** - * Query all hdr types that are not supported by the application. - * @return List of unsupported hdr types. The list will be empty if there are none. - */ - @NonNull - public List<String> getUnsupportedHdrTypes() { - return new ArrayList<>(mUnsupportedHdrTypes); - } - - /** - * Whether handling of slow-motion video is supported - * @hide - */ - public boolean isSlowMotionSupported() { - return mIsSlowMotionSupported; - } - - /** - * Creates {@link ApplicationMediaCapabilities} from an xml. - * - * The xml's syntax is the same as the media_capabilities.xml used by the AndroidManifest.xml. - * <p> Here is an example: - * - * <pre> - * {@code - * <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> - * <format android:name="HEVC" supported="true"/> - * <format android:name="HDR10" supported="false"/> - * <format android:name="HDR10Plus" supported="false"/> - * </media-capabilities> - * } - * </pre> - * <p> - * - * @param xmlParser The underlying {@link XmlPullParser} that will read the xml. - * @return An ApplicationMediaCapabilities object. - * @throws UnsupportedOperationException if the capabilities in xml config are invalid or - * incompatible. - */ - // TODO: Add developer.android.com link for the format of the xml. - @NonNull - public static ApplicationMediaCapabilities createFromXml(@NonNull XmlPullParser xmlParser) { - ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder(); - builder.parseXml(xmlParser); - return builder.build(); - } - - /** - * Builder class for {@link ApplicationMediaCapabilities} objects. - * Use this class to configure and create an ApplicationMediaCapabilities instance. Builder - * could be created from an existing ApplicationMediaCapabilities object, from a xml file or - * MediaCodecList. - * //TODO(hkuang): Add xml parsing support to the builder. - */ - public final static class Builder { - /** List of supported video codec mime types. */ - private Set<String> mSupportedVideoMimeTypes = new HashSet<>(); - - /** List of supported hdr types. */ - private Set<String> mSupportedHdrTypes = new HashSet<>(); - - /** List of unsupported video codec mime types. */ - private Set<String> mUnsupportedVideoMimeTypes = new HashSet<>(); - - /** List of unsupported hdr types. */ - private Set<String> mUnsupportedHdrTypes = new HashSet<>(); - - private boolean mIsSlowMotionSupported = false; - - /* Map to save the format read from the xml. */ - private Map<String, Boolean> mFormatSupportedMap = new HashMap<String, Boolean>(); - - /** - * Constructs a new Builder with all the supports default to false. - */ - public Builder() { - } - - private void parseXml(@NonNull XmlPullParser xmlParser) - throws UnsupportedOperationException { - if (xmlParser == null) { - throw new IllegalArgumentException("XmlParser must not be null"); - } - - try { - while (xmlParser.next() != XmlPullParser.START_TAG) { - continue; - } - - // Validates the tag is "media-capabilities". - if (!xmlParser.getName().equals("media-capabilities")) { - throw new UnsupportedOperationException("Invalid tag"); - } - - xmlParser.next(); - while (xmlParser.getEventType() != XmlPullParser.END_TAG) { - while (xmlParser.getEventType() != XmlPullParser.START_TAG) { - if (xmlParser.getEventType() == XmlPullParser.END_DOCUMENT) { - return; - } - xmlParser.next(); - } - - // Validates the tag is "format". - if (xmlParser.getName().equals("format")) { - parseFormatTag(xmlParser); - } else { - throw new UnsupportedOperationException("Invalid tag"); - } - while (xmlParser.getEventType() != XmlPullParser.END_TAG) { - xmlParser.next(); - } - xmlParser.next(); - } - } catch (XmlPullParserException xppe) { - throw new UnsupportedOperationException("Ill-formatted xml file"); - } catch (java.io.IOException ioe) { - throw new UnsupportedOperationException("Unable to read xml file"); - } - } - - private void parseFormatTag(XmlPullParser xmlParser) { - String name = null; - String supported = null; - for (int i = 0; i < xmlParser.getAttributeCount(); i++) { - String attrName = xmlParser.getAttributeName(i); - if (attrName.equals("name")) { - name = xmlParser.getAttributeValue(i); - } else if (attrName.equals("supported")) { - supported = xmlParser.getAttributeValue(i); - } else { - throw new UnsupportedOperationException("Invalid attribute name " + attrName); - } - } - - if (name != null && supported != null) { - if (!supported.equals("true") && !supported.equals("false")) { - throw new UnsupportedOperationException( - ("Supported value must be either true or false")); - } - boolean isSupported = Boolean.parseBoolean(supported); - - // Check if the format is already found before. - if (mFormatSupportedMap.get(name) != null && mFormatSupportedMap.get(name) - != isSupported) { - throw new UnsupportedOperationException( - "Format: " + name + " has conflict supported value"); - } - - switch (name) { - case "HEVC": - if (isSupported) { - mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_HEVC); - } else { - mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_HEVC); - } - break; - case "VP9": - if (isSupported) { - mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_VP9); - } else { - mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_VP9); - } - break; - case "AV1": - if (isSupported) { - mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_AV1); - } else { - mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_AV1); - } - break; - case "HDR10": - if (isSupported) { - mSupportedHdrTypes.add(MediaFeature.HdrType.HDR10); - } else { - mUnsupportedHdrTypes.add(MediaFeature.HdrType.HDR10); - } - break; - case "HDR10Plus": - if (isSupported) { - mSupportedHdrTypes.add(MediaFeature.HdrType.HDR10_PLUS); - } else { - mUnsupportedHdrTypes.add(MediaFeature.HdrType.HDR10_PLUS); - } - break; - case "Dolby-Vision": - if (isSupported) { - mSupportedHdrTypes.add(MediaFeature.HdrType.DOLBY_VISION); - } else { - mUnsupportedHdrTypes.add(MediaFeature.HdrType.DOLBY_VISION); - } - break; - case "HLG": - if (isSupported) { - mSupportedHdrTypes.add(MediaFeature.HdrType.HLG); - } else { - mUnsupportedHdrTypes.add(MediaFeature.HdrType.HLG); - } - break; - case "SlowMotion": - mIsSlowMotionSupported = isSupported; - break; - default: - Log.w(TAG, "Invalid format name " + name); - } - // Save the name and isSupported into the map for validate later. - mFormatSupportedMap.put(name, isSupported); - } else { - throw new UnsupportedOperationException( - "Format name and supported must both be specified"); - } - } - - /** - * Builds a {@link ApplicationMediaCapabilities} object. - * - * @return a new {@link ApplicationMediaCapabilities} 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 ApplicationMediaCapabilities build() { - Log.d(TAG, - "Building ApplicationMediaCapabilities with: (Supported HDR: " - + mSupportedHdrTypes.toString() + " Unsupported HDR: " - + mUnsupportedHdrTypes.toString() + ") (Supported Codec: " - + " " + mSupportedVideoMimeTypes.toString() + " Unsupported Codec:" - + mUnsupportedVideoMimeTypes.toString() + ") " - + mIsSlowMotionSupported); - - // If hdr is supported, application must also support hevc. - if (!mSupportedHdrTypes.isEmpty() && !mSupportedVideoMimeTypes.contains( - MediaFormat.MIMETYPE_VIDEO_HEVC)) { - throw new UnsupportedOperationException("Only support HEVC mime type"); - } - return new ApplicationMediaCapabilities(this); - } - - /** - * Adds a supported video codec mime type. - * - * @param codecMime Supported codec mime types. Must be one of the mime type defined - * in {@link MediaFormat}. - * @throws IllegalArgumentException if mime type is not valid. - */ - @NonNull - public Builder addSupportedVideoMimeType( - @NonNull String codecMime) { - mSupportedVideoMimeTypes.add(codecMime); - return this; - } - - private List<String> getSupportedVideoMimeTypes() { - return new ArrayList<>(mSupportedVideoMimeTypes); - } - - private boolean isValidVideoCodecMimeType(@NonNull String codecMime) { - if (!codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC) - && !codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9) - && !codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) { - return false; - } - return true; - } - - /** - * Adds an unsupported video codec mime type. - * - * @param codecMime Unsupported codec mime type. Must be one of the mime type defined - * in {@link MediaFormat}. - * @throws IllegalArgumentException if mime type is not valid. - */ - @NonNull - public Builder addUnsupportedVideoMimeType( - @NonNull String codecMime) { - if (!isValidVideoCodecMimeType(codecMime)) { - throw new IllegalArgumentException("Invalid codec mime type: " + codecMime); - } - mUnsupportedVideoMimeTypes.add(codecMime); - return this; - } - - private List<String> getUnsupportedVideoMimeTypes() { - return new ArrayList<>(mUnsupportedVideoMimeTypes); - } - - /** - * Adds a supported hdr type. - * - * @param hdrType Supported hdr type. Must be one of the String defined in - * {@link MediaFeature.HdrType}. - * @throws IllegalArgumentException if hdrType is not valid. - */ - @NonNull - public Builder addSupportedHdrType( - @NonNull @MediaFeature.MediaHdrType String hdrType) { - if (!isValidVideoCodecHdrType(hdrType)) { - throw new IllegalArgumentException("Invalid hdr type: " + hdrType); - } - mSupportedHdrTypes.add(hdrType); - return this; - } - - private List<String> getSupportedHdrTypes() { - return new ArrayList<>(mSupportedHdrTypes); - } - - private boolean isValidVideoCodecHdrType(@NonNull String hdrType) { - if (!hdrType.equals(MediaFeature.HdrType.DOLBY_VISION) - && !hdrType.equals(MediaFeature.HdrType.HDR10) - && !hdrType.equals(MediaFeature.HdrType.HDR10_PLUS) - && !hdrType.equals(MediaFeature.HdrType.HLG)) { - return false; - } - return true; - } - - /** - * Adds an unsupported hdr type. - * - * @param hdrType Unsupported hdr type. Must be one of the String defined in - * {@link MediaFeature.HdrType}. - * @throws IllegalArgumentException if hdrType is not valid. - */ - @NonNull - public Builder addUnsupportedHdrType( - @NonNull @MediaFeature.MediaHdrType String hdrType) { - if (!isValidVideoCodecHdrType(hdrType)) { - throw new IllegalArgumentException("Invalid hdr type: " + hdrType); - } - mUnsupportedHdrTypes.add(hdrType); - return this; - } - - private List<String> getUnsupportedHdrTypes() { - return new ArrayList<>(mUnsupportedHdrTypes); - } - - /** - * Sets whether slow-motion video is supported. - * If an application indicates support for slow-motion, it is application's responsibility - * to parse the slow-motion videos using their own parser or using support library. - * @see android.media.MediaFormat#KEY_SLOW_MOTION_MARKERS - * @hide - */ - @NonNull - public Builder setSlowMotionSupported(boolean slowMotionSupported) { - mIsSlowMotionSupported = slowMotionSupported; - return this; - } - } -} diff --git a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java deleted file mode 100644 index 915f3f6a3fb0..000000000000 --- a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 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 android.media; - -import android.os.Binder; -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * This is a copied version of BaseParceledListSlice in framework with hidden API usages - * removed. - * - * Transfer a large list of Parcelable objects across an IPC. Splits into - * multiple transactions if needed. - * - * Caveat: for efficiency and security, all elements must be the same concrete type. - * In order to avoid writing the class name of each object, we must ensure that - * each object is the same type, or else unparceling then reparceling the data may yield - * a different result if the class name encoded in the Parcelable is a Base type. - * See b/17671747. - * - * @hide - */ -abstract class BaseMediaParceledListSlice<T> implements Parcelable { - private static String TAG = "BaseMediaParceledListSlice"; - private static final boolean DEBUG = false; - - /* - * TODO get this number from somewhere else. For now set it to a quarter of - * the 1MB limit. - */ - // private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); - private static final int MAX_IPC_SIZE = 64 * 1024; - - private final List<T> mList; - - private int mInlineCountLimit = Integer.MAX_VALUE; - - public BaseMediaParceledListSlice(List<T> list) { - mList = list; - } - - @SuppressWarnings("unchecked") - BaseMediaParceledListSlice(Parcel p, ClassLoader loader) { - final int N = p.readInt(); - mList = new ArrayList<T>(N); - if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); - if (N <= 0) { - return; - } - - Parcelable.Creator<?> creator = readParcelableCreator(p, loader); - Class<?> listElementClass = null; - - int i = 0; - while (i < N) { - if (p.readInt() == 0) { - break; - } - - final T parcelable = readCreator(creator, p, loader); - if (listElementClass == null) { - listElementClass = parcelable.getClass(); - } else { - verifySameType(listElementClass, parcelable.getClass()); - } - - mList.add(parcelable); - - if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); - i++; - } - if (i >= N) { - return; - } - final IBinder retriever = p.readStrongBinder(); - while (i < N) { - if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInt(i); - try { - retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); - } catch (RemoteException e) { - Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e); - return; - } - while (i < N && reply.readInt() != 0) { - final T parcelable = readCreator(creator, reply, loader); - verifySameType(listElementClass, parcelable.getClass()); - - mList.add(parcelable); - - if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1)); - i++; - } - reply.recycle(); - data.recycle(); - } - } - - private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) { - if (creator instanceof Parcelable.ClassLoaderCreator<?>) { - Parcelable.ClassLoaderCreator<?> classLoaderCreator = - (Parcelable.ClassLoaderCreator<?>) creator; - return (T) classLoaderCreator.createFromParcel(p, loader); - } - return (T) creator.createFromParcel(p); - } - - private static void verifySameType(final Class<?> expected, final Class<?> actual) { - if (!actual.equals(expected)) { - throw new IllegalArgumentException("Can't unparcel type " - + (actual == null ? null : actual.getName()) + " in list of type " - + (expected == null ? null : expected.getName())); - } - } - - public List<T> getList() { - return mList; - } - - /** - * Set a limit on the maximum number of entries in the array that will be included - * inline in the initial parcelling of this object. - */ - public void setInlineCountLimit(int maxCount) { - mInlineCountLimit = maxCount; - } - - /** - * Write this to another Parcel. Note that this discards the internal Parcel - * and should not be used anymore. This is so we can pass this to a Binder - * where we won't have a chance to call recycle on this. - */ - @Override - public void writeToParcel(Parcel dest, int flags) { - final int N = mList.size(); - final int callFlags = flags; - dest.writeInt(N); - if (DEBUG) Log.d(TAG, "Writing " + N + " items"); - if (N > 0) { - final Class<?> listElementClass = mList.get(0).getClass(); - writeParcelableCreator(mList.get(0), dest); - int i = 0; - while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { - dest.writeInt(1); - - final T parcelable = mList.get(i); - verifySameType(listElementClass, parcelable.getClass()); - writeElement(parcelable, dest, callFlags); - - if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); - i++; - } - if (i < N) { - dest.writeInt(0); - Binder retriever = new Binder() { - @Override - protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - if (code != FIRST_CALL_TRANSACTION) { - return super.onTransact(code, data, reply, flags); - } - int i = data.readInt(); - if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N); - while (i < N && reply.dataSize() < MAX_IPC_SIZE) { - reply.writeInt(1); - - final T parcelable = mList.get(i); - verifySameType(listElementClass, parcelable.getClass()); - writeElement(parcelable, reply, callFlags); - - if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); - i++; - } - if (i < N) { - if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N); - reply.writeInt(0); - } - return true; - } - }; - if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); - dest.writeStrongBinder(retriever); - } - } - } - - abstract void writeElement(T parcelable, Parcel reply, int callFlags); - - abstract void writeParcelableCreator(T parcelable, Parcel dest); - - abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader); -} diff --git a/apex/media/framework/java/android/media/BufferingParams.java b/apex/media/framework/java/android/media/BufferingParams.java deleted file mode 100644 index 04af02874bbd..000000000000 --- a/apex/media/framework/java/android/media/BufferingParams.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Structure for source buffering management params. - * - * Used by {@link MediaPlayer#getBufferingParams()} and - * {@link MediaPlayer#setBufferingParams(BufferingParams)} - * to control source buffering behavior. - * - * <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering - * (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer} - * is playing back source). {@link BufferingParams} includes corresponding marks for each - * stage of source buffering. The marks are time based (in milliseconds). - * - * <p>{@link MediaPlayer} source component has default marks which can be queried by - * calling {@link MediaPlayer#getBufferingParams()} before any change is made by - * {@link MediaPlayer#setBufferingParams()}. - * <ul> - * <li><strong>initial buffering:</strong> initialMarkMs is used when - * {@link MediaPlayer} is being prepared. When cached data amount exceeds this mark - * {@link MediaPlayer} is prepared. </li> - * <li><strong>rebuffering during playback:</strong> resumePlaybackMarkMs is used when - * {@link MediaPlayer} is playing back content. - * <ul> - * <li> {@link MediaPlayer} has internal mark, namely pausePlaybackMarkMs, to decide when - * to pause playback if cached data amount runs low. This internal mark varies based on - * type of data source. </li> - * <li> When cached data amount exceeds resumePlaybackMarkMs, {@link MediaPlayer} will - * resume playback if it has been paused due to low cached data amount. The internal mark - * pausePlaybackMarkMs shall be less than resumePlaybackMarkMs. </li> - * <li> {@link MediaPlayer} has internal mark, namely pauseRebufferingMarkMs, to decide - * when to pause rebuffering. Apparently, this internal mark shall be no less than - * resumePlaybackMarkMs. </li> - * <li> {@link MediaPlayer} has internal mark, namely resumeRebufferingMarkMs, to decide - * when to resume buffering. This internal mark varies based on type of data source. This - * mark shall be larger than pausePlaybackMarkMs, and less than pauseRebufferingMarkMs. - * </li> - * </ul> </li> - * </ul> - * <p>Users should use {@link Builder} to change {@link BufferingParams}. - * @hide - */ -public final class BufferingParams implements Parcelable { - private static final int BUFFERING_NO_MARK = -1; - - // params - private int mInitialMarkMs = BUFFERING_NO_MARK; - - private int mResumePlaybackMarkMs = BUFFERING_NO_MARK; - - private BufferingParams() { - } - - /** - * Return initial buffering mark in milliseconds. - * @return initial buffering mark in milliseconds - */ - public int getInitialMarkMs() { - return mInitialMarkMs; - } - - /** - * Return the mark in milliseconds for resuming playback. - * @return the mark for resuming playback in milliseconds - */ - public int getResumePlaybackMarkMs() { - return mResumePlaybackMarkMs; - } - - /** - * Builder class for {@link BufferingParams} objects. - * <p> Here is an example where <code>Builder</code> is used to define the - * {@link BufferingParams} to be used by a {@link MediaPlayer} instance: - * - * <pre class="prettyprint"> - * BufferingParams myParams = mediaplayer.getDefaultBufferingParams(); - * myParams = new BufferingParams.Builder(myParams) - * .setInitialMarkMs(10000) - * .setResumePlaybackMarkMs(15000) - * .build(); - * mediaplayer.setBufferingParams(myParams); - * </pre> - */ - public static class Builder { - private int mInitialMarkMs = BUFFERING_NO_MARK; - private int mResumePlaybackMarkMs = BUFFERING_NO_MARK; - - /** - * Constructs a new Builder with the defaults. - * By default, all marks are -1. - */ - public Builder() { - } - - /** - * Constructs a new Builder from a given {@link BufferingParams} instance - * @param bp the {@link BufferingParams} object whose data will be reused - * in the new Builder. - */ - public Builder(BufferingParams bp) { - mInitialMarkMs = bp.mInitialMarkMs; - mResumePlaybackMarkMs = bp.mResumePlaybackMarkMs; - } - - /** - * Combines all of the fields that have been set and return a new - * {@link BufferingParams} object. <code>IllegalStateException</code> will be - * thrown if there is conflict between fields. - * @return a new {@link BufferingParams} object - */ - public BufferingParams build() { - BufferingParams bp = new BufferingParams(); - bp.mInitialMarkMs = mInitialMarkMs; - bp.mResumePlaybackMarkMs = mResumePlaybackMarkMs; - - return bp; - } - - /** - * Sets the time based mark in milliseconds for initial buffering. - * @param markMs time based mark in milliseconds - * @return the same Builder instance. - */ - public Builder setInitialMarkMs(int markMs) { - mInitialMarkMs = markMs; - return this; - } - - /** - * Sets the time based mark in milliseconds for resuming playback. - * @param markMs time based mark in milliseconds for resuming playback - * @return the same Builder instance. - */ - public Builder setResumePlaybackMarkMs(int markMs) { - mResumePlaybackMarkMs = markMs; - return this; - } - } - - private BufferingParams(Parcel in) { - mInitialMarkMs = in.readInt(); - mResumePlaybackMarkMs = in.readInt(); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BufferingParams> CREATOR = - new Parcelable.Creator<BufferingParams>() { - @Override - public BufferingParams createFromParcel(Parcel in) { - return new BufferingParams(in); - } - - @Override - public BufferingParams[] newArray(int size) { - return new BufferingParams[size]; - } - }; - - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mInitialMarkMs); - dest.writeInt(mResumePlaybackMarkMs); - } -} diff --git a/apex/media/framework/java/android/media/Controller2Link.java b/apex/media/framework/java/android/media/Controller2Link.java deleted file mode 100644 index 8eefec73194c..000000000000 --- a/apex/media/framework/java/android/media/Controller2Link.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.os.Binder; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import android.os.ResultReceiver; - -import java.util.Objects; - -/** - * Handles incoming commands from {@link MediaSession2} to {@link MediaController2}. - * @hide - */ -// @SystemApi -public final class Controller2Link implements Parcelable { - private static final String TAG = "Controller2Link"; - private static final boolean DEBUG = MediaController2.DEBUG; - - public static final @android.annotation.NonNull Parcelable.Creator<Controller2Link> CREATOR = - new Parcelable.Creator<Controller2Link>() { - @Override - public Controller2Link createFromParcel(Parcel in) { - return new Controller2Link(in); - } - - @Override - public Controller2Link[] newArray(int size) { - return new Controller2Link[size]; - } - }; - - - private final MediaController2 mController; - private final IMediaController2 mIController; - - public Controller2Link(MediaController2 controller) { - mController = controller; - mIController = new Controller2Stub(); - } - - Controller2Link(Parcel in) { - mController = null; - mIController = IMediaController2.Stub.asInterface(in.readStrongBinder()); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeStrongBinder(mIController.asBinder()); - } - - @Override - public int hashCode() { - return mIController.asBinder().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof Controller2Link)) { - return false; - } - Controller2Link other = (Controller2Link) obj; - return Objects.equals(mIController.asBinder(), other.mIController.asBinder()); - } - - /** Interface method for IMediaController2.notifyConnected */ - public void notifyConnected(int seq, Bundle connectionResult) { - try { - mIController.notifyConnected(seq, connectionResult); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Interface method for IMediaController2.notifyDisonnected */ - public void notifyDisconnected(int seq) { - try { - mIController.notifyDisconnected(seq); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Interface method for IMediaController2.notifyPlaybackActiveChanged */ - public void notifyPlaybackActiveChanged(int seq, boolean playbackActive) { - try { - mIController.notifyPlaybackActiveChanged(seq, playbackActive); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Interface method for IMediaController2.sendSessionCommand */ - public void sendSessionCommand(int seq, Session2Command command, Bundle args, - ResultReceiver resultReceiver) { - try { - mIController.sendSessionCommand(seq, command, args, resultReceiver); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Interface method for IMediaController2.cancelSessionCommand */ - public void cancelSessionCommand(int seq) { - try { - mIController.cancelSessionCommand(seq); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Stub implementation for IMediaController2.notifyConnected */ - public void onConnected(int seq, Bundle connectionResult) { - if (connectionResult == null) { - onDisconnected(seq); - return; - } - mController.onConnected(seq, connectionResult); - } - - /** Stub implementation for IMediaController2.notifyDisonnected */ - public void onDisconnected(int seq) { - mController.onDisconnected(seq); - } - - /** Stub implementation for IMediaController2.notifyPlaybackActiveChanged */ - public void onPlaybackActiveChanged(int seq, boolean playbackActive) { - mController.onPlaybackActiveChanged(seq, playbackActive); - } - - /** Stub implementation for IMediaController2.sendSessionCommand */ - public void onSessionCommand(int seq, Session2Command command, Bundle args, - ResultReceiver resultReceiver) { - mController.onSessionCommand(seq, command, args, resultReceiver); - } - - /** Stub implementation for IMediaController2.cancelSessionCommand */ - public void onCancelCommand(int seq) { - mController.onCancelCommand(seq); - } - - private class Controller2Stub extends IMediaController2.Stub { - @Override - public void notifyConnected(int seq, Bundle connectionResult) { - final long token = Binder.clearCallingIdentity(); - try { - Controller2Link.this.onConnected(seq, connectionResult); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void notifyDisconnected(int seq) { - final long token = Binder.clearCallingIdentity(); - try { - Controller2Link.this.onDisconnected(seq); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void notifyPlaybackActiveChanged(int seq, boolean playbackActive) { - final long token = Binder.clearCallingIdentity(); - try { - Controller2Link.this.onPlaybackActiveChanged(seq, playbackActive); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void sendSessionCommand(int seq, Session2Command command, Bundle args, - ResultReceiver resultReceiver) { - final long token = Binder.clearCallingIdentity(); - try { - Controller2Link.this.onSessionCommand(seq, command, args, resultReceiver); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void cancelSessionCommand(int seq) { - final long token = Binder.clearCallingIdentity(); - try { - Controller2Link.this.onCancelCommand(seq); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } -} diff --git a/apex/media/framework/java/android/media/DataSourceCallback.java b/apex/media/framework/java/android/media/DataSourceCallback.java deleted file mode 100644 index c297ecda249c..000000000000 --- a/apex/media/framework/java/android/media/DataSourceCallback.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package android.media; - -import android.annotation.NonNull; - -import java.io.Closeable; -import java.io.IOException; - -/** - * For supplying media data to the framework. Implement this if your app has - * special requirements for the way media data is obtained. - * - * <p class="note">Methods of this interface may be called on multiple different - * threads. There will be a thread synchronization point between each call to ensure that - * modifications to the state of your DataSourceCallback are visible to future calls. This means - * you don't need to do your own synchronization unless you're modifying the - * DataSourceCallback from another thread while it's being used by the framework.</p> - * - * @hide - */ -public abstract class DataSourceCallback implements Closeable { - - public static final int END_OF_STREAM = -1; - - /** - * Called to request data from the given position. - * - * Implementations should should write up to {@code size} bytes into - * {@code buffer}, and return the number of bytes written. - * - * Return {@code 0} if size is zero (thus no bytes are read). - * - * Return {@code -1} to indicate that end of stream is reached. - * - * @param position the position in the data source to read from. - * @param buffer the buffer to read the data into. - * @param offset the offset within buffer to read the data into. - * @param size the number of bytes to read. - * @throws IOException on fatal errors. - * @return the number of bytes read, or {@link #END_OF_STREAM} if end of stream is reached. - */ - public abstract int readAt(long position, @NonNull byte[] buffer, int offset, int size) - throws IOException; - - /** - * Called to get the size of the data source. - * - * @throws IOException on fatal errors - * @return the size of data source in bytes, or -1 if the size is unknown. - */ - public abstract long getSize() throws IOException; -} diff --git a/apex/media/framework/java/android/media/MediaCommunicationManager.java b/apex/media/framework/java/android/media/MediaCommunicationManager.java deleted file mode 100644 index ef5552e70e6b..000000000000 --- a/apex/media/framework/java/android/media/MediaCommunicationManager.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright 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 android.media; - -import static android.Manifest.permission.MEDIA_CONTENT_CONTROL; -import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; - -import android.annotation.CallbackExecutor; -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SystemApi; -import android.annotation.SystemService; -import android.content.Context; -import android.media.session.MediaSession; -import android.media.session.MediaSessionManager; -import android.os.Build; -import android.os.RemoteException; -import android.os.UserHandle; -import android.service.media.MediaBrowserService; -import android.util.Log; -import android.view.KeyEvent; - -import androidx.annotation.RequiresApi; - -import androidx.annotation.RequiresApi; - -import com.android.internal.annotations.GuardedBy; -import com.android.modules.annotation.MinSdk; -import com.android.modules.utils.build.SdkLevel; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Executor; - -/** - * Provides support for interacting with {@link android.media.MediaSession2 MediaSession2s} - * that applications have published to express their ongoing media playback state. - */ -@MinSdk(Build.VERSION_CODES.S) -@RequiresApi(Build.VERSION_CODES.S) -@SystemService(Context.MEDIA_COMMUNICATION_SERVICE) -public class MediaCommunicationManager { - private static final String TAG = "MediaCommunicationManager"; - - /** - * The manager version used from beginning. - */ - private static final int VERSION_1 = 1; - - /** - * Current manager version. - */ - private static final int CURRENT_VERSION = VERSION_1; - - private final Context mContext; - // Do not access directly use getService(). - private IMediaCommunicationService mService; - - private final Object mLock = new Object(); - private final CopyOnWriteArrayList<SessionCallbackRecord> mTokenCallbackRecords = - new CopyOnWriteArrayList<>(); - - @GuardedBy("mLock") - private MediaCommunicationServiceCallbackStub mCallbackStub; - - // TODO: remove this when MCS implements dispatchMediaKeyEvent. - private MediaSessionManager mMediaSessionManager; - - /** - * @hide - */ - public MediaCommunicationManager(@NonNull Context context) { - if (!SdkLevel.isAtLeastS()) { - throw new UnsupportedOperationException("Android version must be S or greater."); - } - mContext = context; - } - - /** - * Gets the version of this {@link MediaCommunicationManager}. - */ - public @IntRange(from = 1) int getVersion() { - return CURRENT_VERSION; - } - - /** - * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is - * created. - * @param token newly created session2 token - * @hide - */ - public void notifySession2Created(@NonNull Session2Token token) { - Objects.requireNonNull(token, "token shouldn't be null"); - if (token.getType() != Session2Token.TYPE_SESSION) { - throw new IllegalArgumentException("token's type should be TYPE_SESSION"); - } - try { - getService().notifySession2Created(token); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - - /** - * Checks whether the remote user is a trusted app. - * <p> - * An app is trusted if the app holds the - * {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission or has an enabled - * notification listener. - * - * @param userInfo The remote user info from either - * {@link MediaSession#getCurrentControllerInfo()} or - * {@link MediaBrowserService#getCurrentBrowserInfo()}. - * @return {@code true} if the remote user is trusted or {@code false} otherwise. - * @hide - */ - public boolean isTrustedForMediaControl(@NonNull MediaSessionManager.RemoteUserInfo userInfo) { - Objects.requireNonNull(userInfo, "userInfo shouldn't be null"); - if (userInfo.getPackageName() == null) { - return false; - } - try { - return getService().isTrusted( - userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid()); - } catch (RemoteException e) { - Log.w(TAG, "Cannot communicate with the service.", e); - } - return false; - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the - * current user. - * <p> - * Although this API can be used without any restriction, each session owners can accept or - * reject your uses of {@link MediaSession2}. - * - * @return A list of {@link Session2Token}. - */ - @NonNull - public List<Session2Token> getSession2Tokens() { - return getSession2Tokens(UserHandle.myUserId()); - } - - /** - * Adds a callback to be notified when the list of active sessions changes. - * <p> - * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be - * held by the calling app. - * </p> - * @hide - */ - @SystemApi(client = MODULE_LIBRARIES) - @RequiresPermission(MEDIA_CONTENT_CONTROL) - public void registerSessionCallback(@CallbackExecutor @NonNull Executor executor, - @NonNull SessionCallback callback) { - Objects.requireNonNull(executor, "executor must not be null"); - Objects.requireNonNull(callback, "callback must not be null"); - - if (!mTokenCallbackRecords.addIfAbsent( - new SessionCallbackRecord(executor, callback))) { - Log.w(TAG, "registerSession2TokenCallback: Ignoring the same callback"); - return; - } - synchronized (mLock) { - if (mCallbackStub == null) { - MediaCommunicationServiceCallbackStub callbackStub = - new MediaCommunicationServiceCallbackStub(); - try { - getService().registerCallback(callbackStub, mContext.getPackageName()); - mCallbackStub = callbackStub; - } catch (RemoteException ex) { - Log.e(TAG, "Failed to register callback.", ex); - } - } - } - } - - /** - * Stops receiving active sessions updates on the specified callback. - * @hide - */ - @SystemApi(client = MODULE_LIBRARIES) - public void unregisterSessionCallback(@NonNull SessionCallback callback) { - if (!mTokenCallbackRecords.remove( - new SessionCallbackRecord(null, callback))) { - Log.w(TAG, "unregisterSession2TokenCallback: Ignoring an unknown callback."); - return; - } - synchronized (mLock) { - if (mCallbackStub != null && mTokenCallbackRecords.isEmpty()) { - try { - getService().unregisterCallback(mCallbackStub); - } catch (RemoteException ex) { - Log.e(TAG, "Failed to unregister callback.", ex); - } - mCallbackStub = null; - } - } - } - - private IMediaCommunicationService getService() { - if (mService == null) { - mService = IMediaCommunicationService.Stub.asInterface( - MediaFrameworkInitializer.getMediaServiceManager() - .getMediaCommunicationServiceRegisterer() - .get()); - } - return mService; - } - - // TODO: remove this when MCS implements dispatchMediaKeyEvent. - private MediaSessionManager getMediaSessionManager() { - if (mMediaSessionManager == null) { - mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); - } - return mMediaSessionManager; - } - - private List<Session2Token> getSession2Tokens(int userId) { - try { - MediaParceledListSlice slice = getService().getSession2Tokens(userId); - return slice == null ? Collections.emptyList() : slice.getList(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get session tokens", e); - } - return Collections.emptyList(); - } - - /** - * Sends a media key event. The receiver will be selected automatically. - * - * @param keyEvent the key event to send - * @param asSystemService if {@code true}, the event sent to the session as if it was come from - * the system service instead of the app process. - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean asSystemService) { - Objects.requireNonNull(keyEvent, "keyEvent shouldn't be null"); - - // When MCS handles this, caller is changed. - // TODO: remove this when MCS implementation is done. - if (!asSystemService) { - getMediaSessionManager().dispatchMediaKeyEvent(keyEvent, false); - return; - } - - try { - getService().dispatchMediaKeyEvent(mContext.getPackageName(), - keyEvent, asSystemService); - } catch (RemoteException e) { - Log.e(TAG, "Failed to send key event.", e); - } - } - - /** - * Callback for listening to changes to the sessions. - * @see #registerSessionCallback(Executor, SessionCallback) - * @hide - */ - @SystemApi(client = MODULE_LIBRARIES) - public interface SessionCallback { - /** - * Called when a new {@link MediaSession2 media session2} is created. - * @param token the newly created token - */ - default void onSession2TokenCreated(@NonNull Session2Token token) {} - - /** - * Called when {@link #getSession2Tokens() session tokens} are changed. - */ - default void onSession2TokensChanged(@NonNull List<Session2Token> tokens) {} - } - - private static final class SessionCallbackRecord { - public final Executor executor; - public final SessionCallback callback; - - SessionCallbackRecord(Executor executor, SessionCallback callback) { - this.executor = executor; - this.callback = callback; - } - - @Override - public int hashCode() { - return Objects.hash(callback); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof SessionCallbackRecord)) { - return false; - } - return Objects.equals(this.callback, ((SessionCallbackRecord) obj).callback); - } - } - - class MediaCommunicationServiceCallbackStub extends IMediaCommunicationServiceCallback.Stub { - @Override - public void onSession2Created(Session2Token token) throws RemoteException { - for (SessionCallbackRecord record : mTokenCallbackRecords) { - record.executor.execute(() -> record.callback.onSession2TokenCreated(token)); - } - } - - @Override - public void onSession2Changed(MediaParceledListSlice tokens) throws RemoteException { - List<Session2Token> tokenList = tokens.getList(); - for (SessionCallbackRecord record : mTokenCallbackRecords) { - record.executor.execute(() -> record.callback.onSession2TokensChanged(tokenList)); - } - } - } -} diff --git a/apex/media/framework/java/android/media/MediaConstants.java b/apex/media/framework/java/android/media/MediaConstants.java deleted file mode 100644 index ce108894b9a5..000000000000 --- a/apex/media/framework/java/android/media/MediaConstants.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -class MediaConstants { - // Bundle key for int - static final String KEY_PID = "android.media.key.PID"; - - // Bundle key for String - static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME"; - - // Bundle key for Parcelable - static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK"; - static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS"; - static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE"; - static final String KEY_TOKEN_EXTRAS = "android.media.key.TOKEN_EXTRAS"; - static final String KEY_CONNECTION_HINTS = "android.media.key.CONNECTION_HINTS"; - - private MediaConstants() { - } -} diff --git a/apex/media/framework/java/android/media/MediaController2.java b/apex/media/framework/java/android/media/MediaController2.java deleted file mode 100644 index 159e8e551d11..000000000000 --- a/apex/media/framework/java/android/media/MediaController2.java +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; -import static android.media.MediaConstants.KEY_CONNECTION_HINTS; -import static android.media.MediaConstants.KEY_PACKAGE_NAME; -import static android.media.MediaConstants.KEY_PID; -import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; -import static android.media.MediaConstants.KEY_TOKEN_EXTRAS; -import static android.media.Session2Command.Result.RESULT_ERROR_UNKNOWN_ERROR; -import static android.media.Session2Command.Result.RESULT_INFO_SKIPPED; -import static android.media.Session2Token.TYPE_SESSION; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Process; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; - -import java.util.concurrent.Executor; - -/** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * - * Allows an app to interact with an active {@link MediaSession2} or a - * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other - * commands can be sent to the session. - */ -public class MediaController2 implements AutoCloseable { - static final String TAG = "MediaController2"; - static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final ControllerCallback mCallback; - - private final IBinder.DeathRecipient mDeathRecipient = () -> close(); - private final Context mContext; - private final Session2Token mSessionToken; - private final Executor mCallbackExecutor; - private final Controller2Link mControllerStub; - private final Handler mResultHandler; - private final SessionServiceConnection mServiceConnection; - - private final Object mLock = new Object(); - //@GuardedBy("mLock") - private boolean mClosed; - //@GuardedBy("mLock") - private int mNextSeqNumber; - //@GuardedBy("mLock") - private Session2Link mSessionBinder; - //@GuardedBy("mLock") - private Session2CommandGroup mAllowedCommands; - //@GuardedBy("mLock") - private Session2Token mConnectedToken; - //@GuardedBy("mLock") - private ArrayMap<ResultReceiver, Integer> mPendingCommands; - //@GuardedBy("mLock") - private ArraySet<Integer> mRequestedCommandSeqNumbers; - //@GuardedBy("mLock") - private boolean mPlaybackActive; - - /** - * Create a {@link MediaController2} from the {@link Session2Token}. - * This connects to the session and may wake up the service if it's not available. - * - * @param context context - * @param token token to connect to - * @param connectionHints a session-specific argument to send to the session when connecting. - * The contents of this bundle may affect the connection result. - * @param executor executor to run callbacks on. - * @param callback controller callback to receive changes in. - */ - MediaController2(@NonNull Context context, @NonNull Session2Token token, - @NonNull Bundle connectionHints, @NonNull Executor executor, - @NonNull ControllerCallback callback) { - if (context == null) { - throw new IllegalArgumentException("context shouldn't be null"); - } - if (token == null) { - throw new IllegalArgumentException("token shouldn't be null"); - } - mContext = context; - mSessionToken = token; - mCallbackExecutor = (executor == null) ? context.getMainExecutor() : executor; - mCallback = (callback == null) ? new ControllerCallback() {} : callback; - mControllerStub = new Controller2Link(this); - // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. - mResultHandler = new Handler(context.getMainLooper()); - - mNextSeqNumber = 0; - mPendingCommands = new ArrayMap<>(); - mRequestedCommandSeqNumbers = new ArraySet<>(); - - boolean connectRequested; - if (token.getType() == TYPE_SESSION) { - mServiceConnection = null; - connectRequested = requestConnectToSession(connectionHints); - } else { - mServiceConnection = new SessionServiceConnection(connectionHints); - connectRequested = requestConnectToService(); - } - if (!connectRequested) { - close(); - } - } - - @Override - public void close() { - synchronized (mLock) { - if (mClosed) { - // Already closed. Ignore rest of clean up code. - // Note: unbindService() throws IllegalArgumentException when it's called twice. - return; - } - if (DEBUG) { - Log.d(TAG, "closing " + this); - } - mClosed = true; - if (mServiceConnection != null) { - // Note: This should be called even when the bindService() has returned false. - mContext.unbindService(mServiceConnection); - } - if (mSessionBinder != null) { - try { - mSessionBinder.disconnect(mControllerStub, getNextSeqNumber()); - mSessionBinder.unlinkToDeath(mDeathRecipient, 0); - } catch (RuntimeException e) { - // No-op - } - } - mConnectedToken = null; - mPendingCommands.clear(); - mRequestedCommandSeqNumbers.clear(); - mCallbackExecutor.execute(() -> { - mCallback.onDisconnected(MediaController2.this); - }); - mSessionBinder = null; - } - } - - /** - * Returns {@link Session2Token} of the connected session. - * If it is not connected yet, it returns {@code null}. - * <p> - * This may differ with the {@link Session2Token} from the constructor. For example, if the - * controller is created with the token for {@link MediaSession2Service}, this would return - * token for the {@link MediaSession2} in the service. - * - * @return Session2Token of the connected session, or {@code null} if not connected - */ - @Nullable - public Session2Token getConnectedToken() { - synchronized (mLock) { - return mConnectedToken; - } - } - - /** - * Returns whether the session's playback is active. - * - * @return {@code true} if playback active. {@code false} otherwise. - * @see ControllerCallback#onPlaybackActiveChanged(MediaController2, boolean) - */ - public boolean isPlaybackActive() { - synchronized (mLock) { - return mPlaybackActive; - } - } - - /** - * Sends a session command to the session - * <p> - * @param command the session command - * @param args optional arguments - * @return a token which will be sent together in {@link ControllerCallback#onCommandResult} - * when its result is received. - */ - @NonNull - public Object sendSessionCommand(@NonNull Session2Command command, @Nullable Bundle args) { - if (command == null) { - throw new IllegalArgumentException("command shouldn't be null"); - } - - ResultReceiver resultReceiver = new ResultReceiver(mResultHandler) { - protected void onReceiveResult(int resultCode, Bundle resultData) { - synchronized (mLock) { - mPendingCommands.remove(this); - } - mCallbackExecutor.execute(() -> { - mCallback.onCommandResult(MediaController2.this, this, - command, new Session2Command.Result(resultCode, resultData)); - }); - } - }; - - synchronized (mLock) { - if (mSessionBinder != null) { - int seq = getNextSeqNumber(); - mPendingCommands.put(resultReceiver, seq); - try { - mSessionBinder.sendSessionCommand(mControllerStub, seq, command, args, - resultReceiver); - } catch (RuntimeException e) { - mPendingCommands.remove(resultReceiver); - resultReceiver.send(RESULT_ERROR_UNKNOWN_ERROR, null); - } - } - } - return resultReceiver; - } - - /** - * Cancels the session command previously sent. - * - * @param token the token which is returned from {@link #sendSessionCommand}. - */ - public void cancelSessionCommand(@NonNull Object token) { - if (token == null) { - throw new IllegalArgumentException("token shouldn't be null"); - } - synchronized (mLock) { - if (mSessionBinder == null) return; - Integer seq = mPendingCommands.remove(token); - if (seq != null) { - mSessionBinder.cancelSessionCommand(mControllerStub, seq); - } - } - } - - // Called by Controller2Link.onConnected - void onConnected(int seq, Bundle connectionResult) { - Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); - Session2CommandGroup allowedCommands = - connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); - boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE); - - Bundle tokenExtras = connectionResult.getBundle(KEY_TOKEN_EXTRAS); - if (tokenExtras == null) { - Log.w(TAG, "extras shouldn't be null."); - tokenExtras = Bundle.EMPTY; - } else if (MediaSession2.hasCustomParcelable(tokenExtras)) { - Log.w(TAG, "extras contain custom parcelable. Ignoring."); - tokenExtras = Bundle.EMPTY; - } - - if (DEBUG) { - Log.d(TAG, "notifyConnected sessionBinder=" + sessionBinder - + ", allowedCommands=" + allowedCommands); - } - if (sessionBinder == null || allowedCommands == null) { - // Connection rejected. - close(); - return; - } - synchronized (mLock) { - mSessionBinder = sessionBinder; - mAllowedCommands = allowedCommands; - mPlaybackActive = playbackActive; - - // Implementation for the local binder is no-op, - // so can be used without worrying about deadlock. - sessionBinder.linkToDeath(mDeathRecipient, 0); - mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION, - mSessionToken.getPackageName(), sessionBinder, tokenExtras); - } - mCallbackExecutor.execute(() -> { - mCallback.onConnected(MediaController2.this, allowedCommands); - }); - } - - // Called by Controller2Link.onDisconnected - void onDisconnected(int seq) { - // close() will call mCallback.onDisconnected - close(); - } - - // Called by Controller2Link.onPlaybackActiveChanged - void onPlaybackActiveChanged(int seq, boolean playbackActive) { - synchronized (mLock) { - mPlaybackActive = playbackActive; - } - mCallbackExecutor.execute(() -> { - mCallback.onPlaybackActiveChanged(MediaController2.this, playbackActive); - }); - } - - // Called by Controller2Link.onSessionCommand - void onSessionCommand(int seq, Session2Command command, Bundle args, - @Nullable ResultReceiver resultReceiver) { - synchronized (mLock) { - mRequestedCommandSeqNumbers.add(seq); - } - mCallbackExecutor.execute(() -> { - boolean isCanceled; - synchronized (mLock) { - isCanceled = !mRequestedCommandSeqNumbers.remove(seq); - } - if (isCanceled) { - if (resultReceiver != null) { - resultReceiver.send(RESULT_INFO_SKIPPED, null); - } - return; - } - Session2Command.Result result = mCallback.onSessionCommand( - MediaController2.this, command, args); - if (resultReceiver != null) { - if (result == null) { - resultReceiver.send(RESULT_INFO_SKIPPED, null); - } else { - resultReceiver.send(result.getResultCode(), result.getResultData()); - } - } - }); - } - - // Called by Controller2Link.onSessionCommand - void onCancelCommand(int seq) { - synchronized (mLock) { - mRequestedCommandSeqNumbers.remove(seq); - } - } - - private int getNextSeqNumber() { - synchronized (mLock) { - return mNextSeqNumber++; - } - } - - private Bundle createConnectionRequest(@NonNull Bundle connectionHints) { - Bundle connectionRequest = new Bundle(); - connectionRequest.putString(KEY_PACKAGE_NAME, mContext.getPackageName()); - connectionRequest.putInt(KEY_PID, Process.myPid()); - connectionRequest.putBundle(KEY_CONNECTION_HINTS, connectionHints); - return connectionRequest; - } - - private boolean requestConnectToSession(@NonNull Bundle connectionHints) { - Session2Link sessionBinder = mSessionToken.getSessionLink(); - Bundle connectionRequest = createConnectionRequest(connectionHints); - try { - sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest); - } catch (RuntimeException e) { - Log.w(TAG, "Failed to call connection request", e); - return false; - } - return true; - } - - private boolean requestConnectToService() { - // Service. Needs to get fresh binder whenever connection is needed. - final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE); - intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName()); - - // Use bindService() instead of startForegroundService() to start session service for three - // reasons. - // 1. Prevent session service owner's stopSelf() from destroying service. - // With the startForegroundService(), service's call of stopSelf() will trigger immediate - // onDestroy() calls on the main thread even when onConnect() is running in another - // thread. - // 2. Minimize APIs for developers to take care about. - // With bindService(), developers only need to take care about Service.onBind() - // but Service.onStartCommand() should be also taken care about with the - // startForegroundService(). - // 3. Future support for UI-less playback - // If a service wants to keep running, it should be either foreground service or - // bound service. But there had been request for the feature for system apps - // and using bindService() will be better fit with it. - synchronized (mLock) { - boolean result = mContext.bindService( - intent, mServiceConnection, Context.BIND_AUTO_CREATE); - if (!result) { - Log.w(TAG, "bind to " + mSessionToken + " failed"); - return false; - } else if (DEBUG) { - Log.d(TAG, "bind to " + mSessionToken + " succeeded"); - } - } - return true; - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Builder for {@link MediaController2}. - * <p> - * Any incoming event from the {@link MediaSession2} will be handled on the callback - * executor. If it's not set, {@link Context#getMainExecutor()} will be used by default. - */ - public static final class Builder { - private Context mContext; - private Session2Token mToken; - private Bundle mConnectionHints; - private Executor mCallbackExecutor; - private ControllerCallback mCallback; - - /** - * Creates a builder for {@link MediaController2}. - * - * @param context context - * @param token token of the session to connect to - */ - public Builder(@NonNull Context context, @NonNull Session2Token token) { - if (context == null) { - throw new IllegalArgumentException("context shouldn't be null"); - } - if (token == null) { - throw new IllegalArgumentException("token shouldn't be null"); - } - mContext = context; - mToken = token; - } - - /** - * Set the connection hints for the controller. - * <p> - * {@code connectionHints} is a session-specific argument to send to the session when - * connecting. The contents of this bundle may affect the connection result. - * <p> - * An {@link IllegalArgumentException} will be thrown if the bundle contains any - * non-framework Parcelable objects. - * - * @param connectionHints a bundle which contains the connection hints - * @return The Builder to allow chaining - */ - @NonNull - public Builder setConnectionHints(@NonNull Bundle connectionHints) { - if (connectionHints == null) { - throw new IllegalArgumentException("connectionHints shouldn't be null"); - } - if (MediaSession2.hasCustomParcelable(connectionHints)) { - throw new IllegalArgumentException("connectionHints shouldn't contain any custom " - + "parcelables"); - } - mConnectionHints = new Bundle(connectionHints); - return this; - } - - /** - * Set callback for the controller and its executor. - * - * @param executor callback executor - * @param callback session callback. - * @return The Builder to allow chaining - */ - @NonNull - public Builder setControllerCallback(@NonNull Executor executor, - @NonNull ControllerCallback callback) { - if (executor == null) { - throw new IllegalArgumentException("executor shouldn't be null"); - } - if (callback == null) { - throw new IllegalArgumentException("callback shouldn't be null"); - } - mCallbackExecutor = executor; - mCallback = callback; - return this; - } - - /** - * Build {@link MediaController2}. - * - * @return a new controller - */ - @NonNull - public MediaController2 build() { - if (mCallbackExecutor == null) { - mCallbackExecutor = mContext.getMainExecutor(); - } - if (mCallback == null) { - mCallback = new ControllerCallback() {}; - } - if (mConnectionHints == null) { - mConnectionHints = Bundle.EMPTY; - } - return new MediaController2( - mContext, mToken, mConnectionHints, mCallbackExecutor, mCallback); - } - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Interface for listening to change in activeness of the {@link MediaSession2}. - */ - public abstract static class ControllerCallback { - /** - * Called when the controller is successfully connected to the session. The controller - * becomes available afterwards. - * - * @param controller the controller for this event - * @param allowedCommands commands that's allowed by the session. - */ - public void onConnected(@NonNull MediaController2 controller, - @NonNull Session2CommandGroup allowedCommands) {} - - /** - * Called when the session refuses the controller or the controller is disconnected from - * the session. The controller becomes unavailable afterwards and the callback wouldn't - * be called. - * <p> - * It will be also called after the {@link #close()}, so you can put clean up code here. - * You don't need to call {@link #close()} after this. - * - * @param controller the controller for this event - */ - public void onDisconnected(@NonNull MediaController2 controller) {} - - /** - * Called when the session's playback activeness is changed. - * - * @param controller the controller for this event - * @param playbackActive {@code true} if the session's playback is active. - * {@code false} otherwise. - * @see MediaController2#isPlaybackActive() - */ - public void onPlaybackActiveChanged(@NonNull MediaController2 controller, - boolean playbackActive) {} - - /** - * Called when the connected session sent a session command. - * - * @param controller the controller for this event - * @param command the session command - * @param args optional arguments - * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED - * will be sent to the session. - */ - @Nullable - public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller, - @NonNull Session2Command command, @Nullable Bundle args) { - return null; - } - - /** - * Called when the command sent to the connected session is finished. - * - * @param controller the controller for this event - * @param token the token got from {@link MediaController2#sendSessionCommand} - * @param command the session command - * @param result the result of the session command - */ - public void onCommandResult(@NonNull MediaController2 controller, @NonNull Object token, - @NonNull Session2Command command, @NonNull Session2Command.Result result) {} - } - - // This will be called on the main thread. - private class SessionServiceConnection implements ServiceConnection { - private final Bundle mConnectionHints; - - SessionServiceConnection(@Nullable Bundle connectionHints) { - mConnectionHints = connectionHints; - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - // Note that it's always main-thread. - boolean connectRequested = false; - try { - if (DEBUG) { - Log.d(TAG, "onServiceConnected " + name + " " + this); - } - if (!mSessionToken.getPackageName().equals(name.getPackageName())) { - Log.wtf(TAG, "Expected connection to " + mSessionToken.getPackageName() - + " but is connected to " + name); - return; - } - IMediaSession2Service iService = IMediaSession2Service.Stub.asInterface(service); - if (iService == null) { - Log.wtf(TAG, "Service interface is missing."); - return; - } - Bundle connectionRequest = createConnectionRequest(mConnectionHints); - iService.connect(mControllerStub, getNextSeqNumber(), connectionRequest); - connectRequested = true; - } catch (RemoteException e) { - Log.w(TAG, "Service " + name + " has died prematurely", e); - } finally { - if (!connectRequested) { - close(); - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - // Temporal lose of the binding because of the service crash. System will automatically - // rebind, so just no-op. - if (DEBUG) { - Log.w(TAG, "Session service " + name + " is disconnected."); - } - close(); - } - - @Override - public void onBindingDied(ComponentName name) { - // Permanent lose of the binding because of the service package update or removed. - // This SessionServiceRecord will be removed accordingly, but forget session binder here - // for sure. - close(); - } - } -} diff --git a/apex/media/framework/java/android/media/MediaFeature.java b/apex/media/framework/java/android/media/MediaFeature.java deleted file mode 100644 index 8d1b159cd70b..000000000000 --- a/apex/media/framework/java/android/media/MediaFeature.java +++ /dev/null @@ -1,67 +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 android.media; - -import android.annotation.StringDef; -import android.os.Build; - -import com.android.modules.annotation.MinSdk; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * MediaFeature defines various media features, e.g. hdr type. - */ -@MinSdk(Build.VERSION_CODES.S) -public final class MediaFeature { - /** - * Defines tye type of HDR(high dynamic range) video. - */ - public static final class HdrType { - private HdrType() { - } - - /** - * HDR type for dolby-vision. - */ - public static final String DOLBY_VISION = "android.media.feature.hdr.dolby_vision"; - /** - * HDR type for hdr10. - */ - public static final String HDR10 = "android.media.feature.hdr.hdr10"; - /** - * HDR type for hdr10+. - */ - public static final String HDR10_PLUS = "android.media.feature.hdr.hdr10_plus"; - /** - * HDR type for hlg. - */ - public static final String HLG = "android.media.feature.hdr.hlg"; - } - - /** @hide */ - @StringDef({ - HdrType.DOLBY_VISION, - HdrType.HDR10, - HdrType.HDR10_PLUS, - HdrType.HLG, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface MediaHdrType { - } -} diff --git a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java b/apex/media/framework/java/android/media/MediaFrameworkInitializer.java deleted file mode 100644 index d7ad97d55daf..000000000000 --- a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 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 android.media; - -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.annotation.SystemApi.Client; -import android.app.SystemServiceRegistry; -import android.content.Context; -import android.os.Build; - -import androidx.annotation.RequiresApi; - -import com.android.modules.annotation.MinSdk; -import com.android.modules.utils.build.SdkLevel; - -/** - * Class for performing registration for all media services on com.android.media apex. - * - * @hide - */ -@MinSdk(Build.VERSION_CODES.S) -@RequiresApi(Build.VERSION_CODES.S) -@SystemApi(client = Client.MODULE_LIBRARIES) -public class MediaFrameworkInitializer { - private MediaFrameworkInitializer() { - } - - private static volatile MediaServiceManager sMediaServiceManager; - - /** - * Sets an instance of {@link MediaServiceManager} that allows - * the media mainline module to register/obtain media binder services. This is called - * by the platform during the system initialization. - * - * @param mediaServiceManager instance of {@link MediaServiceManager} that allows - * the media mainline module to register/obtain media binder services. - */ - public static void setMediaServiceManager( - @NonNull MediaServiceManager mediaServiceManager) { - if (sMediaServiceManager != null) { - throw new IllegalStateException("setMediaServiceManager called twice!"); - } - - if (mediaServiceManager == null) { - throw new NullPointerException("mediaServiceManager is null!"); - } - - sMediaServiceManager = mediaServiceManager; - } - - /** @hide */ - public static MediaServiceManager getMediaServiceManager() { - return sMediaServiceManager; - } - - /** - * Called by {@link SystemServiceRegistry}'s static initializer and registers all media - * services to {@link Context}, so that {@link Context#getSystemService} can return them. - * - * @throws IllegalStateException if this is called from anywhere besides - * {@link SystemServiceRegistry} - */ - public static void registerServiceWrappers() { - SystemServiceRegistry.registerContextAwareService( - Context.MEDIA_TRANSCODING_SERVICE, - MediaTranscodingManager.class, - context -> new MediaTranscodingManager(context) - ); - if (SdkLevel.isAtLeastS()) { - SystemServiceRegistry.registerContextAwareService( - Context.MEDIA_COMMUNICATION_SERVICE, - MediaCommunicationManager.class, - context -> new MediaCommunicationManager(context) - ); - } - } -} diff --git a/apex/media/framework/java/android/media/MediaParceledListSlice.java b/apex/media/framework/java/android/media/MediaParceledListSlice.java deleted file mode 100644 index fb36e809e811..000000000000 --- a/apex/media/framework/java/android/media/MediaParceledListSlice.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 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 android.media; - -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.RequiresApi; - -import java.util.Collections; -import java.util.List; - -/** - * This is a copied version of MediaParceledListSlice in framework with hidden API usages removed, - * and also with some lint error fixed. - * - * Transfer a large list of Parcelable objects across an IPC. Splits into - * multiple transactions if needed. - * - * TODO: Remove this from @SystemApi once all the MediaSession related classes are moved - * to apex (or ParceledListSlice moved to apex). This class is temporaily added to system API - * for moving classes step by step. - * - * @param <T> The type of the elements in the list. - * @see BaseMediaParceledListSlice - * @deprecated This is temporary marked as @SystemApi. Should be removed from the API surface. - * @hide - */ -@Deprecated -@RequiresApi(Build.VERSION_CODES.S) -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) -public final class MediaParceledListSlice<T extends Parcelable> - extends BaseMediaParceledListSlice<T> { - public MediaParceledListSlice(@NonNull List<T> list) { - super(list); - } - - private MediaParceledListSlice(Parcel in, ClassLoader loader) { - super(in, loader); - } - - @NonNull - public static <T extends Parcelable> MediaParceledListSlice<T> emptyList() { - return new MediaParceledListSlice<T>(Collections.<T> emptyList()); - } - - @Override - public int describeContents() { - int contents = 0; - final List<T> list = getList(); - for (int i=0; i<list.size(); i++) { - contents |= list.get(i).describeContents(); - } - return contents; - } - - @Override - void writeElement(T parcelable, Parcel dest, int callFlags) { - parcelable.writeToParcel(dest, callFlags); - } - - @Override - void writeParcelableCreator(T parcelable, Parcel dest) { - dest.writeParcelableCreator((Parcelable) parcelable); - } - - @Override - Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) { - return from.readParcelableCreator(loader); - } - - @NonNull - @SuppressWarnings("unchecked") - public static final Parcelable.ClassLoaderCreator<MediaParceledListSlice> CREATOR = - new Parcelable.ClassLoaderCreator<MediaParceledListSlice>() { - public MediaParceledListSlice createFromParcel(Parcel in) { - return new MediaParceledListSlice(in, null); - } - - @Override - public MediaParceledListSlice createFromParcel(Parcel in, ClassLoader loader) { - return new MediaParceledListSlice(in, loader); - } - - @Override - public MediaParceledListSlice[] newArray(int size) { - return new MediaParceledListSlice[size]; - } - }; -} diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java deleted file mode 100644 index b6f85c7eac6f..000000000000 --- a/apex/media/framework/java/android/media/MediaParser.java +++ /dev/null @@ -1,2293 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.media; - -import android.annotation.CheckResult; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.StringDef; -import android.media.MediaCodec.CryptoInfo; -import android.media.metrics.LogSessionId; -import android.os.Build; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; -import android.util.SparseArray; - -import androidx.annotation.RequiresApi; - -import com.android.modules.utils.build.SdkLevel; - -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.ParserException; -import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; -import com.google.android.exoplayer2.extractor.ChunkIndex; -import com.google.android.exoplayer2.extractor.DefaultExtractorInput; -import com.google.android.exoplayer2.extractor.Extractor; -import com.google.android.exoplayer2.extractor.ExtractorInput; -import com.google.android.exoplayer2.extractor.ExtractorOutput; -import com.google.android.exoplayer2.extractor.PositionHolder; -import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; -import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.extractor.amr.AmrExtractor; -import com.google.android.exoplayer2.extractor.flac.FlacExtractor; -import com.google.android.exoplayer2.extractor.flv.FlvExtractor; -import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; -import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; -import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; -import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; -import com.google.android.exoplayer2.extractor.ogg.OggExtractor; -import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; -import com.google.android.exoplayer2.extractor.ts.Ac4Extractor; -import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; -import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; -import com.google.android.exoplayer2.extractor.ts.PsExtractor; -import com.google.android.exoplayer2.extractor.ts.TsExtractor; -import com.google.android.exoplayer2.extractor.wav.WavExtractor; -import com.google.android.exoplayer2.upstream.DataReader; -import com.google.android.exoplayer2.util.ParsableByteArray; -import com.google.android.exoplayer2.util.TimestampAdjuster; -import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.ColorInfo; - -import java.io.EOFException; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Function; - -/** - * Parses media container formats and extracts contained media samples and metadata. - * - * <p>This class provides access to a battery of low-level media container parsers. Each instance of - * this class is associated to a specific media parser implementation which is suitable for - * extraction from a specific media container format. The media parser implementation assignment - * depends on the factory method (see {@link #create} and {@link #createByName}) used to create the - * instance. - * - * <p>Users must implement the following to use this class. - * - * <ul> - * <li>{@link InputReader}: Provides the media container's bytes to parse. - * <li>{@link OutputConsumer}: Provides a sink for all extracted data and metadata. - * </ul> - * - * <p>The following code snippet includes a usage example: - * - * <pre> - * MyOutputConsumer myOutputConsumer = new MyOutputConsumer(); - * MyInputReader myInputReader = new MyInputReader("www.example.com"); - * MediaParser mediaParser = MediaParser.create(myOutputConsumer); - * - * while (mediaParser.advance(myInputReader)) {} - * - * mediaParser.release(); - * mediaParser = null; - * </pre> - * - * <p>The following code snippet provides a rudimentary {@link OutputConsumer} sample implementation - * which extracts and publishes all video samples: - * - * <pre> - * class VideoOutputConsumer implements MediaParser.OutputConsumer { - * - * private byte[] sampleDataBuffer = new byte[4096]; - * private byte[] discardedDataBuffer = new byte[4096]; - * private int videoTrackIndex = -1; - * private int bytesWrittenCount = 0; - * - * @Override - * public void onSeekMapFound(int i, @NonNull MediaFormat mediaFormat) { - * // Do nothing. - * } - * - * @Override - * public void onTrackDataFound(int i, @NonNull TrackData trackData) { - * MediaFormat mediaFormat = trackData.mediaFormat; - * if (videoTrackIndex == -1 && - * mediaFormat - * .getString(MediaFormat.KEY_MIME, /* defaultValue= */ "") - * .startsWith("video/")) { - * videoTrackIndex = i; - * } - * } - * - * @Override - * public void onSampleDataFound(int trackIndex, @NonNull InputReader inputReader) - * throws IOException { - * int numberOfBytesToRead = (int) inputReader.getLength(); - * if (videoTrackIndex != trackIndex) { - * // Discard contents. - * inputReader.read( - * discardedDataBuffer, - * /* offset= */ 0, - * Math.min(discardDataBuffer.length, numberOfBytesToRead)); - * } else { - * ensureSpaceInBuffer(numberOfBytesToRead); - * int bytesRead = inputReader.read( - * sampleDataBuffer, bytesWrittenCount, numberOfBytesToRead); - * bytesWrittenCount += bytesRead; - * } - * } - * - * @Override - * public void onSampleCompleted( - * int trackIndex, - * long timeMicros, - * int flags, - * int size, - * int offset, - * @Nullable CryptoInfo cryptoData) { - * if (videoTrackIndex != trackIndex) { - * return; // It's not the video track. Ignore. - * } - * byte[] sampleData = new byte[size]; - * int sampleStartOffset = bytesWrittenCount - size - offset; - * System.arraycopy( - * sampleDataBuffer, - * sampleStartOffset, - * sampleData, - * /* destPos= */ 0, - * size); - * // Place trailing bytes at the start of the buffer. - * System.arraycopy( - * sampleDataBuffer, - * bytesWrittenCount - offset, - * sampleDataBuffer, - * /* destPos= */ 0, - * /* size= */ offset); - * bytesWrittenCount = bytesWrittenCount - offset; - * publishSample(sampleData, timeMicros, flags); - * } - * - * private void ensureSpaceInBuffer(int numberOfBytesToRead) { - * int requiredLength = bytesWrittenCount + numberOfBytesToRead; - * if (requiredLength > sampleDataBuffer.length) { - * sampleDataBuffer = Arrays.copyOf(sampleDataBuffer, requiredLength); - * } - * } - * - * } - * - * </pre> - */ -@RequiresApi(Build.VERSION_CODES.R) -public final class MediaParser { - - /** - * Maps seek positions to {@link SeekPoint SeekPoints} in the stream. - * - * <p>A {@link SeekPoint} is a position in the stream from which a player may successfully start - * playing media samples. - */ - public static final class SeekMap { - - /** Returned by {@link #getDurationMicros()} when the duration is unknown. */ - public static final int UNKNOWN_DURATION = Integer.MIN_VALUE; - - /** - * For each {@link #getSeekPoints} call, returns a single {@link SeekPoint} whose {@link - * SeekPoint#timeMicros} matches the requested timestamp, and whose {@link - * SeekPoint#position} is 0. - * - * @hide - */ - public static final SeekMap DUMMY = new SeekMap(new DummyExoPlayerSeekMap()); - - private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap; - - private SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { - mExoPlayerSeekMap = exoplayerSeekMap; - } - - /** Returns whether seeking is supported. */ - public boolean isSeekable() { - return mExoPlayerSeekMap.isSeekable(); - } - - /** - * Returns the duration of the stream in microseconds or {@link #UNKNOWN_DURATION} if the - * duration is unknown. - */ - public long getDurationMicros() { - long durationUs = mExoPlayerSeekMap.getDurationUs(); - return durationUs != C.TIME_UNSET ? durationUs : UNKNOWN_DURATION; - } - - /** - * Obtains {@link SeekPoint SeekPoints} for the specified seek time in microseconds. - * - * <p>{@code getSeekPoints(timeMicros).first} contains the latest seek point for samples - * with timestamp equal to or smaller than {@code timeMicros}. - * - * <p>{@code getSeekPoints(timeMicros).second} contains the earliest seek point for samples - * with timestamp equal to or greater than {@code timeMicros}. If a seek point exists for - * {@code timeMicros}, the returned pair will contain the same {@link SeekPoint} twice. - * - * @param timeMicros A seek time in microseconds. - * @return The corresponding {@link SeekPoint SeekPoints}. - */ - @NonNull - public Pair<SeekPoint, SeekPoint> getSeekPoints(long timeMicros) { - SeekPoints seekPoints = mExoPlayerSeekMap.getSeekPoints(timeMicros); - return new Pair<>(toSeekPoint(seekPoints.first), toSeekPoint(seekPoints.second)); - } - } - - /** Holds information associated with a track. */ - public static final class TrackData { - - /** Holds {@link MediaFormat} information for the track. */ - @NonNull public final MediaFormat mediaFormat; - - /** - * Holds {@link DrmInitData} necessary to acquire keys associated with the track, or null if - * the track has no encryption data. - */ - @Nullable public final DrmInitData drmInitData; - - private TrackData(MediaFormat mediaFormat, DrmInitData drmInitData) { - this.mediaFormat = mediaFormat; - this.drmInitData = drmInitData; - } - } - - /** Defines a seek point in a media stream. */ - public static final class SeekPoint { - - /** A {@link SeekPoint} whose time and byte offset are both set to 0. */ - @NonNull public static final SeekPoint START = new SeekPoint(0, 0); - - /** The time of the seek point, in microseconds. */ - public final long timeMicros; - - /** The byte offset of the seek point. */ - public final long position; - - /** - * @param timeMicros The time of the seek point, in microseconds. - * @param position The byte offset of the seek point. - */ - private SeekPoint(long timeMicros, long position) { - this.timeMicros = timeMicros; - this.position = position; - } - - @Override - @NonNull - public String toString() { - return "[timeMicros=" + timeMicros + ", position=" + position + "]"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - SeekPoint other = (SeekPoint) obj; - return timeMicros == other.timeMicros && position == other.position; - } - - @Override - public int hashCode() { - int result = (int) timeMicros; - result = 31 * result + (int) position; - return result; - } - } - - /** Provides input data to {@link MediaParser}. */ - public interface InputReader { - - /** - * Reads up to {@code readLength} bytes of data and stores them into {@code buffer}, - * starting at index {@code offset}. - * - * <p>This method blocks until at least one byte is read, the end of input is detected, or - * an exception is thrown. The read position advances to the first unread byte. - * - * @param buffer The buffer into which the read data should be stored. - * @param offset The start offset into {@code buffer} at which data should be written. - * @param readLength The maximum number of bytes to read. - * @return The non-zero number of bytes read, or -1 if no data is available because the end - * of the input has been reached. - * @throws java.io.IOException If an error occurs reading from the source. - */ - int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException; - - /** Returns the current read position (byte offset) in the stream. */ - long getPosition(); - - /** Returns the length of the input in bytes, or -1 if the length is unknown. */ - long getLength(); - } - - /** {@link InputReader} that allows setting the read position. */ - public interface SeekableInputReader extends InputReader { - - /** - * Sets the read position at the given {@code position}. - * - * <p>{@link #advance} will immediately return after calling this method. - * - * @param position The position to seek to, in bytes. - */ - void seekToPosition(long position); - } - - /** Receives extracted media sample data and metadata from {@link MediaParser}. */ - public interface OutputConsumer { - - /** - * Called when a {@link SeekMap} has been extracted from the stream. - * - * <p>This method is called at least once before any samples are {@link #onSampleCompleted - * complete}. May be called multiple times after that in order to add {@link SeekPoint - * SeekPoints}. - * - * @param seekMap The extracted {@link SeekMap}. - */ - void onSeekMapFound(@NonNull SeekMap seekMap); - - /** - * Called when the number of tracks is found. - * - * @param numberOfTracks The number of tracks in the stream. - */ - void onTrackCountFound(int numberOfTracks); - - /** - * Called when new {@link TrackData} is found in the stream. - * - * @param trackIndex The index of the track for which the {@link TrackData} was extracted. - * @param trackData The extracted {@link TrackData}. - */ - void onTrackDataFound(int trackIndex, @NonNull TrackData trackData); - - /** - * Called when sample data is found in the stream. - * - * <p>If the invocation of this method returns before the entire {@code inputReader} {@link - * InputReader#getLength() length} is consumed, the method will be called again for the - * implementer to read the remaining data. Implementers should surface any thrown {@link - * IOException} caused by reading from {@code input}. - * - * @param trackIndex The index of the track to which the sample data corresponds. - * @param inputReader The {@link InputReader} from which to read the data. - * @throws IOException If an exception occurs while reading from {@code inputReader}. - */ - void onSampleDataFound(int trackIndex, @NonNull InputReader inputReader) throws IOException; - - /** - * Called once all the data of a sample has been passed to {@link #onSampleDataFound}. - * - * <p>Includes sample metadata, like presentation timestamp and flags. - * - * @param trackIndex The index of the track to which the sample corresponds. - * @param timeMicros The media timestamp associated with the sample, in microseconds. - * @param flags Flags associated with the sample. See the {@code SAMPLE_FLAG_*} constants. - * @param size The size of the sample data, in bytes. - * @param offset The number of bytes that have been consumed by {@code - * onSampleDataFound(int, MediaParser.InputReader)} for the specified track, since the - * last byte belonging to the sample whose metadata is being passed. - * @param cryptoInfo Encryption data required to decrypt the sample. May be null for - * unencrypted samples. Implementors should treat any output {@link CryptoInfo} - * instances as immutable. MediaParser will not modify any output {@code cryptoInfos} - * and implementors should not modify them either. - */ - void onSampleCompleted( - int trackIndex, - long timeMicros, - @SampleFlags int flags, - int size, - int offset, - @Nullable CryptoInfo cryptoInfo); - } - - /** - * Thrown if all parser implementations provided to {@link #create} failed to sniff the input - * content. - */ - public static final class UnrecognizedInputFormatException extends IOException { - - /** - * Creates a new instance which signals that the parsers with the given names failed to - * parse the input. - */ - @NonNull - @CheckResult - private static UnrecognizedInputFormatException createForExtractors( - @NonNull String... extractorNames) { - StringBuilder builder = new StringBuilder(); - builder.append("None of the available parsers ( "); - builder.append(extractorNames[0]); - for (int i = 1; i < extractorNames.length; i++) { - builder.append(", "); - builder.append(extractorNames[i]); - } - builder.append(") could read the stream."); - return new UnrecognizedInputFormatException(builder.toString()); - } - - private UnrecognizedInputFormatException(String extractorNames) { - super(extractorNames); - } - } - - /** Thrown when an error occurs while parsing a media stream. */ - public static final class ParsingException extends IOException { - - private ParsingException(ParserException cause) { - super(cause); - } - } - - // Sample flags. - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - flag = true, - value = { - SAMPLE_FLAG_KEY_FRAME, - SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA, - SAMPLE_FLAG_LAST_SAMPLE, - SAMPLE_FLAG_ENCRYPTED, - SAMPLE_FLAG_DECODE_ONLY - }) - public @interface SampleFlags {} - /** Indicates that the sample holds a synchronization sample. */ - public static final int SAMPLE_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; - /** - * Indicates that the sample has supplemental data. - * - * <p>Samples will not have this flag set unless the {@code - * "android.media.mediaparser.includeSupplementalData"} parameter is set to {@code true} via - * {@link #setParameter}. - * - * <p>Samples with supplemental data have the following sample data format: - * - * <ul> - * <li>If the {@code "android.media.mediaparser.inBandCryptoInfo"} parameter is set, all - * encryption information. - * <li>(4 bytes) {@code sample_data_size}: The size of the actual sample data, not including - * supplemental data or encryption information. - * <li>({@code sample_data_size} bytes): The media sample data. - * <li>(remaining bytes) The supplemental data. - * </ul> - */ - public static final int SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28; - /** Indicates that the sample is known to contain the last media sample of the stream. */ - public static final int SAMPLE_FLAG_LAST_SAMPLE = 1 << 29; - /** Indicates that the sample is (at least partially) encrypted. */ - public static final int SAMPLE_FLAG_ENCRYPTED = 1 << 30; - /** Indicates that the sample should be decoded but not rendered. */ - public static final int SAMPLE_FLAG_DECODE_ONLY = 1 << 31; - - // Parser implementation names. - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @StringDef( - prefix = {"PARSER_NAME_"}, - value = { - PARSER_NAME_UNKNOWN, - PARSER_NAME_MATROSKA, - PARSER_NAME_FMP4, - PARSER_NAME_MP4, - PARSER_NAME_MP3, - PARSER_NAME_ADTS, - PARSER_NAME_AC3, - PARSER_NAME_TS, - PARSER_NAME_FLV, - PARSER_NAME_OGG, - PARSER_NAME_PS, - PARSER_NAME_WAV, - PARSER_NAME_AMR, - PARSER_NAME_AC4, - PARSER_NAME_FLAC - }) - public @interface ParserName {} - - /** Parser name returned by {@link #getParserName()} when no parser has been selected yet. */ - public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN"; - /** - * Parser for the Matroska container format, as defined in the <a - * href="https://matroska.org/technical/specs/">spec</a>. - */ - public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser"; - /** - * Parser for fragmented files using the MP4 container format, as defined in ISO/IEC 14496-12. - */ - public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser"; - /** - * Parser for non-fragmented files using the MP4 container format, as defined in ISO/IEC - * 14496-12. - */ - public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser"; - /** Parser for the MP3 container format, as defined in ISO/IEC 11172-3. */ - public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser"; - /** Parser for the ADTS container format, as defined in ISO/IEC 13818-7. */ - public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser"; - /** - * Parser for the AC-3 container format, as defined in Digital Audio Compression Standard - * (AC-3). - */ - public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser"; - /** Parser for the TS container format, as defined in ISO/IEC 13818-1. */ - public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser"; - /** - * Parser for the FLV container format, as defined in Adobe Flash Video File Format - * Specification. - */ - public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser"; - /** Parser for the OGG container format, as defined in RFC 3533. */ - public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser"; - /** Parser for the PS container format, as defined in ISO/IEC 11172-1. */ - public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser"; - /** - * Parser for the WAV container format, as defined in Multimedia Programming Interface and Data - * Specifications. - */ - public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser"; - /** Parser for the AMR container format, as defined in RFC 4867. */ - public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser"; - /** - * Parser for the AC-4 container format, as defined by Dolby AC-4: Audio delivery for - * Next-Generation Entertainment Services. - */ - public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser"; - /** - * Parser for the FLAC container format, as defined in the <a - * href="https://xiph.org/flac/">spec</a>. - */ - public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser"; - - // MediaParser parameters. - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @StringDef( - prefix = {"PARAMETER_"}, - value = { - PARAMETER_ADTS_ENABLE_CBR_SEEKING, - PARAMETER_AMR_ENABLE_CBR_SEEKING, - PARAMETER_FLAC_DISABLE_ID3, - PARAMETER_MP4_IGNORE_EDIT_LISTS, - PARAMETER_MP4_IGNORE_TFDT_BOX, - PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES, - PARAMETER_MATROSKA_DISABLE_CUES_SEEKING, - PARAMETER_MP3_DISABLE_ID3, - PARAMETER_MP3_ENABLE_CBR_SEEKING, - PARAMETER_MP3_ENABLE_INDEX_SEEKING, - PARAMETER_TS_MODE, - PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES, - PARAMETER_TS_IGNORE_AAC_STREAM, - PARAMETER_TS_IGNORE_AVC_STREAM, - PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM, - PARAMETER_TS_DETECT_ACCESS_UNITS, - PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS, - PARAMETER_IN_BAND_CRYPTO_INFO, - PARAMETER_INCLUDE_SUPPLEMENTAL_DATA - }) - public @interface ParameterName {} - - /** - * Sets whether constant bitrate seeking should be enabled for ADTS parsing. {@code boolean} - * expected. Default value is {@code false}. - */ - public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING = - "android.media.mediaparser.adts.enableCbrSeeking"; - /** - * Sets whether constant bitrate seeking should be enabled for AMR. {@code boolean} expected. - * Default value is {@code false}. - */ - public static final String PARAMETER_AMR_ENABLE_CBR_SEEKING = - "android.media.mediaparser.amr.enableCbrSeeking"; - /** - * Sets whether the ID3 track should be disabled for FLAC. {@code boolean} expected. Default - * value is {@code false}. - */ - public static final String PARAMETER_FLAC_DISABLE_ID3 = - "android.media.mediaparser.flac.disableId3"; - /** - * Sets whether MP4 parsing should ignore edit lists. {@code boolean} expected. Default value is - * {@code false}. - */ - public static final String PARAMETER_MP4_IGNORE_EDIT_LISTS = - "android.media.mediaparser.mp4.ignoreEditLists"; - /** - * Sets whether MP4 parsing should ignore the tfdt box. {@code boolean} expected. Default value - * is {@code false}. - */ - public static final String PARAMETER_MP4_IGNORE_TFDT_BOX = - "android.media.mediaparser.mp4.ignoreTfdtBox"; - /** - * Sets whether MP4 parsing should treat all video frames as key frames. {@code boolean} - * expected. Default value is {@code false}. - */ - public static final String PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES = - "android.media.mediaparser.mp4.treatVideoFramesAsKeyframes"; - /** - * Sets whether Matroska parsing should avoid seeking to the cues element. {@code boolean} - * expected. Default value is {@code false}. - * - * <p>If this flag is enabled and the cues element occurs after the first cluster, then the - * media is treated as unseekable. - */ - public static final String PARAMETER_MATROSKA_DISABLE_CUES_SEEKING = - "android.media.mediaparser.matroska.disableCuesSeeking"; - /** - * Sets whether the ID3 track should be disabled for MP3. {@code boolean} expected. Default - * value is {@code false}. - */ - public static final String PARAMETER_MP3_DISABLE_ID3 = - "android.media.mediaparser.mp3.disableId3"; - /** - * Sets whether constant bitrate seeking should be enabled for MP3. {@code boolean} expected. - * Default value is {@code false}. - */ - public static final String PARAMETER_MP3_ENABLE_CBR_SEEKING = - "android.media.mediaparser.mp3.enableCbrSeeking"; - /** - * Sets whether MP3 parsing should generate a time-to-byte mapping. {@code boolean} expected. - * Default value is {@code false}. - * - * <p>Enabling this flag may require to scan a significant portion of the file to compute a seek - * point. Therefore, it should only be used if: - * - * <ul> - * <li>the file is small, or - * <li>the bitrate is variable (or the type of bitrate is unknown) and the seeking metadata - * provided in the file is not precise enough (or is not present). - * </ul> - */ - public static final String PARAMETER_MP3_ENABLE_INDEX_SEEKING = - "android.media.mediaparser.mp3.enableIndexSeeking"; - /** - * Sets the operation mode for TS parsing. {@code String} expected. Valid values are {@code - * "single_pmt"}, {@code "multi_pmt"}, and {@code "hls"}. Default value is {@code "single_pmt"}. - * - * <p>The operation modes alter the way TS behaves so that it can handle certain kinds of - * commonly-occurring malformed media. - * - * <ul> - * <li>{@code "single_pmt"}: Only the first found PMT is parsed. Others are ignored, even if - * more PMTs are declared in the PAT. - * <li>{@code "multi_pmt"}: Behave as described in ISO/IEC 13818-1. - * <li>{@code "hls"}: Enable {@code "single_pmt"} mode, and ignore continuity counters. - * </ul> - */ - public static final String PARAMETER_TS_MODE = "android.media.mediaparser.ts.mode"; - /** - * Sets whether TS should treat samples consisting of non-IDR I slices as synchronization - * samples (key-frames). {@code boolean} expected. Default value is {@code false}. - */ - public static final String PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES = - "android.media.mediaparser.ts.allowNonIdrAvcKeyframes"; - /** - * Sets whether TS parsing should ignore AAC elementary streams. {@code boolean} expected. - * Default value is {@code false}. - */ - public static final String PARAMETER_TS_IGNORE_AAC_STREAM = - "android.media.mediaparser.ts.ignoreAacStream"; - /** - * Sets whether TS parsing should ignore AVC elementary streams. {@code boolean} expected. - * Default value is {@code false}. - */ - public static final String PARAMETER_TS_IGNORE_AVC_STREAM = - "android.media.mediaparser.ts.ignoreAvcStream"; - /** - * Sets whether TS parsing should ignore splice information streams. {@code boolean} expected. - * Default value is {@code false}. - */ - public static final String PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM = - "android.media.mediaparser.ts.ignoreSpliceInfoStream"; - /** - * Sets whether TS parsing should split AVC stream into access units based on slice headers. - * {@code boolean} expected. Default value is {@code false}. - * - * <p>This flag should be left disabled if the stream contains access units delimiters in order - * to avoid unnecessary computational costs. - */ - public static final String PARAMETER_TS_DETECT_ACCESS_UNITS = - "android.media.mediaparser.ts.ignoreDetectAccessUnits"; - /** - * Sets whether TS parsing should handle HDMV DTS audio streams. {@code boolean} expected. - * Default value is {@code false}. - * - * <p>Enabling this flag will disable the detection of SCTE subtitles. - */ - public static final String PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS = - "android.media.mediaparser.ts.enableHdmvDtsAudioStreams"; - /** - * Sets whether encryption data should be sent in-band with the sample data, as per {@link - * OutputConsumer#onSampleDataFound}. {@code boolean} expected. Default value is {@code false}. - * - * <p>If this parameter is set, encrypted samples' data will be prefixed with the encryption - * information bytes. The format for in-band encryption information is: - * - * <ul> - * <li>(1 byte) {@code encryption_signal_byte}: Most significant bit signals whether the - * encryption data contains subsample encryption data. The remaining bits contain {@code - * initialization_vector_size}. - * <li>({@code initialization_vector_size} bytes) Initialization vector. - * <li>If subsample encryption data is present, as per {@code encryption_signal_byte}, the - * encryption data also contains: - * <ul> - * <li>(2 bytes) {@code subsample_encryption_data_length}. - * <li>({@code subsample_encryption_data_length * 6} bytes) Subsample encryption data - * (repeated {@code subsample_encryption_data_length} times): - * <ul> - * <li>(2 bytes) Size of a clear section in sample. - * <li>(4 bytes) Size of an encryption section in sample. - * </ul> - * </ul> - * </ul> - * - * @hide - */ - public static final String PARAMETER_IN_BAND_CRYPTO_INFO = - "android.media.mediaparser.inBandCryptoInfo"; - /** - * Sets whether supplemental data should be included as part of the sample data. {@code boolean} - * expected. Default value is {@code false}. See {@link #SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA} for - * information about the sample data format. - * - * @hide - */ - public static final String PARAMETER_INCLUDE_SUPPLEMENTAL_DATA = - "android.media.mediaparser.includeSupplementalData"; - /** - * Sets whether sample timestamps may start from non-zero offsets. {@code boolean} expected. - * Default value is {@code false}. - * - * <p>When set to true, sample timestamps will not be offset to start from zero, and the media - * provided timestamps will be used instead. For example, transport stream sample timestamps - * will not be converted to a zero-based timebase. - * - * @hide - */ - public static final String PARAMETER_IGNORE_TIMESTAMP_OFFSET = - "android.media.mediaparser.ignoreTimestampOffset"; - /** - * Sets whether each track type should be eagerly exposed. {@code boolean} expected. Default - * value is {@code false}. - * - * <p>When set to true, each track type will be eagerly exposed through a call to {@link - * OutputConsumer#onTrackDataFound} containing a single-value {@link MediaFormat}. The key for - * the track type is {@code "track-type-string"}, and the possible values are {@code "video"}, - * {@code "audio"}, {@code "text"}, {@code "metadata"}, and {@code "unknown"}. - * - * @hide - */ - public static final String PARAMETER_EAGERLY_EXPOSE_TRACKTYPE = - "android.media.mediaparser.eagerlyExposeTrackType"; - /** - * Sets whether a dummy {@link SeekMap} should be exposed before starting extraction. {@code - * boolean} expected. Default value is {@code false}. - * - * <p>For each {@link SeekMap#getSeekPoints} call, the dummy {@link SeekMap} returns a single - * {@link SeekPoint} whose {@link SeekPoint#timeMicros} matches the requested timestamp, and - * whose {@link SeekPoint#position} is 0. - * - * @hide - */ - public static final String PARAMETER_EXPOSE_DUMMY_SEEKMAP = - "android.media.mediaparser.exposeDummySeekMap"; - - /** - * Sets whether chunk indices available in the extracted media should be exposed as {@link - * MediaFormat MediaFormats}. {@code boolean} expected. Default value is {@link false}. - * - * <p>When set to true, any information about media segmentation will be exposed as a {@link - * MediaFormat} (with track index 0) containing four {@link ByteBuffer} elements under the - * following keys: - * - * <ul> - * <li>"chunk-index-int-sizes": Contains {@code ints} representing the sizes in bytes of each - * of the media segments. - * <li>"chunk-index-long-offsets": Contains {@code longs} representing the byte offsets of - * each segment in the stream. - * <li>"chunk-index-long-us-durations": Contains {@code longs} representing the media duration - * of each segment, in microseconds. - * <li>"chunk-index-long-us-times": Contains {@code longs} representing the start time of each - * segment, in microseconds. - * </ul> - * - * @hide - */ - public static final String PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT = - "android.media.mediaParser.exposeChunkIndexAsMediaFormat"; - /** - * Sets a list of closed-caption {@link MediaFormat MediaFormats} that should be exposed as part - * of the extracted media. {@code List<MediaFormat>} expected. Default value is an empty list. - * - * <p>Expected keys in the {@link MediaFormat} are: - * - * <ul> - * <p>{@link MediaFormat#KEY_MIME}: Determine the type of captions (for example, - * application/cea-608). Mandatory. - * <p>{@link MediaFormat#KEY_CAPTION_SERVICE_NUMBER}: Determine the channel on which the - * captions are transmitted. Optional. - * </ul> - * - * @hide - */ - public static final String PARAMETER_EXPOSE_CAPTION_FORMATS = - "android.media.mediaParser.exposeCaptionFormats"; - /** - * Sets whether the value associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS} should - * override any in-band caption service declarations. {@code boolean} expected. Default value is - * {@link false}. - * - * <p>When {@code false}, any present in-band caption services information will override the - * values associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS}. - * - * @hide - */ - public static final String PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS = - "android.media.mediaParser.overrideInBandCaptionDeclarations"; - /** - * Sets whether a track for EMSG events should be exposed in case of parsing a container that - * supports them. {@code boolean} expected. Default value is {@link false}. - * - * @hide - */ - public static final String PARAMETER_EXPOSE_EMSG_TRACK = - "android.media.mediaParser.exposeEmsgTrack"; - - // Private constants. - - private static final String TAG = "MediaParser"; - private static final String JNI_LIBRARY_NAME = "mediaparser-jni"; - private static final Map<String, ExtractorFactory> EXTRACTOR_FACTORIES_BY_NAME; - private static final Map<String, Class> EXPECTED_TYPE_BY_PARAMETER_NAME; - private static final String TS_MODE_SINGLE_PMT = "single_pmt"; - private static final String TS_MODE_MULTI_PMT = "multi_pmt"; - private static final String TS_MODE_HLS = "hls"; - private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6; - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - private static final String MEDIAMETRICS_ELEMENT_SEPARATOR = "|"; - private static final int MEDIAMETRICS_MAX_STRING_SIZE = 200; - private static final int MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH; - /** - * Intentional error introduced to reported metrics to prevent identification of the parsed - * media. Note: Increasing this value may cause older hostside CTS tests to fail. - */ - private static final float MEDIAMETRICS_DITHER = .02f; - - @IntDef( - value = { - STATE_READING_SIGNAL_BYTE, - STATE_READING_INIT_VECTOR, - STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE, - STATE_READING_SUBSAMPLE_ENCRYPTION_DATA - }) - private @interface EncryptionDataReadState {} - - private static final int STATE_READING_SIGNAL_BYTE = 0; - private static final int STATE_READING_INIT_VECTOR = 1; - private static final int STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE = 2; - private static final int STATE_READING_SUBSAMPLE_ENCRYPTION_DATA = 3; - - // Instance creation methods. - - /** - * Creates an instance backed by the parser with the given {@code name}. The returned instance - * will attempt parsing without sniffing the content. - * - * @param name The name of the parser that will be associated with the created instance. - * @param outputConsumer The {@link OutputConsumer} to which track data and samples are pushed. - * @return A new instance. - * @throws IllegalArgumentException If an invalid name is provided. - */ - @NonNull - public static MediaParser createByName( - @NonNull @ParserName String name, @NonNull OutputConsumer outputConsumer) { - String[] nameAsArray = new String[] {name}; - assertValidNames(nameAsArray); - return new MediaParser(outputConsumer, /* createdByName= */ true, name); - } - - /** - * Creates an instance whose backing parser will be selected by sniffing the content during the - * first {@link #advance} call. Parser implementations will sniff the content in order of - * appearance in {@code parserNames}. - * - * @param outputConsumer The {@link OutputConsumer} to which extracted data is output. - * @param parserNames The names of the parsers to sniff the content with. If empty, a default - * array of names is used. - * @return A new instance. - */ - @NonNull - public static MediaParser create( - @NonNull OutputConsumer outputConsumer, @NonNull @ParserName String... parserNames) { - assertValidNames(parserNames); - if (parserNames.length == 0) { - parserNames = EXTRACTOR_FACTORIES_BY_NAME.keySet().toArray(new String[0]); - } - return new MediaParser(outputConsumer, /* createdByName= */ false, parserNames); - } - - // Misc static methods. - - /** - * Returns an immutable list with the names of the parsers that are suitable for container - * formats with the given {@link MediaFormat}. - * - * <p>A parser supports a {@link MediaFormat} if the mime type associated with {@link - * MediaFormat#KEY_MIME} corresponds to the supported container format. - * - * @param mediaFormat The {@link MediaFormat} to check support for. - * @return The parser names that support the given {@code mediaFormat}, or the list of all - * parsers available if no container specific format information is provided. - */ - @NonNull - @ParserName - public static List<String> getParserNames(@NonNull MediaFormat mediaFormat) { - String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); - mimeType = mimeType == null ? null : Util.toLowerInvariant(mimeType.trim()); - if (TextUtils.isEmpty(mimeType)) { - // No MIME type provided. Return all. - return Collections.unmodifiableList( - new ArrayList<>(EXTRACTOR_FACTORIES_BY_NAME.keySet())); - } - ArrayList<String> result = new ArrayList<>(); - switch (mimeType) { - case "video/x-matroska": - case "audio/x-matroska": - case "video/x-webm": - case "audio/x-webm": - result.add(PARSER_NAME_MATROSKA); - break; - case "video/mp4": - case "audio/mp4": - case "application/mp4": - result.add(PARSER_NAME_MP4); - result.add(PARSER_NAME_FMP4); - break; - case "audio/mpeg": - result.add(PARSER_NAME_MP3); - break; - case "audio/aac": - result.add(PARSER_NAME_ADTS); - break; - case "audio/ac3": - result.add(PARSER_NAME_AC3); - break; - case "video/mp2t": - case "audio/mp2t": - result.add(PARSER_NAME_TS); - break; - case "video/x-flv": - result.add(PARSER_NAME_FLV); - break; - case "video/ogg": - case "audio/ogg": - case "application/ogg": - result.add(PARSER_NAME_OGG); - break; - case "video/mp2p": - case "video/mp1s": - result.add(PARSER_NAME_PS); - break; - case "audio/vnd.wave": - case "audio/wav": - case "audio/wave": - case "audio/x-wav": - result.add(PARSER_NAME_WAV); - break; - case "audio/amr": - result.add(PARSER_NAME_AMR); - break; - case "audio/ac4": - result.add(PARSER_NAME_AC4); - break; - case "audio/flac": - case "audio/x-flac": - result.add(PARSER_NAME_FLAC); - break; - default: - // No parsers support the given mime type. Do nothing. - break; - } - return Collections.unmodifiableList(result); - } - - // Private fields. - - private final Map<String, Object> mParserParameters; - private final OutputConsumer mOutputConsumer; - private final String[] mParserNamesPool; - private final PositionHolder mPositionHolder; - private final InputReadingDataReader mExoDataReader; - private final DataReaderAdapter mScratchDataReaderAdapter; - private final ParsableByteArrayAdapter mScratchParsableByteArrayAdapter; - @Nullable private final Constructor<DrmInitData.SchemeInitData> mSchemeInitDataConstructor; - private final ArrayList<Format> mMuxedCaptionFormats; - private boolean mInBandCryptoInfo; - private boolean mIncludeSupplementalData; - private boolean mIgnoreTimestampOffset; - private boolean mEagerlyExposeTrackType; - private boolean mExposeDummySeekMap; - private boolean mExposeChunkIndexAsMediaFormat; - private String mParserName; - private Extractor mExtractor; - private ExtractorInput mExtractorInput; - private boolean mPendingExtractorInit; - private long mPendingSeekPosition; - private long mPendingSeekTimeMicros; - private boolean mLoggedSchemeInitDataCreationException; - private boolean mReleased; - - // MediaMetrics fields. - @Nullable private LogSessionId mLogSessionId; - private final boolean mCreatedByName; - private final SparseArray<Format> mTrackFormats; - private String mLastObservedExceptionName; - private long mDurationMillis; - private long mResourceByteCount; - - // Public methods. - - /** - * Sets parser-specific parameters which allow customizing behavior. - * - * <p>Must be called before the first call to {@link #advance}. - * - * @param parameterName The name of the parameter to set. See {@code PARAMETER_*} constants for - * documentation on possible values. - * @param value The value to set for the given {@code parameterName}. See {@code PARAMETER_*} - * constants for documentation on the expected types. - * @return This instance, for convenience. - * @throws IllegalStateException If called after calling {@link #advance} on the same instance. - */ - @NonNull - public MediaParser setParameter( - @NonNull @ParameterName String parameterName, @NonNull Object value) { - if (mExtractor != null) { - throw new IllegalStateException( - "setParameters() must be called before the first advance() call."); - } - Class expectedType = EXPECTED_TYPE_BY_PARAMETER_NAME.get(parameterName); - // Ignore parameter names that are not contained in the map, in case the client is passing - // a parameter that is being added in a future version of this library. - if (expectedType != null && !expectedType.isInstance(value)) { - throw new IllegalArgumentException( - parameterName - + " expects a " - + expectedType.getSimpleName() - + " but a " - + value.getClass().getSimpleName() - + " was passed."); - } - if (PARAMETER_TS_MODE.equals(parameterName) - && !TS_MODE_SINGLE_PMT.equals(value) - && !TS_MODE_HLS.equals(value) - && !TS_MODE_MULTI_PMT.equals(value)) { - throw new IllegalArgumentException(PARAMETER_TS_MODE + " does not accept: " + value); - } - if (PARAMETER_IN_BAND_CRYPTO_INFO.equals(parameterName)) { - mInBandCryptoInfo = (boolean) value; - } - if (PARAMETER_INCLUDE_SUPPLEMENTAL_DATA.equals(parameterName)) { - mIncludeSupplementalData = (boolean) value; - } - if (PARAMETER_IGNORE_TIMESTAMP_OFFSET.equals(parameterName)) { - mIgnoreTimestampOffset = (boolean) value; - } - if (PARAMETER_EAGERLY_EXPOSE_TRACKTYPE.equals(parameterName)) { - mEagerlyExposeTrackType = (boolean) value; - } - if (PARAMETER_EXPOSE_DUMMY_SEEKMAP.equals(parameterName)) { - mExposeDummySeekMap = (boolean) value; - } - if (PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT.equals(parameterName)) { - mExposeChunkIndexAsMediaFormat = (boolean) value; - } - if (PARAMETER_EXPOSE_CAPTION_FORMATS.equals(parameterName)) { - setMuxedCaptionFormats((List<MediaFormat>) value); - } - mParserParameters.put(parameterName, value); - return this; - } - - /** - * Returns whether the given {@code parameterName} is supported by this parser. - * - * @param parameterName The parameter name to check support for. One of the {@code PARAMETER_*} - * constants. - * @return Whether the given {@code parameterName} is supported. - */ - public boolean supportsParameter(@NonNull @ParameterName String parameterName) { - return EXPECTED_TYPE_BY_PARAMETER_NAME.containsKey(parameterName); - } - - /** - * Returns the name of the backing parser implementation. - * - * <p>If this instance was creating using {@link #createByName}, the provided name is returned. - * If this instance was created using {@link #create}, this method will return {@link - * #PARSER_NAME_UNKNOWN} until the first call to {@link #advance}, after which the name of the - * backing parser implementation is returned. - * - * @return The name of the backing parser implementation, or null if the backing parser - * implementation has not yet been selected. - */ - @NonNull - @ParserName - public String getParserName() { - return mParserName; - } - - /** - * Makes progress in the extraction of the input media stream, unless the end of the input has - * been reached. - * - * <p>This method will block until some progress has been made. - * - * <p>If this instance was created using {@link #create}, the first call to this method will - * sniff the content using the selected parser implementations. - * - * @param seekableInputReader The {@link SeekableInputReader} from which to obtain the media - * container data. - * @return Whether there is any data left to extract. Returns false if the end of input has been - * reached. - * @throws IOException If an error occurs while reading from the {@link SeekableInputReader}. - * @throws UnrecognizedInputFormatException If the format cannot be recognized by any of the - * underlying parser implementations. - */ - public boolean advance(@NonNull SeekableInputReader seekableInputReader) throws IOException { - if (mExtractorInput == null) { - // TODO: For efficiency, the same implementation should be used, by providing a - // clearBuffers() method, or similar. - long resourceLength = seekableInputReader.getLength(); - if (mResourceByteCount == 0) { - // For resource byte count metric collection, we only take into account the length - // of the first provided input reader. - mResourceByteCount = resourceLength; - } - mExtractorInput = - new DefaultExtractorInput( - mExoDataReader, seekableInputReader.getPosition(), resourceLength); - } - mExoDataReader.mInputReader = seekableInputReader; - - if (mExtractor == null) { - mPendingExtractorInit = true; - if (!mParserName.equals(PARSER_NAME_UNKNOWN)) { - mExtractor = createExtractor(mParserName); - } else { - for (String parserName : mParserNamesPool) { - Extractor extractor = createExtractor(parserName); - try { - if (extractor.sniff(mExtractorInput)) { - mParserName = parserName; - mExtractor = extractor; - mPendingExtractorInit = true; - break; - } - } catch (EOFException e) { - // Do nothing. - } finally { - mExtractorInput.resetPeekPosition(); - } - } - if (mExtractor == null) { - UnrecognizedInputFormatException exception = - UnrecognizedInputFormatException.createForExtractors(mParserNamesPool); - mLastObservedExceptionName = exception.getClass().getName(); - throw exception; - } - return true; - } - } - - if (mPendingExtractorInit) { - if (mExposeDummySeekMap) { - // We propagate the dummy seek map before initializing the extractor, in case the - // extractor initialization outputs a seek map. - mOutputConsumer.onSeekMapFound(SeekMap.DUMMY); - } - mExtractor.init(new ExtractorOutputAdapter()); - mPendingExtractorInit = false; - // We return after initialization to allow clients use any output information before - // starting actual extraction. - return true; - } - - if (isPendingSeek()) { - mExtractor.seek(mPendingSeekPosition, mPendingSeekTimeMicros); - removePendingSeek(); - } - - mPositionHolder.position = seekableInputReader.getPosition(); - int result; - try { - result = mExtractor.read(mExtractorInput, mPositionHolder); - } catch (Exception e) { - mLastObservedExceptionName = e.getClass().getName(); - if (e instanceof ParserException) { - throw new ParsingException((ParserException) e); - } else { - throw e; - } - } - if (result == Extractor.RESULT_END_OF_INPUT) { - mExtractorInput = null; - return false; - } - if (result == Extractor.RESULT_SEEK) { - mExtractorInput = null; - seekableInputReader.seekToPosition(mPositionHolder.position); - } - return true; - } - - /** - * Seeks within the media container being extracted. - * - * <p>{@link SeekPoint SeekPoints} can be obtained from the {@link SeekMap} passed to {@link - * OutputConsumer#onSeekMapFound(SeekMap)}. - * - * <p>Following a call to this method, the {@link InputReader} passed to the next invocation of - * {@link #advance} must provide data starting from {@link SeekPoint#position} in the stream. - * - * @param seekPoint The {@link SeekPoint} to seek to. - */ - public void seek(@NonNull SeekPoint seekPoint) { - if (mExtractor == null) { - mPendingSeekPosition = seekPoint.position; - mPendingSeekTimeMicros = seekPoint.timeMicros; - } else { - mExtractor.seek(seekPoint.position, seekPoint.timeMicros); - } - } - - /** - * Releases any acquired resources. - * - * <p>After calling this method, this instance becomes unusable and no other methods should be - * invoked. - */ - public void release() { - mExtractorInput = null; - mExtractor = null; - if (mReleased) { - // Nothing to do. - return; - } - mReleased = true; - - String trackMimeTypes = buildMediaMetricsString(format -> format.sampleMimeType); - String trackCodecs = buildMediaMetricsString(format -> format.codecs); - int videoWidth = -1; - int videoHeight = -1; - for (int i = 0; i < mTrackFormats.size(); i++) { - Format format = mTrackFormats.valueAt(i); - if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) { - videoWidth = format.width; - videoHeight = format.height; - break; - } - } - - String alteredParameters = - String.join( - MEDIAMETRICS_ELEMENT_SEPARATOR, - mParserParameters.keySet().toArray(new String[0])); - alteredParameters = - alteredParameters.substring( - 0, - Math.min( - alteredParameters.length(), - MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH)); - - nativeSubmitMetrics( - SdkLevel.isAtLeastS() ? getLogSessionIdStringV31() : "", - mParserName, - mCreatedByName, - String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool), - mLastObservedExceptionName, - addDither(mResourceByteCount), - addDither(mDurationMillis), - trackMimeTypes, - trackCodecs, - alteredParameters, - videoWidth, - videoHeight); - } - - @RequiresApi(31) - public void setLogSessionId(@NonNull LogSessionId logSessionId) { - this.mLogSessionId = Objects.requireNonNull(logSessionId); - } - - @RequiresApi(31) - @NonNull - public LogSessionId getLogSessionId() { - return mLogSessionId != null ? mLogSessionId : LogSessionId.LOG_SESSION_ID_NONE; - } - - // Private methods. - - private MediaParser( - OutputConsumer outputConsumer, boolean createdByName, String... parserNamesPool) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - throw new UnsupportedOperationException("Android version must be R or greater."); - } - mParserParameters = new HashMap<>(); - mOutputConsumer = outputConsumer; - mParserNamesPool = parserNamesPool; - mCreatedByName = createdByName; - mParserName = createdByName ? parserNamesPool[0] : PARSER_NAME_UNKNOWN; - mPositionHolder = new PositionHolder(); - mExoDataReader = new InputReadingDataReader(); - removePendingSeek(); - mScratchDataReaderAdapter = new DataReaderAdapter(); - mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter(); - mSchemeInitDataConstructor = getSchemeInitDataConstructor(); - mMuxedCaptionFormats = new ArrayList<>(); - - // MediaMetrics. - mTrackFormats = new SparseArray<>(); - mLastObservedExceptionName = ""; - mDurationMillis = -1; - } - - private String buildMediaMetricsString(Function<Format, String> formatFieldGetter) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < mTrackFormats.size(); i++) { - if (i > 0) { - stringBuilder.append(MEDIAMETRICS_ELEMENT_SEPARATOR); - } - String fieldValue = formatFieldGetter.apply(mTrackFormats.valueAt(i)); - stringBuilder.append(fieldValue != null ? fieldValue : ""); - } - return stringBuilder.substring( - 0, Math.min(stringBuilder.length(), MEDIAMETRICS_MAX_STRING_SIZE)); - } - - private void setMuxedCaptionFormats(List<MediaFormat> mediaFormats) { - mMuxedCaptionFormats.clear(); - for (MediaFormat mediaFormat : mediaFormats) { - mMuxedCaptionFormats.add(toExoPlayerCaptionFormat(mediaFormat)); - } - } - - private boolean isPendingSeek() { - return mPendingSeekPosition >= 0; - } - - private void removePendingSeek() { - mPendingSeekPosition = -1; - mPendingSeekTimeMicros = -1; - } - - private Extractor createExtractor(String parserName) { - int flags = 0; - TimestampAdjuster timestampAdjuster = null; - if (mIgnoreTimestampOffset) { - timestampAdjuster = new TimestampAdjuster(TimestampAdjuster.DO_NOT_OFFSET); - } - switch (parserName) { - case PARSER_NAME_MATROSKA: - flags = - getBooleanParameter(PARAMETER_MATROSKA_DISABLE_CUES_SEEKING) - ? MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES - : 0; - return new MatroskaExtractor(flags); - case PARSER_NAME_FMP4: - flags |= - getBooleanParameter(PARAMETER_EXPOSE_EMSG_TRACK) - ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK - : 0; - flags |= - getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS) - ? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS - : 0; - flags |= - getBooleanParameter(PARAMETER_MP4_IGNORE_TFDT_BOX) - ? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX - : 0; - flags |= - getBooleanParameter(PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES) - ? FragmentedMp4Extractor - .FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME - : 0; - return new FragmentedMp4Extractor( - flags, - timestampAdjuster, - /* sideloadedTrack= */ null, - mMuxedCaptionFormats); - case PARSER_NAME_MP4: - flags |= - getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS) - ? Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS - : 0; - return new Mp4Extractor(flags); - case PARSER_NAME_MP3: - flags |= - getBooleanParameter(PARAMETER_MP3_DISABLE_ID3) - ? Mp3Extractor.FLAG_DISABLE_ID3_METADATA - : 0; - flags |= - getBooleanParameter(PARAMETER_MP3_ENABLE_CBR_SEEKING) - ? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING - : 0; - // TODO: Add index seeking once we update the ExoPlayer version. - return new Mp3Extractor(flags); - case PARSER_NAME_ADTS: - flags |= - getBooleanParameter(PARAMETER_ADTS_ENABLE_CBR_SEEKING) - ? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING - : 0; - return new AdtsExtractor(flags); - case PARSER_NAME_AC3: - return new Ac3Extractor(); - case PARSER_NAME_TS: - flags |= - getBooleanParameter(PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES) - ? DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES - : 0; - flags |= - getBooleanParameter(PARAMETER_TS_DETECT_ACCESS_UNITS) - ? DefaultTsPayloadReaderFactory.FLAG_DETECT_ACCESS_UNITS - : 0; - flags |= - getBooleanParameter(PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS) - ? DefaultTsPayloadReaderFactory.FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS - : 0; - flags |= - getBooleanParameter(PARAMETER_TS_IGNORE_AAC_STREAM) - ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM - : 0; - flags |= - getBooleanParameter(PARAMETER_TS_IGNORE_AVC_STREAM) - ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM - : 0; - flags |= - getBooleanParameter(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM) - ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM - : 0; - flags |= - getBooleanParameter(PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS) - ? DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS - : 0; - String tsMode = getStringParameter(PARAMETER_TS_MODE, TS_MODE_SINGLE_PMT); - int hlsMode = - TS_MODE_SINGLE_PMT.equals(tsMode) - ? TsExtractor.MODE_SINGLE_PMT - : TS_MODE_HLS.equals(tsMode) - ? TsExtractor.MODE_HLS - : TsExtractor.MODE_MULTI_PMT; - return new TsExtractor( - hlsMode, - timestampAdjuster != null - ? timestampAdjuster - : new TimestampAdjuster(/* firstSampleTimestampUs= */ 0), - new DefaultTsPayloadReaderFactory(flags, mMuxedCaptionFormats)); - case PARSER_NAME_FLV: - return new FlvExtractor(); - case PARSER_NAME_OGG: - return new OggExtractor(); - case PARSER_NAME_PS: - return new PsExtractor(); - case PARSER_NAME_WAV: - return new WavExtractor(); - case PARSER_NAME_AMR: - flags |= - getBooleanParameter(PARAMETER_AMR_ENABLE_CBR_SEEKING) - ? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING - : 0; - return new AmrExtractor(flags); - case PARSER_NAME_AC4: - return new Ac4Extractor(); - case PARSER_NAME_FLAC: - flags |= - getBooleanParameter(PARAMETER_FLAC_DISABLE_ID3) - ? FlacExtractor.FLAG_DISABLE_ID3_METADATA - : 0; - return new FlacExtractor(flags); - default: - // Should never happen. - throw new IllegalStateException("Unexpected attempt to create: " + parserName); - } - } - - private boolean getBooleanParameter(String name) { - return (boolean) mParserParameters.getOrDefault(name, false); - } - - private String getStringParameter(String name, String defaultValue) { - return (String) mParserParameters.getOrDefault(name, defaultValue); - } - - @RequiresApi(31) - private String getLogSessionIdStringV31() { - return mLogSessionId != null ? mLogSessionId.getStringId() : ""; - } - - // Private classes. - - private static final class InputReadingDataReader implements DataReader { - - public InputReader mInputReader; - - @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { - return mInputReader.read(buffer, offset, readLength); - } - } - - private final class MediaParserDrmInitData extends DrmInitData { - - private final SchemeInitData[] mSchemeDatas; - - private MediaParserDrmInitData(com.google.android.exoplayer2.drm.DrmInitData exoDrmInitData) - throws IllegalAccessException, InstantiationException, InvocationTargetException { - mSchemeDatas = new SchemeInitData[exoDrmInitData.schemeDataCount]; - for (int i = 0; i < mSchemeDatas.length; i++) { - mSchemeDatas[i] = toFrameworkSchemeInitData(exoDrmInitData.get(i)); - } - } - - @Override - @Nullable - public SchemeInitData get(UUID schemeUuid) { - for (SchemeInitData schemeInitData : mSchemeDatas) { - if (schemeInitData.uuid.equals(schemeUuid)) { - return schemeInitData; - } - } - return null; - } - - @Override - public SchemeInitData getSchemeInitDataAt(int index) { - return mSchemeDatas[index]; - } - - @Override - public int getSchemeInitDataCount() { - return mSchemeDatas.length; - } - - private DrmInitData.SchemeInitData toFrameworkSchemeInitData(SchemeData exoSchemeData) - throws IllegalAccessException, InvocationTargetException, InstantiationException { - return mSchemeInitDataConstructor.newInstance( - exoSchemeData.uuid, exoSchemeData.mimeType, exoSchemeData.data); - } - } - - private final class ExtractorOutputAdapter implements ExtractorOutput { - - private final SparseArray<TrackOutput> mTrackOutputAdapters; - private boolean mTracksEnded; - - private ExtractorOutputAdapter() { - mTrackOutputAdapters = new SparseArray<>(); - } - - @Override - public TrackOutput track(int id, int type) { - TrackOutput trackOutput = mTrackOutputAdapters.get(id); - if (trackOutput == null) { - int trackIndex = mTrackOutputAdapters.size(); - trackOutput = new TrackOutputAdapter(trackIndex); - mTrackOutputAdapters.put(id, trackOutput); - if (mEagerlyExposeTrackType) { - MediaFormat mediaFormat = new MediaFormat(); - mediaFormat.setString("track-type-string", toTypeString(type)); - mOutputConsumer.onTrackDataFound( - trackIndex, new TrackData(mediaFormat, /* drmInitData= */ null)); - } - } - return trackOutput; - } - - @Override - public void endTracks() { - mOutputConsumer.onTrackCountFound(mTrackOutputAdapters.size()); - } - - @Override - public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { - long durationUs = exoplayerSeekMap.getDurationUs(); - if (durationUs != C.TIME_UNSET) { - mDurationMillis = C.usToMs(durationUs); - } - if (mExposeChunkIndexAsMediaFormat && exoplayerSeekMap instanceof ChunkIndex) { - ChunkIndex chunkIndex = (ChunkIndex) exoplayerSeekMap; - MediaFormat mediaFormat = new MediaFormat(); - mediaFormat.setByteBuffer("chunk-index-int-sizes", toByteBuffer(chunkIndex.sizes)); - mediaFormat.setByteBuffer( - "chunk-index-long-offsets", toByteBuffer(chunkIndex.offsets)); - mediaFormat.setByteBuffer( - "chunk-index-long-us-durations", toByteBuffer(chunkIndex.durationsUs)); - mediaFormat.setByteBuffer( - "chunk-index-long-us-times", toByteBuffer(chunkIndex.timesUs)); - mOutputConsumer.onTrackDataFound( - /* trackIndex= */ 0, new TrackData(mediaFormat, /* drmInitData= */ null)); - } - mOutputConsumer.onSeekMapFound(new SeekMap(exoplayerSeekMap)); - } - } - - private class TrackOutputAdapter implements TrackOutput { - - private final int mTrackIndex; - - private CryptoInfo mLastOutputCryptoInfo; - private CryptoInfo.Pattern mLastOutputEncryptionPattern; - private CryptoData mLastReceivedCryptoData; - - @EncryptionDataReadState private int mEncryptionDataReadState; - private int mEncryptionDataSizeToSubtractFromSampleDataSize; - private int mEncryptionVectorSize; - private byte[] mScratchIvSpace; - private int mSubsampleEncryptionDataSize; - private int[] mScratchSubsampleEncryptedBytesCount; - private int[] mScratchSubsampleClearBytesCount; - private boolean mHasSubsampleEncryptionData; - private int mSkippedSupplementalDataBytes; - - private TrackOutputAdapter(int trackIndex) { - mTrackIndex = trackIndex; - mScratchIvSpace = new byte[16]; // Size documented in CryptoInfo. - mScratchSubsampleEncryptedBytesCount = new int[32]; - mScratchSubsampleClearBytesCount = new int[32]; - mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE; - mLastOutputEncryptionPattern = - new CryptoInfo.Pattern(/* blocksToEncrypt= */ 0, /* blocksToSkip= */ 0); - } - - @Override - public void format(Format format) { - mTrackFormats.put(mTrackIndex, format); - mOutputConsumer.onTrackDataFound( - mTrackIndex, - new TrackData( - toMediaFormat(format), toFrameworkDrmInitData(format.drmInitData))); - } - - @Override - public int sampleData( - DataReader input, - int length, - boolean allowEndOfInput, - @SampleDataPart int sampleDataPart) - throws IOException { - mScratchDataReaderAdapter.setDataReader(input, length); - long positionBeforeReading = mScratchDataReaderAdapter.getPosition(); - mOutputConsumer.onSampleDataFound(mTrackIndex, mScratchDataReaderAdapter); - return (int) (mScratchDataReaderAdapter.getPosition() - positionBeforeReading); - } - - @Override - public void sampleData( - ParsableByteArray data, int length, @SampleDataPart int sampleDataPart) { - if (sampleDataPart == SAMPLE_DATA_PART_ENCRYPTION && !mInBandCryptoInfo) { - while (length > 0) { - switch (mEncryptionDataReadState) { - case STATE_READING_SIGNAL_BYTE: - int encryptionSignalByte = data.readUnsignedByte(); - length--; - mHasSubsampleEncryptionData = ((encryptionSignalByte >> 7) & 1) != 0; - mEncryptionVectorSize = encryptionSignalByte & 0x7F; - mEncryptionDataSizeToSubtractFromSampleDataSize = - mEncryptionVectorSize + 1; // Signal byte. - mEncryptionDataReadState = STATE_READING_INIT_VECTOR; - break; - case STATE_READING_INIT_VECTOR: - Arrays.fill(mScratchIvSpace, (byte) 0); // Ensure 0-padding. - data.readBytes(mScratchIvSpace, /* offset= */ 0, mEncryptionVectorSize); - length -= mEncryptionVectorSize; - if (mHasSubsampleEncryptionData) { - mEncryptionDataReadState = STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE; - } else { - mSubsampleEncryptionDataSize = 0; - mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE; - } - break; - case STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE: - mSubsampleEncryptionDataSize = data.readUnsignedShort(); - if (mScratchSubsampleClearBytesCount.length - < mSubsampleEncryptionDataSize) { - mScratchSubsampleClearBytesCount = - new int[mSubsampleEncryptionDataSize]; - mScratchSubsampleEncryptedBytesCount = - new int[mSubsampleEncryptionDataSize]; - } - length -= 2; - mEncryptionDataSizeToSubtractFromSampleDataSize += - 2 - + mSubsampleEncryptionDataSize - * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY; - mEncryptionDataReadState = STATE_READING_SUBSAMPLE_ENCRYPTION_DATA; - break; - case STATE_READING_SUBSAMPLE_ENCRYPTION_DATA: - for (int i = 0; i < mSubsampleEncryptionDataSize; i++) { - mScratchSubsampleClearBytesCount[i] = data.readUnsignedShort(); - mScratchSubsampleEncryptedBytesCount[i] = data.readInt(); - } - length -= - mSubsampleEncryptionDataSize - * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY; - mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE; - if (length != 0) { - throw new IllegalStateException(); - } - break; - default: - // Never happens. - throw new IllegalStateException(); - } - } - } else if (sampleDataPart == SAMPLE_DATA_PART_SUPPLEMENTAL - && !mIncludeSupplementalData) { - mSkippedSupplementalDataBytes += length; - data.skipBytes(length); - } else { - outputSampleData(data, length); - } - } - - @Override - public void sampleMetadata( - long timeUs, int flags, int size, int offset, @Nullable CryptoData cryptoData) { - size -= mSkippedSupplementalDataBytes; - mSkippedSupplementalDataBytes = 0; - mOutputConsumer.onSampleCompleted( - mTrackIndex, - timeUs, - getMediaParserFlags(flags), - size - mEncryptionDataSizeToSubtractFromSampleDataSize, - offset, - getPopulatedCryptoInfo(cryptoData)); - mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE; - mEncryptionDataSizeToSubtractFromSampleDataSize = 0; - } - - @Nullable - private CryptoInfo getPopulatedCryptoInfo(@Nullable CryptoData cryptoData) { - if (cryptoData == null) { - // The sample is not encrypted. - return null; - } else if (mInBandCryptoInfo) { - if (cryptoData != mLastReceivedCryptoData) { - mLastOutputCryptoInfo = - createNewCryptoInfoAndPopulateWithCryptoData(cryptoData); - // We are using in-band crypto info, so the IV will be ignored. But we prevent - // it from being null because toString assumes it non-null. - mLastOutputCryptoInfo.iv = EMPTY_BYTE_ARRAY; - } - } else /* We must populate the full CryptoInfo. */ { - // CryptoInfo.pattern is not accessible to the user, so the user needs to feed - // this CryptoInfo directly to MediaCodec. We need to create a new CryptoInfo per - // sample because of per-sample initialization vector changes. - CryptoInfo newCryptoInfo = createNewCryptoInfoAndPopulateWithCryptoData(cryptoData); - newCryptoInfo.iv = Arrays.copyOf(mScratchIvSpace, mScratchIvSpace.length); - boolean canReuseSubsampleInfo = - mLastOutputCryptoInfo != null - && mLastOutputCryptoInfo.numSubSamples - == mSubsampleEncryptionDataSize; - for (int i = 0; i < mSubsampleEncryptionDataSize && canReuseSubsampleInfo; i++) { - canReuseSubsampleInfo = - mLastOutputCryptoInfo.numBytesOfClearData[i] - == mScratchSubsampleClearBytesCount[i] - && mLastOutputCryptoInfo.numBytesOfEncryptedData[i] - == mScratchSubsampleEncryptedBytesCount[i]; - } - newCryptoInfo.numSubSamples = mSubsampleEncryptionDataSize; - if (canReuseSubsampleInfo) { - newCryptoInfo.numBytesOfClearData = mLastOutputCryptoInfo.numBytesOfClearData; - newCryptoInfo.numBytesOfEncryptedData = - mLastOutputCryptoInfo.numBytesOfEncryptedData; - } else { - newCryptoInfo.numBytesOfClearData = - Arrays.copyOf( - mScratchSubsampleClearBytesCount, mSubsampleEncryptionDataSize); - newCryptoInfo.numBytesOfEncryptedData = - Arrays.copyOf( - mScratchSubsampleEncryptedBytesCount, - mSubsampleEncryptionDataSize); - } - mLastOutputCryptoInfo = newCryptoInfo; - } - mLastReceivedCryptoData = cryptoData; - return mLastOutputCryptoInfo; - } - - private CryptoInfo createNewCryptoInfoAndPopulateWithCryptoData(CryptoData cryptoData) { - CryptoInfo cryptoInfo = new CryptoInfo(); - cryptoInfo.key = cryptoData.encryptionKey; - cryptoInfo.mode = cryptoData.cryptoMode; - if (cryptoData.clearBlocks != mLastOutputEncryptionPattern.getSkipBlocks() - || cryptoData.encryptedBlocks - != mLastOutputEncryptionPattern.getEncryptBlocks()) { - mLastOutputEncryptionPattern = - new CryptoInfo.Pattern(cryptoData.encryptedBlocks, cryptoData.clearBlocks); - } - cryptoInfo.setPattern(mLastOutputEncryptionPattern); - return cryptoInfo; - } - - private void outputSampleData(ParsableByteArray data, int length) { - mScratchParsableByteArrayAdapter.resetWithByteArray(data, length); - try { - // Read all bytes from data. ExoPlayer extractors expect all sample data to be - // consumed by TrackOutput implementations when passing a ParsableByteArray. - while (mScratchParsableByteArrayAdapter.getLength() > 0) { - mOutputConsumer.onSampleDataFound( - mTrackIndex, mScratchParsableByteArrayAdapter); - } - } catch (IOException e) { - // Unexpected. - throw new RuntimeException(e); - } - } - } - - private static final class DataReaderAdapter implements InputReader { - - private DataReader mDataReader; - private int mCurrentPosition; - private long mLength; - - public void setDataReader(DataReader dataReader, long length) { - mDataReader = dataReader; - mCurrentPosition = 0; - mLength = length; - } - - // Input implementation. - - @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { - int readBytes = 0; - readBytes = mDataReader.read(buffer, offset, readLength); - mCurrentPosition += readBytes; - return readBytes; - } - - @Override - public long getPosition() { - return mCurrentPosition; - } - - @Override - public long getLength() { - return mLength - mCurrentPosition; - } - } - - private static final class ParsableByteArrayAdapter implements InputReader { - - private ParsableByteArray mByteArray; - private long mLength; - private int mCurrentPosition; - - public void resetWithByteArray(ParsableByteArray byteArray, long length) { - mByteArray = byteArray; - mCurrentPosition = 0; - mLength = length; - } - - // Input implementation. - - @Override - public int read(byte[] buffer, int offset, int readLength) { - mByteArray.readBytes(buffer, offset, readLength); - mCurrentPosition += readLength; - return readLength; - } - - @Override - public long getPosition() { - return mCurrentPosition; - } - - @Override - public long getLength() { - return mLength - mCurrentPosition; - } - } - - private static final class DummyExoPlayerSeekMap - implements com.google.android.exoplayer2.extractor.SeekMap { - - @Override - public boolean isSeekable() { - return true; - } - - @Override - public long getDurationUs() { - return C.TIME_UNSET; - } - - @Override - public SeekPoints getSeekPoints(long timeUs) { - com.google.android.exoplayer2.extractor.SeekPoint seekPoint = - new com.google.android.exoplayer2.extractor.SeekPoint( - timeUs, /* position= */ 0); - return new SeekPoints(seekPoint, seekPoint); - } - } - - /** Creates extractor instances. */ - private interface ExtractorFactory { - - /** Returns a new extractor instance. */ - Extractor createInstance(); - } - - // Private static methods. - - private static Format toExoPlayerCaptionFormat(MediaFormat mediaFormat) { - Format.Builder formatBuilder = - new Format.Builder().setSampleMimeType(mediaFormat.getString(MediaFormat.KEY_MIME)); - if (mediaFormat.containsKey(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)) { - formatBuilder.setAccessibilityChannel( - mediaFormat.getInteger(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)); - } - return formatBuilder.build(); - } - - private static MediaFormat toMediaFormat(Format format) { - MediaFormat result = new MediaFormat(); - setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate); - setOptionalMediaFormatInt(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount); - - ColorInfo colorInfo = format.colorInfo; - if (colorInfo != null) { - setOptionalMediaFormatInt( - result, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer); - setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange); - setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace); - - if (format.colorInfo.hdrStaticInfo != null) { - result.setByteBuffer( - MediaFormat.KEY_HDR_STATIC_INFO, - ByteBuffer.wrap(format.colorInfo.hdrStaticInfo)); - } - } - - setOptionalMediaFormatString(result, MediaFormat.KEY_MIME, format.sampleMimeType); - setOptionalMediaFormatString(result, MediaFormat.KEY_CODECS_STRING, format.codecs); - if (format.frameRate != Format.NO_VALUE) { - result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate); - } - setOptionalMediaFormatInt(result, MediaFormat.KEY_WIDTH, format.width); - setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height); - - List<byte[]> initData = format.initializationData; - for (int i = 0; i < initData.size(); i++) { - result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i))); - } - setPcmEncoding(format, result); - setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language); - setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); - setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees); - setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate); - setOptionalMediaFormatInt( - result, MediaFormat.KEY_CAPTION_SERVICE_NUMBER, format.accessibilityChannel); - - int selectionFlags = format.selectionFlags; - result.setInteger( - MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT); - result.setInteger(MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT); - result.setInteger( - MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED); - - setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay); - setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding); - - if (format.pixelWidthHeightRatio != Format.NO_VALUE && format.pixelWidthHeightRatio != 0) { - int parWidth = 1; - int parHeight = 1; - if (format.pixelWidthHeightRatio < 1.0f) { - parHeight = 1 << 30; - parWidth = (int) (format.pixelWidthHeightRatio * parHeight); - } else if (format.pixelWidthHeightRatio > 1.0f) { - parWidth = 1 << 30; - parHeight = (int) (parWidth / format.pixelWidthHeightRatio); - } - result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, parWidth); - result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, parHeight); - result.setFloat("pixel-width-height-ratio-float", format.pixelWidthHeightRatio); - } - if (format.drmInitData != null) { - // The crypto mode is propagated along with sample metadata. We also include it in the - // format for convenient use from ExoPlayer. - result.setString("crypto-mode-fourcc", format.drmInitData.schemeType); - } - if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) { - result.setLong("subsample-offset-us-long", format.subsampleOffsetUs); - } - // LACK OF SUPPORT FOR: - // format.id; - // format.metadata; - // format.stereoMode; - return result; - } - - private static ByteBuffer toByteBuffer(long[] longArray) { - ByteBuffer byteBuffer = ByteBuffer.allocateDirect(longArray.length * Long.BYTES); - for (long element : longArray) { - byteBuffer.putLong(element); - } - byteBuffer.flip(); - return byteBuffer; - } - - private static ByteBuffer toByteBuffer(int[] intArray) { - ByteBuffer byteBuffer = ByteBuffer.allocateDirect(intArray.length * Integer.BYTES); - for (int element : intArray) { - byteBuffer.putInt(element); - } - byteBuffer.flip(); - return byteBuffer; - } - - private static String toTypeString(int type) { - switch (type) { - case C.TRACK_TYPE_VIDEO: - return "video"; - case C.TRACK_TYPE_AUDIO: - return "audio"; - case C.TRACK_TYPE_TEXT: - return "text"; - case C.TRACK_TYPE_METADATA: - return "metadata"; - default: - return "unknown"; - } - } - - private static void setPcmEncoding(Format format, MediaFormat result) { - int exoPcmEncoding = format.pcmEncoding; - setOptionalMediaFormatInt(result, "exo-pcm-encoding", format.pcmEncoding); - int mediaFormatPcmEncoding; - switch (exoPcmEncoding) { - case C.ENCODING_PCM_8BIT: - mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_8BIT; - break; - case C.ENCODING_PCM_16BIT: - mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_16BIT; - break; - case C.ENCODING_PCM_FLOAT: - mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_FLOAT; - break; - default: - // No matching value. Do nothing. - return; - } - result.setInteger(MediaFormat.KEY_PCM_ENCODING, mediaFormatPcmEncoding); - } - - private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) { - if (value != Format.NO_VALUE) { - mediaFormat.setInteger(key, value); - } - } - - private static void setOptionalMediaFormatString( - MediaFormat mediaFormat, String key, @Nullable String value) { - if (value != null) { - mediaFormat.setString(key, value); - } - } - - private DrmInitData toFrameworkDrmInitData( - com.google.android.exoplayer2.drm.DrmInitData exoDrmInitData) { - try { - return exoDrmInitData != null && mSchemeInitDataConstructor != null - ? new MediaParserDrmInitData(exoDrmInitData) - : null; - } catch (Throwable e) { - if (!mLoggedSchemeInitDataCreationException) { - mLoggedSchemeInitDataCreationException = true; - Log.e(TAG, "Unable to create SchemeInitData instance."); - } - return null; - } - } - - /** Returns a new {@link SeekPoint} equivalent to the given {@code exoPlayerSeekPoint}. */ - private static SeekPoint toSeekPoint( - com.google.android.exoplayer2.extractor.SeekPoint exoPlayerSeekPoint) { - return new SeekPoint(exoPlayerSeekPoint.timeUs, exoPlayerSeekPoint.position); - } - - /** - * Introduces random error to the given metric value in order to prevent the identification of - * the parsed media. - */ - private static long addDither(long value) { - // Generate a random in [0, 1]. - double randomDither = ThreadLocalRandom.current().nextFloat(); - // Clamp the random number to [0, 2 * MEDIAMETRICS_DITHER]. - randomDither *= 2 * MEDIAMETRICS_DITHER; - // Translate the random number to [1 - MEDIAMETRICS_DITHER, 1 + MEDIAMETRICS_DITHER]. - randomDither += 1 - MEDIAMETRICS_DITHER; - return value != -1 ? (long) (value * randomDither) : -1; - } - - private static void assertValidNames(@NonNull String[] names) { - for (String name : names) { - if (!EXTRACTOR_FACTORIES_BY_NAME.containsKey(name)) { - throw new IllegalArgumentException( - "Invalid extractor name: " - + name - + ". Supported parsers are: " - + TextUtils.join(", ", EXTRACTOR_FACTORIES_BY_NAME.keySet()) - + "."); - } - } - } - - private int getMediaParserFlags(int flags) { - @SampleFlags int result = 0; - result |= (flags & C.BUFFER_FLAG_ENCRYPTED) != 0 ? SAMPLE_FLAG_ENCRYPTED : 0; - result |= (flags & C.BUFFER_FLAG_KEY_FRAME) != 0 ? SAMPLE_FLAG_KEY_FRAME : 0; - result |= (flags & C.BUFFER_FLAG_DECODE_ONLY) != 0 ? SAMPLE_FLAG_DECODE_ONLY : 0; - result |= - (flags & C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA) != 0 && mIncludeSupplementalData - ? SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA - : 0; - result |= (flags & C.BUFFER_FLAG_LAST_SAMPLE) != 0 ? SAMPLE_FLAG_LAST_SAMPLE : 0; - return result; - } - - @Nullable - private static Constructor<DrmInitData.SchemeInitData> getSchemeInitDataConstructor() { - // TODO: Use constructor statically when available. - Constructor<DrmInitData.SchemeInitData> constructor; - try { - return DrmInitData.SchemeInitData.class.getConstructor( - UUID.class, String.class, byte[].class); - } catch (Throwable e) { - Log.e(TAG, "Unable to get SchemeInitData constructor."); - return null; - } - } - - // Native methods. - - private native void nativeSubmitMetrics( - String logSessionId, - String parserName, - boolean createdByName, - String parserPool, - String lastObservedExceptionName, - long resourceByteCount, - long durationMillis, - String trackMimeTypes, - String trackCodecs, - String alteredParameters, - int videoWidth, - int videoHeight); - - // Static initialization. - - static { - System.loadLibrary(JNI_LIBRARY_NAME); - - // Using a LinkedHashMap to keep the insertion order when iterating over the keys. - LinkedHashMap<String, ExtractorFactory> extractorFactoriesByName = new LinkedHashMap<>(); - // Parsers are ordered to match ExoPlayer's DefaultExtractorsFactory extractor ordering, - // which in turn aims to minimize the chances of incorrect extractor selections. - extractorFactoriesByName.put(PARSER_NAME_MATROSKA, MatroskaExtractor::new); - extractorFactoriesByName.put(PARSER_NAME_FMP4, FragmentedMp4Extractor::new); - extractorFactoriesByName.put(PARSER_NAME_MP4, Mp4Extractor::new); - extractorFactoriesByName.put(PARSER_NAME_MP3, Mp3Extractor::new); - extractorFactoriesByName.put(PARSER_NAME_ADTS, AdtsExtractor::new); - extractorFactoriesByName.put(PARSER_NAME_AC3, Ac3Extractor::new); - extractorFactoriesByName.put(PARSER_NAME_TS, TsExtractor::new); - extractorFactoriesByName.put(PARSER_NAME_FLV, FlvExtractor::new); - extractorFactoriesByName.put(PARSER_NAME_OGG, OggExtractor::new); - extractorFactoriesByName.put(PARSER_NAME_PS, PsExtractor::new); - extractorFactoriesByName.put(PARSER_NAME_WAV, WavExtractor::new); - extractorFactoriesByName.put(PARSER_NAME_AMR, AmrExtractor::new); - extractorFactoriesByName.put(PARSER_NAME_AC4, Ac4Extractor::new); - extractorFactoriesByName.put(PARSER_NAME_FLAC, FlacExtractor::new); - EXTRACTOR_FACTORIES_BY_NAME = Collections.unmodifiableMap(extractorFactoriesByName); - - HashMap<String, Class> expectedTypeByParameterName = new HashMap<>(); - expectedTypeByParameterName.put(PARAMETER_ADTS_ENABLE_CBR_SEEKING, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_AMR_ENABLE_CBR_SEEKING, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_FLAC_DISABLE_ID3, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_MP4_IGNORE_EDIT_LISTS, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_MP4_IGNORE_TFDT_BOX, Boolean.class); - expectedTypeByParameterName.put( - PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_MATROSKA_DISABLE_CUES_SEEKING, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_MP3_DISABLE_ID3, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_MP3_ENABLE_CBR_SEEKING, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_MP3_ENABLE_INDEX_SEEKING, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_TS_MODE, String.class); - expectedTypeByParameterName.put(PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_AAC_STREAM, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_AVC_STREAM, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_TS_DETECT_ACCESS_UNITS, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_IN_BAND_CRYPTO_INFO, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class); - expectedTypeByParameterName.put( - PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, Boolean.class); - expectedTypeByParameterName.put( - PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS, Boolean.class); - expectedTypeByParameterName.put(PARAMETER_EXPOSE_EMSG_TRACK, Boolean.class); - // We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters - // instead. Checking that the value is a List is insufficient to catch wrong parameter - // value types. - int sumOfParameterNameLengths = - expectedTypeByParameterName.keySet().stream() - .map(String::length) - .reduce(0, Integer::sum); - sumOfParameterNameLengths += PARAMETER_EXPOSE_CAPTION_FORMATS.length(); - // Add space for any required separators. - MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH = - sumOfParameterNameLengths + expectedTypeByParameterName.size(); - - EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName); - } -} diff --git a/apex/media/framework/java/android/media/MediaSession2.java b/apex/media/framework/java/android/media/MediaSession2.java deleted file mode 100644 index 7d07eb35832e..000000000000 --- a/apex/media/framework/java/android/media/MediaSession2.java +++ /dev/null @@ -1,932 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; -import static android.media.MediaConstants.KEY_CONNECTION_HINTS; -import static android.media.MediaConstants.KEY_PACKAGE_NAME; -import static android.media.MediaConstants.KEY_PID; -import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; -import static android.media.MediaConstants.KEY_TOKEN_EXTRAS; -import static android.media.Session2Command.Result.RESULT_ERROR_UNKNOWN_ERROR; -import static android.media.Session2Command.Result.RESULT_INFO_SKIPPED; -import static android.media.Session2Token.TYPE_SESSION; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.media.session.MediaSessionManager; -import android.media.session.MediaSessionManager.RemoteUserInfo; -import android.os.BadParcelableException; -import android.os.Bundle; -import android.os.Handler; -import android.os.Parcel; -import android.os.Process; -import android.os.ResultReceiver; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; - -import com.android.modules.utils.build.SdkLevel; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.Executor; - -/** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Allows a media app to expose its transport controls and playback information in a process to - * other processes including the Android framework and other apps. - */ -public class MediaSession2 implements AutoCloseable { - static final String TAG = "MediaSession2"; - static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - // Note: This checks the uniqueness of a session ID only in a single process. - // When the framework becomes able to check the uniqueness, this logic should be removed. - //@GuardedBy("MediaSession.class") - private static final List<String> SESSION_ID_LIST = new ArrayList<>(); - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Object mLock = new Object(); - //@GuardedBy("mLock") - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Map<Controller2Link, ControllerInfo> mConnectedControllers = new HashMap<>(); - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Context mContext; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Executor mCallbackExecutor; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final SessionCallback mCallback; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Session2Link mSessionStub; - - private final String mSessionId; - private final PendingIntent mSessionActivity; - private final Session2Token mSessionToken; - private final MediaSessionManager mMediaSessionManager; - private final MediaCommunicationManager mCommunicationManager; - private final Handler mResultHandler; - - //@GuardedBy("mLock") - private boolean mClosed; - //@GuardedBy("mLock") - private boolean mPlaybackActive; - //@GuardedBy("mLock") - private ForegroundServiceEventCallback mForegroundServiceEventCallback; - - MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity, - @NonNull Executor callbackExecutor, @NonNull SessionCallback callback, - @NonNull Bundle tokenExtras) { - synchronized (MediaSession2.class) { - if (SESSION_ID_LIST.contains(id)) { - throw new IllegalStateException("Session ID must be unique. ID=" + id); - } - SESSION_ID_LIST.add(id); - } - - mContext = context; - mSessionId = id; - mSessionActivity = sessionActivity; - mCallbackExecutor = callbackExecutor; - mCallback = callback; - mSessionStub = new Session2Link(this); - mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(), - mSessionStub, tokenExtras); - if (SdkLevel.isAtLeastS()) { - mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class); - mMediaSessionManager = null; - } else { - mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); - mCommunicationManager = null; - } - // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. - mResultHandler = new Handler(context.getMainLooper()); - mClosed = false; - } - - @Override - public void close() { - try { - List<ControllerInfo> controllerInfos; - ForegroundServiceEventCallback callback; - synchronized (mLock) { - if (mClosed) { - return; - } - mClosed = true; - controllerInfos = getConnectedControllers(); - mConnectedControllers.clear(); - callback = mForegroundServiceEventCallback; - mForegroundServiceEventCallback = null; - } - synchronized (MediaSession2.class) { - SESSION_ID_LIST.remove(mSessionId); - } - if (callback != null) { - callback.onSessionClosed(this); - } - for (ControllerInfo info : controllerInfos) { - info.notifyDisconnected(); - } - } catch (Exception e) { - // Should not be here. - } - } - - /** - * Returns the session ID - */ - @NonNull - public String getId() { - return mSessionId; - } - - /** - * Returns the {@link Session2Token} for creating {@link MediaController2}. - */ - @NonNull - public Session2Token getToken() { - return mSessionToken; - } - - /** - * Broadcasts a session command to all the connected controllers - * <p> - * @param command the session command - * @param args optional arguments - */ - public void broadcastSessionCommand(@NonNull Session2Command command, @Nullable Bundle args) { - if (command == null) { - throw new IllegalArgumentException("command shouldn't be null"); - } - List<ControllerInfo> controllerInfos = getConnectedControllers(); - for (ControllerInfo controller : controllerInfos) { - controller.sendSessionCommand(command, args, null); - } - } - - /** - * Sends a session command to a specific controller - * <p> - * @param controller the controller to get the session command - * @param command the session command - * @param args optional arguments - * @return a token which will be sent together in {@link SessionCallback#onCommandResult} - * when its result is received. - */ - @NonNull - public Object sendSessionCommand(@NonNull ControllerInfo controller, - @NonNull Session2Command command, @Nullable Bundle args) { - if (controller == null) { - throw new IllegalArgumentException("controller shouldn't be null"); - } - if (command == null) { - throw new IllegalArgumentException("command shouldn't be null"); - } - ResultReceiver resultReceiver = new ResultReceiver(mResultHandler) { - protected void onReceiveResult(int resultCode, Bundle resultData) { - controller.receiveCommandResult(this); - mCallbackExecutor.execute(() -> { - mCallback.onCommandResult(MediaSession2.this, controller, this, - command, new Session2Command.Result(resultCode, resultData)); - }); - } - }; - controller.sendSessionCommand(command, args, resultReceiver); - return resultReceiver; - } - - /** - * Cancels the session command previously sent. - * - * @param controller the controller to get the session command - * @param token the token which is returned from {@link #sendSessionCommand}. - */ - public void cancelSessionCommand(@NonNull ControllerInfo controller, @NonNull Object token) { - if (controller == null) { - throw new IllegalArgumentException("controller shouldn't be null"); - } - if (token == null) { - throw new IllegalArgumentException("token shouldn't be null"); - } - controller.cancelSessionCommand(token); - } - - /** - * Sets whether the playback is active (i.e. playing something) - * - * @param playbackActive {@code true} if the playback active, {@code false} otherwise. - **/ - public void setPlaybackActive(boolean playbackActive) { - final ForegroundServiceEventCallback serviceCallback; - synchronized (mLock) { - if (mPlaybackActive == playbackActive) { - return; - } - mPlaybackActive = playbackActive; - serviceCallback = mForegroundServiceEventCallback; - } - if (serviceCallback != null) { - serviceCallback.onPlaybackActiveChanged(this, playbackActive); - } - List<ControllerInfo> controllerInfos = getConnectedControllers(); - for (ControllerInfo controller : controllerInfos) { - controller.notifyPlaybackActiveChanged(playbackActive); - } - } - - /** - * Returns whether the playback is active (i.e. playing something) - * - * @return {@code true} if the playback active, {@code false} otherwise. - */ - public boolean isPlaybackActive() { - synchronized (mLock) { - return mPlaybackActive; - } - } - - /** - * Gets the list of the connected controllers - * - * @return list of the connected controllers. - */ - @NonNull - public List<ControllerInfo> getConnectedControllers() { - List<ControllerInfo> controllers = new ArrayList<>(); - synchronized (mLock) { - controllers.addAll(mConnectedControllers.values()); - } - return controllers; - } - - /** - * Returns whether the given bundle includes non-framework Parcelables. - */ - static boolean hasCustomParcelable(@Nullable Bundle bundle) { - if (bundle == null) { - return false; - } - - // Try writing the bundle to parcel, and read it with framework classloader. - Parcel parcel = null; - try { - parcel = Parcel.obtain(); - parcel.writeBundle(bundle); - parcel.setDataPosition(0); - Bundle out = parcel.readBundle(null); - - for (String key : out.keySet()) { - out.get(key); - } - } catch (BadParcelableException e) { - Log.d(TAG, "Custom parcelable in bundle.", e); - return true; - } finally { - if (parcel != null) { - parcel.recycle(); - } - } - return false; - } - - boolean isClosed() { - synchronized (mLock) { - return mClosed; - } - } - - SessionCallback getCallback() { - return mCallback; - } - - boolean isTrustedForMediaControl(RemoteUserInfo remoteUserInfo) { - if (SdkLevel.isAtLeastS()) { - return mCommunicationManager.isTrustedForMediaControl(remoteUserInfo); - } else { - return mMediaSessionManager.isTrustedForMediaControl(remoteUserInfo); - } - } - - void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { - synchronized (mLock) { - if (mForegroundServiceEventCallback == callback) { - return; - } - if (mForegroundServiceEventCallback != null && callback != null) { - throw new IllegalStateException("A session cannot be added to multiple services"); - } - mForegroundServiceEventCallback = callback; - } - } - - // Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect - void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq, - Bundle connectionRequest) { - if (callingPid == 0) { - // The pid here is from Binder.getCallingPid(), which can be 0 for an oneway call from - // the remote process. If it's the case, use PID from the connectionRequest. - callingPid = connectionRequest.getInt(KEY_PID); - } - String callingPkg = connectionRequest.getString(KEY_PACKAGE_NAME); - - RemoteUserInfo remoteUserInfo = new RemoteUserInfo(callingPkg, callingPid, callingUid); - - Bundle connectionHints = connectionRequest.getBundle(KEY_CONNECTION_HINTS); - if (connectionHints == null) { - Log.w(TAG, "connectionHints shouldn't be null."); - connectionHints = Bundle.EMPTY; - } else if (hasCustomParcelable(connectionHints)) { - Log.w(TAG, "connectionHints contain custom parcelable. Ignoring."); - connectionHints = Bundle.EMPTY; - } - - final ControllerInfo controllerInfo = new ControllerInfo( - remoteUserInfo, - isTrustedForMediaControl(remoteUserInfo), - controller, - connectionHints); - mCallbackExecutor.execute(() -> { - boolean connected = false; - try { - if (isClosed()) { - return; - } - controllerInfo.mAllowedCommands = - mCallback.onConnect(MediaSession2.this, controllerInfo); - // Don't reject connection for the request from trusted app. - // Otherwise server will fail to retrieve session's information to dispatch - // media keys to. - if (controllerInfo.mAllowedCommands == null && !controllerInfo.isTrusted()) { - return; - } - if (controllerInfo.mAllowedCommands == null) { - // For trusted apps, send non-null allowed commands to keep - // connection. - controllerInfo.mAllowedCommands = - new Session2CommandGroup.Builder().build(); - } - if (DEBUG) { - Log.d(TAG, "Accepting connection: " + controllerInfo); - } - // If connection is accepted, notify the current state to the controller. - // It's needed because we cannot call synchronous calls between - // session/controller. - Bundle connectionResult = new Bundle(); - connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub); - connectionResult.putParcelable(KEY_ALLOWED_COMMANDS, - controllerInfo.mAllowedCommands); - connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive()); - connectionResult.putBundle(KEY_TOKEN_EXTRAS, mSessionToken.getExtras()); - - // Double check if session is still there, because close() can be called in - // another thread. - if (isClosed()) { - return; - } - controllerInfo.notifyConnected(connectionResult); - synchronized (mLock) { - if (mConnectedControllers.containsKey(controller)) { - Log.w(TAG, "Controller " + controllerInfo + " has sent connection" - + " request multiple times"); - } - mConnectedControllers.put(controller, controllerInfo); - } - mCallback.onPostConnect(MediaSession2.this, controllerInfo); - connected = true; - } finally { - if (!connected || isClosed()) { - if (DEBUG) { - Log.d(TAG, "Rejecting connection or notifying that session is closed" - + ", controllerInfo=" + controllerInfo); - } - synchronized (mLock) { - mConnectedControllers.remove(controller); - } - controllerInfo.notifyDisconnected(); - } - } - }); - } - - // Called by Session2Link.onDisconnect - void onDisconnect(@NonNull final Controller2Link controller, int seq) { - final ControllerInfo controllerInfo; - synchronized (mLock) { - controllerInfo = mConnectedControllers.remove(controller); - } - if (controllerInfo == null) { - return; - } - mCallbackExecutor.execute(() -> { - mCallback.onDisconnected(MediaSession2.this, controllerInfo); - }); - } - - // Called by Session2Link.onSessionCommand - void onSessionCommand(@NonNull final Controller2Link controller, final int seq, - final Session2Command command, final Bundle args, - @Nullable ResultReceiver resultReceiver) { - if (controller == null) { - return; - } - final ControllerInfo controllerInfo; - synchronized (mLock) { - controllerInfo = mConnectedControllers.get(controller); - } - if (controllerInfo == null) { - return; - } - - // TODO: check allowed commands. - synchronized (mLock) { - controllerInfo.addRequestedCommandSeqNumber(seq); - } - mCallbackExecutor.execute(() -> { - if (!controllerInfo.removeRequestedCommandSeqNumber(seq)) { - if (resultReceiver != null) { - resultReceiver.send(RESULT_INFO_SKIPPED, null); - } - return; - } - Session2Command.Result result = mCallback.onSessionCommand( - MediaSession2.this, controllerInfo, command, args); - if (resultReceiver != null) { - if (result == null) { - resultReceiver.send(RESULT_INFO_SKIPPED, null); - } else { - resultReceiver.send(result.getResultCode(), result.getResultData()); - } - } - }); - } - - // Called by Session2Link.onCancelCommand - void onCancelCommand(@NonNull final Controller2Link controller, final int seq) { - final ControllerInfo controllerInfo; - synchronized (mLock) { - controllerInfo = mConnectedControllers.get(controller); - } - if (controllerInfo == null) { - return; - } - controllerInfo.removeRequestedCommandSeqNumber(seq); - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Builder for {@link MediaSession2}. - * <p> - * Any incoming event from the {@link MediaController2} will be handled on the callback - * executor. If it's not set, {@link Context#getMainExecutor()} will be used by default. - */ - public static final class Builder { - private Context mContext; - private String mId; - private PendingIntent mSessionActivity; - private Executor mCallbackExecutor; - private SessionCallback mCallback; - private Bundle mExtras; - - /** - * Creates a builder for {@link MediaSession2}. - * - * @param context Context - * @throws IllegalArgumentException if context is {@code null}. - */ - public Builder(@NonNull Context context) { - if (context == null) { - throw new IllegalArgumentException("context shouldn't be null"); - } - mContext = context; - } - - /** - * Set an intent for launching UI for this Session. This can be used as a - * quick link to an ongoing media screen. The intent should be for an - * activity that may be started using {@link Context#startActivity(Intent)}. - * - * @param pi The intent to launch to show UI for this session. - * @return The Builder to allow chaining - */ - @NonNull - public Builder setSessionActivity(@Nullable PendingIntent pi) { - mSessionActivity = pi; - return this; - } - - /** - * Set ID of the session. If it's not set, an empty string will be used to create a session. - * <p> - * Use this if and only if your app supports multiple playback at the same time and also - * wants to provide external apps to have finer controls of them. - * - * @param id id of the session. Must be unique per package. - * @throws IllegalArgumentException if id is {@code null}. - * @return The Builder to allow chaining - */ - @NonNull - public Builder setId(@NonNull String id) { - if (id == null) { - throw new IllegalArgumentException("id shouldn't be null"); - } - mId = id; - return this; - } - - /** - * Set callback for the session and its executor. - * - * @param executor callback executor - * @param callback session callback. - * @return The Builder to allow chaining - */ - @NonNull - public Builder setSessionCallback(@NonNull Executor executor, - @NonNull SessionCallback callback) { - mCallbackExecutor = executor; - mCallback = callback; - return this; - } - - /** - * Set extras for the session token. If null or not set, {@link Session2Token#getExtras()} - * will return an empty {@link Bundle}. An {@link IllegalArgumentException} will be thrown - * if the bundle contains any non-framework Parcelable objects. - * - * @return The Builder to allow chaining - * @see Session2Token#getExtras() - */ - @NonNull - public Builder setExtras(@NonNull Bundle extras) { - if (extras == null) { - throw new NullPointerException("extras shouldn't be null"); - } - if (hasCustomParcelable(extras)) { - throw new IllegalArgumentException( - "extras shouldn't contain any custom parcelables"); - } - mExtras = new Bundle(extras); - return this; - } - - /** - * Build {@link MediaSession2}. - * - * @return a new session - * @throws IllegalStateException if the session with the same id is already exists for the - * package. - */ - @NonNull - public MediaSession2 build() { - if (mCallbackExecutor == null) { - mCallbackExecutor = mContext.getMainExecutor(); - } - if (mCallback == null) { - mCallback = new SessionCallback() {}; - } - if (mId == null) { - mId = ""; - } - if (mExtras == null) { - mExtras = Bundle.EMPTY; - } - MediaSession2 session2 = new MediaSession2(mContext, mId, mSessionActivity, - mCallbackExecutor, mCallback, mExtras); - - // Notify framework about the newly create session after the constructor is finished. - // Otherwise, framework may access the session before the initialization is finished. - try { - if (SdkLevel.isAtLeastS()) { - MediaCommunicationManager manager = - mContext.getSystemService(MediaCommunicationManager.class); - manager.notifySession2Created(session2.getToken()); - } else { - MediaSessionManager manager = - mContext.getSystemService(MediaSessionManager.class); - manager.notifySession2Created(session2.getToken()); - } - } catch (Exception e) { - session2.close(); - throw e; - } - - return session2; - } - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Information of a controller. - */ - public static final class ControllerInfo { - private final RemoteUserInfo mRemoteUserInfo; - private final boolean mIsTrusted; - private final Controller2Link mControllerBinder; - private final Bundle mConnectionHints; - private final Object mLock = new Object(); - //@GuardedBy("mLock") - private int mNextSeqNumber; - //@GuardedBy("mLock") - private ArrayMap<ResultReceiver, Integer> mPendingCommands; - //@GuardedBy("mLock") - private ArraySet<Integer> mRequestedCommandSeqNumbers; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - Session2CommandGroup mAllowedCommands; - - /** - * @param remoteUserInfo remote user info - * @param trusted {@code true} if trusted, {@code false} otherwise - * @param controllerBinder Controller2Link for the connected controller. - * @param connectionHints a session-specific argument sent from the controller for the - * connection. The contents of this bundle may affect the - * connection result. - */ - ControllerInfo(@NonNull RemoteUserInfo remoteUserInfo, boolean trusted, - @Nullable Controller2Link controllerBinder, @NonNull Bundle connectionHints) { - mRemoteUserInfo = remoteUserInfo; - mIsTrusted = trusted; - mControllerBinder = controllerBinder; - mConnectionHints = connectionHints; - mPendingCommands = new ArrayMap<>(); - mRequestedCommandSeqNumbers = new ArraySet<>(); - } - - /** - * @return remote user info of the controller. - */ - @NonNull - public RemoteUserInfo getRemoteUserInfo() { - return mRemoteUserInfo; - } - - /** - * @return package name of the controller. - */ - @NonNull - public String getPackageName() { - return mRemoteUserInfo.getPackageName(); - } - - /** - * @return uid of the controller. Can be a negative value if the uid cannot be obtained. - */ - public int getUid() { - return mRemoteUserInfo.getUid(); - } - - /** - * @return connection hints sent from controller. - */ - @NonNull - public Bundle getConnectionHints() { - return new Bundle(mConnectionHints); - } - - /** - * Return if the controller has granted {@code android.permission.MEDIA_CONTENT_CONTROL} or - * has a enabled notification listener so can be trusted to accept connection and incoming - * command request. - * - * @return {@code true} if the controller is trusted. - * @hide - */ - public boolean isTrusted() { - return mIsTrusted; - } - - @Override - public int hashCode() { - return Objects.hash(mControllerBinder, mRemoteUserInfo); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ControllerInfo)) return false; - if (this == obj) return true; - - ControllerInfo other = (ControllerInfo) obj; - if (mControllerBinder != null || other.mControllerBinder != null) { - return Objects.equals(mControllerBinder, other.mControllerBinder); - } - return mRemoteUserInfo.equals(other.mRemoteUserInfo); - } - - @Override - @NonNull - public String toString() { - return "ControllerInfo {pkg=" + mRemoteUserInfo.getPackageName() + ", uid=" - + mRemoteUserInfo.getUid() + ", allowedCommands=" + mAllowedCommands + "})"; - } - - void notifyConnected(Bundle connectionResult) { - if (mControllerBinder == null) return; - - try { - mControllerBinder.notifyConnected(getNextSeqNumber(), connectionResult); - } catch (RuntimeException e) { - // Controller may be died prematurely. - } - } - - void notifyDisconnected() { - if (mControllerBinder == null) return; - - try { - mControllerBinder.notifyDisconnected(getNextSeqNumber()); - } catch (RuntimeException e) { - // Controller may be died prematurely. - } - } - - void notifyPlaybackActiveChanged(boolean playbackActive) { - if (mControllerBinder == null) return; - - try { - mControllerBinder.notifyPlaybackActiveChanged(getNextSeqNumber(), playbackActive); - } catch (RuntimeException e) { - // Controller may be died prematurely. - } - } - - void sendSessionCommand(Session2Command command, Bundle args, - ResultReceiver resultReceiver) { - if (mControllerBinder == null) return; - - try { - int seq = getNextSeqNumber(); - synchronized (mLock) { - mPendingCommands.put(resultReceiver, seq); - } - mControllerBinder.sendSessionCommand(seq, command, args, resultReceiver); - } catch (RuntimeException e) { - // Controller may be died prematurely. - synchronized (mLock) { - mPendingCommands.remove(resultReceiver); - } - resultReceiver.send(RESULT_ERROR_UNKNOWN_ERROR, null); - } - } - - void cancelSessionCommand(@NonNull Object token) { - if (mControllerBinder == null) return; - Integer seq; - synchronized (mLock) { - seq = mPendingCommands.remove(token); - } - if (seq != null) { - mControllerBinder.cancelSessionCommand(seq); - } - } - - void receiveCommandResult(ResultReceiver resultReceiver) { - synchronized (mLock) { - mPendingCommands.remove(resultReceiver); - } - } - - void addRequestedCommandSeqNumber(int seq) { - synchronized (mLock) { - mRequestedCommandSeqNumbers.add(seq); - } - } - - boolean removeRequestedCommandSeqNumber(int seq) { - synchronized (mLock) { - return mRequestedCommandSeqNumbers.remove(seq); - } - } - - private int getNextSeqNumber() { - synchronized (mLock) { - return mNextSeqNumber++; - } - } - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Callback to be called for all incoming commands from {@link MediaController2}s. - */ - public abstract static class SessionCallback { - /** - * Called when a controller is created for this session. Return allowed commands for - * controller. By default it returns {@code null}. - * <p> - * You can reject the connection by returning {@code null}. In that case, controller - * receives {@link MediaController2.ControllerCallback#onDisconnected(MediaController2)} - * and cannot be used. - * <p> - * The controller hasn't connected yet in this method, so calls to the controller - * (e.g. {@link #sendSessionCommand}) would be ignored. Override {@link #onPostConnect} for - * the custom initialization for the controller instead. - * - * @param session the session for this event - * @param controller controller information. - * @return allowed commands. Can be {@code null} to reject connection. - */ - @Nullable - public Session2CommandGroup onConnect(@NonNull MediaSession2 session, - @NonNull ControllerInfo controller) { - return null; - } - - /** - * Called immediately after a controller is connected. This is a convenient method to add - * custom initialization between the session and a controller. - * <p> - * Note that calls to the controller (e.g. {@link #sendSessionCommand}) work here but don't - * work in {@link #onConnect} because the controller hasn't connected yet in - * {@link #onConnect}. - * - * @param session the session for this event - * @param controller controller information. - */ - public void onPostConnect(@NonNull MediaSession2 session, - @NonNull ControllerInfo controller) { - } - - /** - * Called when a controller is disconnected - * - * @param session the session for this event - * @param controller controller information - */ - public void onDisconnected(@NonNull MediaSession2 session, - @NonNull ControllerInfo controller) {} - - /** - * Called when a controller sent a session command. - * - * @param session the session for this event - * @param controller controller information - * @param command the session command - * @param args optional arguments - * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED - * will be sent to the session. - */ - @Nullable - public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session, - @NonNull ControllerInfo controller, @NonNull Session2Command command, - @Nullable Bundle args) { - return null; - } - - /** - * Called when the command sent to the controller is finished. - * - * @param session the session for this event - * @param controller controller information - * @param token the token got from {@link MediaSession2#sendSessionCommand} - * @param command the session command - * @param result the result of the session command - */ - public void onCommandResult(@NonNull MediaSession2 session, - @NonNull ControllerInfo controller, @NonNull Object token, - @NonNull Session2Command command, @NonNull Session2Command.Result result) {} - } - - abstract static class ForegroundServiceEventCallback { - public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {} - public void onSessionClosed(MediaSession2 session) {} - } -} diff --git a/apex/media/framework/java/android/media/MediaSession2Service.java b/apex/media/framework/java/android/media/MediaSession2Service.java deleted file mode 100644 index 9f80c433d580..000000000000 --- a/apex/media/framework/java/android/media/MediaSession2Service.java +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import static android.media.MediaConstants.KEY_CONNECTION_HINTS; -import static android.media.MediaConstants.KEY_PACKAGE_NAME; -import static android.media.MediaConstants.KEY_PID; - -import android.annotation.CallSuper; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.media.MediaSession2.ControllerInfo; -import android.media.session.MediaSessionManager; -import android.media.session.MediaSessionManager.RemoteUserInfo; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.util.ArrayMap; -import android.util.Log; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Service containing {@link MediaSession2}. - */ -public abstract class MediaSession2Service extends Service { - /** - * The {@link Intent} that must be declared as handled by the service. - */ - public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; - - private static final String TAG = "MediaSession2Service"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback = - new MediaSession2.ForegroundServiceEventCallback() { - @Override - public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { - MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive); - } - - @Override - public void onSessionClosed(MediaSession2 session) { - removeSession(session); - } - }; - - private final Object mLock = new Object(); - //@GuardedBy("mLock") - private NotificationManager mNotificationManager; - //@GuardedBy("mLock") - private MediaSessionManager mMediaSessionManager; - //@GuardedBy("mLock") - private Intent mStartSelfIntent; - //@GuardedBy("mLock") - private Map<String, MediaSession2> mSessions = new ArrayMap<>(); - //@GuardedBy("mLock") - private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>(); - //@GuardedBy("mLock") - private MediaSession2ServiceStub mStub; - - /** - * Called by the system when the service is first created. Do not call this method directly. - * <p> - * Override this method if you need your own initialization. Derived classes MUST call through - * to the super class's implementation of this method. - */ - @CallSuper - @Override - public void onCreate() { - super.onCreate(); - synchronized (mLock) { - mStub = new MediaSession2ServiceStub(this); - mStartSelfIntent = new Intent(this, this.getClass()); - mNotificationManager = - (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - mMediaSessionManager = - (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE); - } - } - - @CallSuper - @Override - @Nullable - public IBinder onBind(@NonNull Intent intent) { - if (SERVICE_INTERFACE.equals(intent.getAction())) { - synchronized (mLock) { - return mStub; - } - } - return null; - } - - /** - * Called by the system to notify that it is no longer used and is being removed. Do not call - * this method directly. - * <p> - * Override this method if you need your own clean up. Derived classes MUST call through - * to the super class's implementation of this method. - */ - @CallSuper - @Override - public void onDestroy() { - super.onDestroy(); - synchronized (mLock) { - List<MediaSession2> sessions = getSessions(); - for (MediaSession2 session : sessions) { - removeSession(session); - } - mSessions.clear(); - mNotifications.clear(); - } - mStub.close(); - } - - /** - * Called when a {@link MediaController2} is created with the this service's - * {@link Session2Token}. Return the session for telling the controller which session to - * connect. Return {@code null} to reject the connection from this controller. - * <p> - * Session returned here will be added to this service automatically. You don't need to call - * {@link #addSession(MediaSession2)} for that. - * <p> - * This method is always called on the main thread. - * - * @param controllerInfo information of the controller which is trying to connect. - * @return a {@link MediaSession2} instance for the controller to connect to, or {@code null} - * to reject connection - * @see MediaSession2.Builder - * @see #getSessions() - */ - @Nullable - public abstract MediaSession2 onGetSession(@NonNull ControllerInfo controllerInfo); - - /** - * Called to update the media notification when the playback state changes. - * <p> - * If playback is active and a notification is returned, the service uses it to become a - * foreground service. If playback is not active then the notification is still posted, but the - * service does not become a foreground service. - * <p> - * Apps must request the {@link android.Manifest.permission#FOREGROUND_SERVICE} permission - * in order to use this API. For apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU} - * or later, notifications will only be posted if the app has also been granted the - * {@link android.Manifest.permission#POST_NOTIFICATIONS} permission. - * - * @param session the session for which an updated media notification is required. - * @return the {@link MediaNotification}. Can be {@code null}. - */ - @Nullable - public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session); - - /** - * Adds a session to this service. - * <p> - * Added session will be removed automatically when it's closed, or removed when - * {@link #removeSession} is called. - * - * @param session a session to be added. - * @see #removeSession(MediaSession2) - */ - public final void addSession(@NonNull MediaSession2 session) { - if (session == null) { - throw new IllegalArgumentException("session shouldn't be null"); - } - if (session.isClosed()) { - throw new IllegalArgumentException("session is already closed"); - } - synchronized (mLock) { - MediaSession2 previousSession = mSessions.get(session.getId()); - if (previousSession != null) { - if (previousSession != session) { - Log.w(TAG, "Session ID should be unique, ID=" + session.getId() - + ", previous=" + previousSession + ", session=" + session); - } - return; - } - mSessions.put(session.getId(), session); - session.setForegroundServiceEventCallback(mForegroundServiceEventCallback); - } - } - - /** - * Removes a session from this service. - * - * @param session a session to be removed. - * @see #addSession(MediaSession2) - */ - public final void removeSession(@NonNull MediaSession2 session) { - if (session == null) { - throw new IllegalArgumentException("session shouldn't be null"); - } - MediaNotification notification; - synchronized (mLock) { - if (mSessions.get(session.getId()) != session) { - // Session isn't added or removed already. - return; - } - mSessions.remove(session.getId()); - notification = mNotifications.remove(session); - } - session.setForegroundServiceEventCallback(null); - if (notification != null) { - mNotificationManager.cancel(notification.getNotificationId()); - } - if (getSessions().isEmpty()) { - stopForeground(false); - } - } - - /** - * Gets the list of {@link MediaSession2}s that you've added to this service. - * - * @return sessions - */ - public final @NonNull List<MediaSession2> getSessions() { - List<MediaSession2> list = new ArrayList<>(); - synchronized (mLock) { - list.addAll(mSessions.values()); - } - return list; - } - - /** - * Returns the {@link MediaSessionManager}. - */ - @NonNull - MediaSessionManager getMediaSessionManager() { - synchronized (mLock) { - return mMediaSessionManager; - } - } - - /** - * Called by registered {@link MediaSession2.ForegroundServiceEventCallback} - * - * @param session session with change - * @param playbackActive {@code true} if playback is active. - */ - void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { - MediaNotification mediaNotification = onUpdateNotification(session); - if (mediaNotification == null) { - // The service implementation doesn't want to use the automatic start/stopForeground - // feature. - return; - } - synchronized (mLock) { - mNotifications.put(session, mediaNotification); - } - int id = mediaNotification.getNotificationId(); - Notification notification = mediaNotification.getNotification(); - if (!playbackActive) { - mNotificationManager.notify(id, notification); - return; - } - // playbackActive == true - startForegroundService(mStartSelfIntent); - startForeground(id, notification); - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service - * foreground service to keep playback running in the background. It's highly recommended to - * show media style notification here. - */ - public static class MediaNotification { - private final int mNotificationId; - private final Notification mNotification; - - /** - * Default constructor - * - * @param notificationId notification id to be used for - * {@link NotificationManager#notify(int, Notification)}. - * @param notification a notification to make session service run in the foreground. Media - * style notification is recommended here. - */ - public MediaNotification(int notificationId, @NonNull Notification notification) { - if (notification == null) { - throw new IllegalArgumentException("notification shouldn't be null"); - } - mNotificationId = notificationId; - mNotification = notification; - } - - /** - * Gets the id of the notification. - * - * @return the notification id - */ - public int getNotificationId() { - return mNotificationId; - } - - /** - * Gets the notification. - * - * @return the notification - */ - @NonNull - public Notification getNotification() { - return mNotification; - } - } - - private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub - implements AutoCloseable { - final WeakReference<MediaSession2Service> mService; - final Handler mHandler; - - MediaSession2ServiceStub(MediaSession2Service service) { - mService = new WeakReference<>(service); - mHandler = new Handler(service.getMainLooper()); - } - - @Override - public void connect(Controller2Link caller, int seq, Bundle connectionRequest) { - if (mService.get() == null) { - if (DEBUG) { - Log.d(TAG, "Service is already destroyed"); - } - return; - } - if (caller == null || connectionRequest == null) { - if (DEBUG) { - Log.d(TAG, "Ignoring calls with illegal arguments, caller=" + caller - + ", connectionRequest=" + connectionRequest); - } - return; - } - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - try { - mHandler.post(() -> { - boolean shouldNotifyDisconnected = true; - try { - final MediaSession2Service service = mService.get(); - if (service == null) { - if (DEBUG) { - Log.d(TAG, "Service isn't available"); - } - return; - } - - String callingPkg = connectionRequest.getString(KEY_PACKAGE_NAME); - // The Binder.getCallingPid() can be 0 for an oneway call from the - // remote process. If it's the case, use PID from the connectionRequest. - RemoteUserInfo remoteUserInfo = new RemoteUserInfo( - callingPkg, - pid == 0 ? connectionRequest.getInt(KEY_PID) : pid, - uid); - - Bundle connectionHints = connectionRequest.getBundle(KEY_CONNECTION_HINTS); - if (connectionHints == null) { - Log.w(TAG, "connectionHints shouldn't be null."); - connectionHints = Bundle.EMPTY; - } else if (MediaSession2.hasCustomParcelable(connectionHints)) { - Log.w(TAG, "connectionHints contain custom parcelable. Ignoring."); - connectionHints = Bundle.EMPTY; - } - - final ControllerInfo controllerInfo = new ControllerInfo( - remoteUserInfo, - service.getMediaSessionManager() - .isTrustedForMediaControl(remoteUserInfo), - caller, - connectionHints); - - if (DEBUG) { - Log.d(TAG, "Handling incoming connection request from the" - + " controller=" + controllerInfo); - } - - final MediaSession2 session; - session = service.onGetSession(controllerInfo); - - if (session == null) { - if (DEBUG) { - Log.d(TAG, "Rejecting incoming connection request from the" - + " controller=" + controllerInfo); - } - // Note: Trusted controllers also can be rejected according to the - // service implementation. - return; - } - service.addSession(session); - shouldNotifyDisconnected = false; - session.onConnect(caller, pid, uid, seq, connectionRequest); - } catch (Exception e) { - // Don't propagate exception in service to the controller. - Log.w(TAG, "Failed to add a session to session service", e); - } finally { - // Trick to call onDisconnected() in one place. - if (shouldNotifyDisconnected) { - if (DEBUG) { - Log.d(TAG, "Notifying the controller of its disconnection"); - } - try { - caller.notifyDisconnected(0); - } catch (RuntimeException e) { - // Controller may be died prematurely. - // Not an issue because we'll ignore it anyway. - } - } - } - }); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void close() { - mHandler.removeCallbacksAndMessages(null); - mService.clear(); - } - } -} diff --git a/apex/media/framework/java/android/media/MediaTranscodingManager.java b/apex/media/framework/java/android/media/MediaTranscodingManager.java deleted file mode 100644 index aff320401061..000000000000 --- a/apex/media/framework/java/android/media/MediaTranscodingManager.java +++ /dev/null @@ -1,1749 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.annotation.CallbackExecutor; -import android.annotation.IntDef; -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.net.Uri; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.os.ServiceSpecificException; -import android.system.Os; -import android.util.Log; - -import androidx.annotation.RequiresApi; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.modules.annotation.MinSdk; -import com.android.modules.utils.build.SdkLevel; - -import java.io.FileNotFoundException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - 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>. MediaTranscodingManager 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 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 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 VideoTranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through - {@link MediaTranscodingManager#enqueueRequest( - TranscodingRequest, Executor, OnTranscodingFinishedListener)} - TranscodeRequest are processed based on client process's priority and request priority. When a - transcode operation is completed the caller is notified via its - {@link OnTranscodingFinishedListener}. - In the meantime the caller may use the returned TranscodingSession object to cancel or check the - status of a specific transcode operation. - <p> - Here is an example where <code>Builder</code> is used to specify all parameters - - <pre class=prettyprint> - VideoTranscodingRequest request = - new VideoTranscodingRequest.Builder(srcUri, dstUri, videoFormat).build(); - }</pre> - @hide - */ -@MinSdk(Build.VERSION_CODES.S) -@RequiresApi(Build.VERSION_CODES.S) -@SystemApi -public final class MediaTranscodingManager { - private static final String TAG = "MediaTranscodingManager"; - - /** Maximum number of retry to connect to the service. */ - private static final int CONNECT_SERVICE_RETRY_COUNT = 100; - - /** Interval between trying to reconnect to the service. */ - private static final int INTERVAL_CONNECT_SERVICE_RETRY_MS = 40; - - /** Default bpp(bits-per-pixel) to use for calculating default bitrate. */ - private static final float BPP = 0.25f; - - /** - * 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. - */ - @FunctionalInterface - public interface OnTranscodingFinishedListener { - /** - * Called when the transcoding operation has finished. The receiver may use the - * TranscodingSession to check the result, i.e. whether the operation succeeded, was - * canceled or if an error occurred. - * - * @param session The TranscodingSession instance for the finished transcoding operation. - */ - void onTranscodingFinished(@NonNull TranscodingSession session); - } - - private final Context mContext; - private ContentResolver mContentResolver; - private final String mPackageName; - private final int mPid; - private final int mUid; - private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); - private final HashMap<Integer, TranscodingSession> mPendingTranscodingSessions = new HashMap(); - private final Object mLock = new Object(); - @GuardedBy("mLock") - @NonNull private ITranscodingClient mTranscodingClient = null; - private static MediaTranscodingManager sMediaTranscodingManager; - - private void handleTranscodingFinished(int sessionId, TranscodingResultParcel result) { - synchronized (mPendingTranscodingSessions) { - // Gets the session associated with the sessionId and removes it from - // mPendingTranscodingSessions. - final TranscodingSession session = mPendingTranscodingSessions.remove(sessionId); - - if (session == null) { - // This should not happen in reality. - Log.e(TAG, "Session " + sessionId + " is not in Pendingsessions"); - return; - } - - // Updates the session status and result. - session.updateStatusAndResult(TranscodingSession.STATUS_FINISHED, - TranscodingSession.RESULT_SUCCESS, - TranscodingSession.ERROR_NONE); - - // Notifies client the session is done. - if (session.mListener != null && session.mListenerExecutor != null) { - session.mListenerExecutor.execute( - () -> session.mListener.onTranscodingFinished(session)); - } - } - } - - private void handleTranscodingFailed(int sessionId, int errorCode) { - synchronized (mPendingTranscodingSessions) { - // Gets the session associated with the sessionId and removes it from - // mPendingTranscodingSessions. - final TranscodingSession session = mPendingTranscodingSessions.remove(sessionId); - - if (session == null) { - // This should not happen in reality. - Log.e(TAG, "Session " + sessionId + " is not in Pendingsessions"); - return; - } - - // Updates the session status and result. - session.updateStatusAndResult(TranscodingSession.STATUS_FINISHED, - TranscodingSession.RESULT_ERROR, errorCode); - - // Notifies client the session failed. - if (session.mListener != null && session.mListenerExecutor != null) { - session.mListenerExecutor.execute( - () -> session.mListener.onTranscodingFinished(session)); - } - } - } - - private void handleTranscodingProgressUpdate(int sessionId, int newProgress) { - synchronized (mPendingTranscodingSessions) { - // Gets the session associated with the sessionId. - final TranscodingSession session = mPendingTranscodingSessions.get(sessionId); - - if (session == null) { - // This should not happen in reality. - Log.e(TAG, "Session " + sessionId + " is not in Pendingsessions"); - return; - } - - // Updates the session progress. - session.updateProgress(newProgress); - - // Notifies client the progress update. - if (session.mProgressUpdateExecutor != null - && session.mProgressUpdateListener != null) { - session.mProgressUpdateExecutor.execute( - () -> session.mProgressUpdateListener.onProgressUpdate(session, - newProgress)); - } - } - } - - private IMediaTranscodingService getService(boolean retry) { - // Do not try to get the service on pre-S. The service is lazy-start and getting the - // service could block. - if (!SdkLevel.isAtLeastS()) { - return null; - } - - int retryCount = !retry ? 1 : CONNECT_SERVICE_RETRY_COUNT; - Log.i(TAG, "get service with retry " + retryCount); - for (int count = 1; count <= retryCount; count++) { - Log.d(TAG, "Trying to connect to service. Try count: " + count); - IMediaTranscodingService service = IMediaTranscodingService.Stub.asInterface( - MediaFrameworkInitializer - .getMediaServiceManager() - .getMediaTranscodingServiceRegisterer() - .get()); - if (service != null) { - return service; - } - try { - // Sleep a bit before retry. - Thread.sleep(INTERVAL_CONNECT_SERVICE_RETRY_MS); - } catch (InterruptedException ie) { - /* ignore */ - } - } - Log.w(TAG, "Failed to get service"); - return null; - } - - /* - * Handle client binder died event. - * Upon receiving a binder died event of the client, we will do the following: - * 1) For the session that is running, notify the client that the session is failed with - * error code, so client could choose to retry the session or not. - * TODO(hkuang): Add a new error code to signal service died error. - * 2) For the sessions that is still pending or paused, we will resubmit the session - * once we successfully reconnect to the service and register a new client. - * 3) When trying to connect to the service and register a new client. The service may need time - * to reboot or never boot up again. So we will retry for a number of times. If we still - * could not connect, we will notify client session failure for the pending and paused - * sessions. - */ - private void onClientDied() { - synchronized (mLock) { - mTranscodingClient = null; - } - - // Delegates the session notification and retry to the executor as it may take some time. - mExecutor.execute(() -> { - // List to track the sessions that we want to retry. - List<TranscodingSession> retrySessions = new ArrayList<TranscodingSession>(); - - // First notify the client of session failure for all the running sessions. - synchronized (mPendingTranscodingSessions) { - for (Map.Entry<Integer, TranscodingSession> entry : - mPendingTranscodingSessions.entrySet()) { - TranscodingSession session = entry.getValue(); - - if (session.getStatus() == TranscodingSession.STATUS_RUNNING) { - session.updateStatusAndResult(TranscodingSession.STATUS_FINISHED, - TranscodingSession.RESULT_ERROR, - TranscodingSession.ERROR_SERVICE_DIED); - - // Remove the session from pending sessions. - mPendingTranscodingSessions.remove(entry.getKey()); - - if (session.mListener != null && session.mListenerExecutor != null) { - Log.i(TAG, "Notify client session failed"); - session.mListenerExecutor.execute( - () -> session.mListener.onTranscodingFinished(session)); - } - } else if (session.getStatus() == TranscodingSession.STATUS_PENDING - || session.getStatus() == TranscodingSession.STATUS_PAUSED) { - // Add the session to retrySessions to handle them later. - retrySessions.add(session); - } - } - } - - // Try to register with the service once it boots up. - IMediaTranscodingService service = getService(true /*retry*/); - boolean haveTranscodingClient = false; - if (service != null) { - synchronized (mLock) { - mTranscodingClient = registerClient(service); - if (mTranscodingClient != null) { - haveTranscodingClient = true; - } - } - } - - for (TranscodingSession session : retrySessions) { - // Notify the session failure if we fails to connect to the service or fail - // to retry the session. - if (!haveTranscodingClient) { - // TODO(hkuang): Return correct error code to the client. - handleTranscodingFailed(session.getSessionId(), 0 /*unused */); - } - - try { - // Do not set hasRetried for retry initiated by MediaTranscodingManager. - session.retryInternal(false /*setHasRetried*/); - } catch (Exception re) { - // TODO(hkuang): Return correct error code to the client. - handleTranscodingFailed(session.getSessionId(), 0 /*unused */); - } - } - }); - } - - private void updateStatus(int sessionId, int status) { - synchronized (mPendingTranscodingSessions) { - final TranscodingSession session = mPendingTranscodingSessions.get(sessionId); - - if (session == null) { - // This should not happen in reality. - Log.e(TAG, "Session " + sessionId + " is not in Pendingsessions"); - return; - } - - // Updates the session status. - session.updateStatus(status); - } - } - - // Just forwards all the events to the event handler. - private ITranscodingClientCallback mTranscodingClientCallback = - new ITranscodingClientCallback.Stub() { - // TODO(hkuang): Add more unit test to test difference file open mode. - @Override - public ParcelFileDescriptor openFileDescriptor(String fileUri, String mode) - throws RemoteException { - if (!mode.equals("r") && !mode.equals("w") && !mode.equals("rw")) { - Log.e(TAG, "Unsupport mode: " + mode); - return null; - } - - Uri uri = Uri.parse(fileUri); - try { - AssetFileDescriptor afd = mContentResolver.openAssetFileDescriptor(uri, - mode); - if (afd != null) { - return afd.getParcelFileDescriptor(); - } - } catch (FileNotFoundException e) { - Log.w(TAG, "Cannot find content uri: " + uri, e); - } catch (SecurityException e) { - Log.w(TAG, "Cannot open content uri: " + uri, e); - } catch (Exception e) { - Log.w(TAG, "Unknown content uri: " + uri, e); - } - return null; - } - - @Override - public void onTranscodingStarted(int sessionId) throws RemoteException { - updateStatus(sessionId, TranscodingSession.STATUS_RUNNING); - } - - @Override - public void onTranscodingPaused(int sessionId) throws RemoteException { - updateStatus(sessionId, TranscodingSession.STATUS_PAUSED); - } - - @Override - public void onTranscodingResumed(int sessionId) throws RemoteException { - updateStatus(sessionId, TranscodingSession.STATUS_RUNNING); - } - - @Override - public void onTranscodingFinished(int sessionId, TranscodingResultParcel result) - throws RemoteException { - handleTranscodingFinished(sessionId, result); - } - - @Override - public void onTranscodingFailed(int sessionId, int errorCode) - throws RemoteException { - handleTranscodingFailed(sessionId, errorCode); - } - - @Override - public void onAwaitNumberOfSessionsChanged(int sessionId, int oldAwaitNumber, - int newAwaitNumber) throws RemoteException { - //TODO(hkuang): Implement this. - } - - @Override - public void onProgressUpdate(int sessionId, int newProgress) - throws RemoteException { - handleTranscodingProgressUpdate(sessionId, newProgress); - } - }; - - private ITranscodingClient registerClient(IMediaTranscodingService service) { - synchronized (mLock) { - try { - // Registers the client with MediaTranscoding service. - mTranscodingClient = service.registerClient( - mTranscodingClientCallback, - mPackageName, - mPackageName); - - if (mTranscodingClient != null) { - mTranscodingClient.asBinder().linkToDeath(() -> onClientDied(), /* flags */ 0); - } - } catch (Exception ex) { - Log.e(TAG, "Failed to register new client due to exception " + ex); - mTranscodingClient = null; - } - } - return mTranscodingClient; - } - - /** - * @hide - */ - public MediaTranscodingManager(@NonNull Context context) { - mContext = context; - mContentResolver = mContext.getContentResolver(); - mPackageName = mContext.getPackageName(); - mUid = Os.getuid(); - mPid = Os.getpid(); - } - - /** - * 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; - - /** Uri of the destination media file. */ - private @NonNull Uri mDestinationUri; - - /** FileDescriptor of the source media file. */ - private @Nullable ParcelFileDescriptor mSourceFileDescriptor; - - /** FileDescriptor of the destination media file. */ - private @Nullable ParcelFileDescriptor mDestinationFileDescriptor; - - /** - * The UID of the client that the TranscodingRequest is for. Only privileged caller could - * set this Uid as only they could do the transcoding on behalf of the client. - * -1 means not available. - */ - private int mClientUid = -1; - - /** - * The Pid of the client that the TranscodingRequest is for. Only privileged caller could - * set this Uid as only they could do the transcoding on behalf of the client. - * -1 means not available. - */ - private int mClientPid = -1; - - /** Type of the transcoding. */ - private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN; - - /** Priority of the transcoding. */ - private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN; - - /** - * 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. - * @hide - */ - private @Nullable MediaFormat mImageFormat = null; - - @VisibleForTesting - private TranscodingTestConfig mTestConfig = null; - - /** - * Prevent public constructor access. - */ - /* package private */ TranscodingRequest() { - } - - private TranscodingRequest(Builder b) { - mSourceUri = b.mSourceUri; - mSourceFileDescriptor = b.mSourceFileDescriptor; - mDestinationUri = b.mDestinationUri; - mDestinationFileDescriptor = b.mDestinationFileDescriptor; - mClientUid = b.mClientUid; - mClientPid = b.mClientPid; - mPriority = b.mPriority; - mType = b.mType; - mTestConfig = b.mTestConfig; - } - - /** - * Return the type of the transcoding. - * @hide - */ - @TranscodingType - public int getType() { - return mType; - } - - /** Return source uri of the transcoding. */ - @NonNull - public Uri getSourceUri() { - return mSourceUri; - } - - /** - * Return source file descriptor of the transcoding. - * This will be null if client has not provided it. - */ - @Nullable - public ParcelFileDescriptor getSourceFileDescriptor() { - return mSourceFileDescriptor; - } - - /** Return the UID of the client that this request is for. -1 means not available. */ - public int getClientUid() { - return mClientUid; - } - - /** Return the PID of the client that this request is for. -1 means not available. */ - public int getClientPid() { - return mClientPid; - } - - /** Return destination uri of the transcoding. */ - @NonNull - public Uri getDestinationUri() { - return mDestinationUri; - } - - /** - * Return destination file descriptor of the transcoding. - * This will be null if client has not provided it. - */ - @Nullable - public ParcelFileDescriptor getDestinationFileDescriptor() { - return mDestinationFileDescriptor; - } - - /** - * Return priority of the transcoding. - * @hide - */ - @TranscodingPriority - public int getPriority() { - return mPriority; - } - - /** - * Return TestConfig of the transcoding. - * @hide - */ - @Nullable - public TranscodingTestConfig getTestConfig() { - return mTestConfig; - } - - abstract void writeFormatToParcel(TranscodingRequestParcel parcel); - - /* Writes the TranscodingRequest to a parcel. */ - private TranscodingRequestParcel writeToParcel(@NonNull Context context) { - TranscodingRequestParcel parcel = new TranscodingRequestParcel(); - switch (mPriority) { - case PRIORITY_OFFLINE: - parcel.priority = TranscodingSessionPriority.kUnspecified; - break; - case PRIORITY_REALTIME: - case PRIORITY_UNKNOWN: - default: - parcel.priority = TranscodingSessionPriority.kNormal; - break; - } - parcel.transcodingType = mType; - parcel.sourceFilePath = mSourceUri.toString(); - parcel.sourceFd = mSourceFileDescriptor; - parcel.destinationFilePath = mDestinationUri.toString(); - parcel.destinationFd = mDestinationFileDescriptor; - parcel.clientUid = mClientUid; - parcel.clientPid = mClientPid; - if (mClientUid < 0) { - parcel.clientPackageName = context.getPackageName(); - } else { - String packageName = context.getPackageManager().getNameForUid(mClientUid); - // PackageName is optional as some uid does not have package name. Set to - // "Unavailable" string in this case. - if (packageName == null) { - Log.w(TAG, "Failed to find package for uid: " + mClientUid); - packageName = "Unavailable"; - } - parcel.clientPackageName = packageName; - } - writeFormatToParcel(parcel); - if (mTestConfig != null) { - parcel.isForTesting = true; - parcel.testConfig = mTestConfig; - } - return parcel; - } - - /** - * Builder to build a {@link TranscodingRequest} object. - * - * @param <T> The subclass to be built. - */ - abstract static class Builder<T extends Builder<T>> { - private @NonNull Uri mSourceUri; - private @NonNull Uri mDestinationUri; - private @Nullable ParcelFileDescriptor mSourceFileDescriptor = null; - private @Nullable ParcelFileDescriptor mDestinationFileDescriptor = null; - private int mClientUid = -1; - private int mClientPid = -1; - private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN; - private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN; - private TranscodingTestConfig mTestConfig; - - abstract T self(); - - /** - * 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. - * @param destinationUri Content uri for the destination media file. - * - */ - 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; - - 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 #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 T setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) { - if (fileDescriptor == null || fileDescriptor.getFd() < 0) { - throw new IllegalArgumentException( - "Invalid source descriptor."); - } - mSourceFileDescriptor = fileDescriptor; - 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 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. - * @return The same builder instance. - * @throws IllegalArgumentException if fileDescriptor is invalid. - */ - @NonNull - public T setDestinationFileDescriptor( - @NonNull ParcelFileDescriptor fileDescriptor) { - if (fileDescriptor == null || fileDescriptor.getFd() < 0) { - throw new IllegalArgumentException( - "Invalid destination descriptor."); - } - mDestinationFileDescriptor = fileDescriptor; - 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. - */ - @NonNull - public T setClientUid(int uid) { - if (uid < 0) { - throw new IllegalArgumentException("Invalid Uid"); - } - mClientUid = uid; - return self(); - } - - /** - * 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. - */ - @NonNull - public T setClientPid(int pid) { - if (pid < 0) { - throw new IllegalArgumentException("Invalid pid"); - } - mClientPid = pid; - return self(); - } - - /** - * Specifies the priority of the transcoding. - * - * @param priority Must be one of the {@code PRIORITY_*} - * @return The same builder instance. - * @throws IllegalArgumentException if flags is invalid. - * @hide - */ - @NonNull - public T setPriority(@TranscodingPriority int priority) { - if (priority != PRIORITY_OFFLINE && priority != PRIORITY_REALTIME) { - throw new IllegalArgumentException("Invalid priority: " + priority); - } - mPriority = priority; - return self(); - } - - /** - * Sets the delay in processing this request. - * @param config test config. - * @return The same builder instance. - * @hide - */ - @VisibleForTesting - @NonNull - public T setTestConfig(@NonNull TranscodingTestConfig config) { - mTestConfig = config; - return self(); - } - } - - /** - * Abstract base class for all the format resolvers. - */ - abstract static class MediaFormatResolver { - private @NonNull ApplicationMediaCapabilities mClientCaps; - - /** - * Prevents public constructor access. - */ - /* package private */ MediaFormatResolver() { - } - - /** - * Constructs MediaFormatResolver object. - * - * @param clientCaps An ApplicationMediaCapabilities object containing the client's - * capabilities. - */ - MediaFormatResolver(@NonNull ApplicationMediaCapabilities clientCaps) { - if (clientCaps == null) { - throw new IllegalArgumentException("Client capabilities must not be null"); - } - mClientCaps = clientCaps; - } - - /** - * Returns the client capabilities. - */ - @NonNull - /* package */ ApplicationMediaCapabilities getClientCapabilities() { - return mClientCaps; - } - - abstract boolean shouldTranscode(); - } - - /** - * VideoFormatResolver for deciding if video transcoding is needed, and if so, the track - * formats to use. - */ - public static class VideoFormatResolver extends MediaFormatResolver { - private static final int BIT_RATE = 20000000; // 20Mbps - - private MediaFormat mSrcVideoFormatHint; - private MediaFormat mSrcAudioFormatHint; - - /** - * Constructs a new VideoFormatResolver object. - * - * @param clientCaps An ApplicationMediaCapabilities object containing the client's - * capabilities. - * @param srcVideoFormatHint A MediaFormat object containing information about the - * source's video track format that could affect the - * transcoding decision. Such information could include video - * codec types, color spaces, whether special format info (eg. - * slow-motion markers) are present, etc.. If a particular - * information is not present, it will not be used to make the - * decision. - */ - public VideoFormatResolver(@NonNull ApplicationMediaCapabilities clientCaps, - @NonNull MediaFormat srcVideoFormatHint) { - super(clientCaps); - mSrcVideoFormatHint = srcVideoFormatHint; - } - - /** - * Constructs a new VideoFormatResolver object. - * - * @param clientCaps An ApplicationMediaCapabilities object containing the client's - * capabilities. - * @param srcVideoFormatHint A MediaFormat object containing information about the - * source's video track format that could affect the - * transcoding decision. Such information could include video - * codec types, color spaces, whether special format info (eg. - * slow-motion markers) are present, etc.. If a particular - * information is not present, it will not be used to make the - * decision. - * @param srcAudioFormatHint A MediaFormat object containing information about the - * source's audio track format that could affect the - * transcoding decision. - * @hide - */ - VideoFormatResolver(@NonNull ApplicationMediaCapabilities clientCaps, - @NonNull MediaFormat srcVideoFormatHint, - @NonNull MediaFormat srcAudioFormatHint) { - super(clientCaps); - mSrcVideoFormatHint = srcVideoFormatHint; - mSrcAudioFormatHint = srcAudioFormatHint; - } - - /** - * Returns whether the source content should be transcoded. - * - * @return true if the source should be transcoded. - */ - public boolean shouldTranscode() { - boolean supportHevc = getClientCapabilities().isVideoMimeTypeSupported( - MediaFormat.MIMETYPE_VIDEO_HEVC); - if (!supportHevc && MediaFormat.MIMETYPE_VIDEO_HEVC.equals( - mSrcVideoFormatHint.getString(MediaFormat.KEY_MIME))) { - return true; - } - // TODO: add more checks as needed below. - return false; - } - - /** - * Retrieves the video track format to be used on - * {@link VideoTranscodingRequest.Builder#setVideoTrackFormat(MediaFormat)} for this - * configuration. - * - * @return the video track format to be used if transcoding should be performed, - * and null otherwise. - * @throws IllegalArgumentException if the hinted source video format contains invalid - * parameters. - */ - @Nullable - public MediaFormat resolveVideoFormat() { - if (!shouldTranscode()) { - return null; - } - - MediaFormat videoTrackFormat = new MediaFormat(mSrcVideoFormatHint); - videoTrackFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); - - int width = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_WIDTH, -1); - int height = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_HEIGHT, -1); - if (width <= 0 || height <= 0) { - throw new IllegalArgumentException( - "Source Width and height must be larger than 0"); - } - - float frameRate = - mSrcVideoFormatHint.getNumber(MediaFormat.KEY_FRAME_RATE, 30.0) - .floatValue(); - if (frameRate <= 0) { - throw new IllegalArgumentException( - "frameRate must be larger than 0"); - } - - int bitrate = getAVCBitrate(width, height, frameRate); - videoTrackFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); - return videoTrackFormat; - } - - /** - * Generate a default bitrate with the fixed bpp(bits-per-pixel) 0.25. - * This maps to: - * 1080P@30fps -> 16Mbps - * 1080P@60fps-> 32Mbps - * 4K@30fps -> 62Mbps - */ - private static int getDefaultBitrate(int width, int height, float frameRate) { - return (int) (width * height * frameRate * BPP); - } - - /** - * Query the bitrate from CamcorderProfile. If there are two profiles that match the - * width/height/framerate, we will use the higher one to get better quality. - * Return default bitrate if could not find any match profile. - */ - private static int getAVCBitrate(int width, int height, float frameRate) { - int bitrate = -1; - int[] cameraIds = {0, 1}; - - // Profiles ordered in decreasing order of preference. - int[] preferQualities = { - CamcorderProfile.QUALITY_2160P, - CamcorderProfile.QUALITY_1080P, - CamcorderProfile.QUALITY_720P, - CamcorderProfile.QUALITY_480P, - CamcorderProfile.QUALITY_LOW, - }; - - for (int cameraId : cameraIds) { - for (int quality : preferQualities) { - // Check if camera id has profile for the quality level. - if (!CamcorderProfile.hasProfile(cameraId, quality)) { - continue; - } - CamcorderProfile profile = CamcorderProfile.get(cameraId, quality); - // Check the width/height/framerate/codec, also consider portrait case. - if (((width == profile.videoFrameWidth - && height == profile.videoFrameHeight) - || (height == profile.videoFrameWidth - && width == profile.videoFrameHeight)) - && (int) frameRate == profile.videoFrameRate - && profile.videoCodec == MediaRecorder.VideoEncoder.H264) { - if (bitrate < profile.videoBitRate) { - bitrate = profile.videoBitRate; - } - break; - } - } - } - - if (bitrate == -1) { - Log.w(TAG, "Failed to find CamcorderProfile for w: " + width + "h: " + height - + " fps: " - + frameRate); - bitrate = getDefaultBitrate(width, height, frameRate); - } - Log.d(TAG, "Using bitrate " + bitrate + " for " + width + " " + height + " " - + frameRate); - return bitrate; - } - - /** - * Retrieves the audio track format to be used for transcoding. - * - * @return the audio track format to be used if transcoding should be performed, and - * null otherwise. - * @hide - */ - @Nullable - public MediaFormat resolveAudioFormat() { - if (!shouldTranscode()) { - return null; - } - // Audio transcoding is not supported yet, always return null. - return null; - } - } - } - - /** - * 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(@NonNull ParcelFileDescriptor fd) { - super.setSourceFileDescriptor(fd); - return self(); - } - - @Override - @NonNull - public Builder setDestinationFileDescriptor(@NonNull 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. - */ - public static final class TranscodingSession { - /** The session is enqueued but not yet running. */ - public static final int STATUS_PENDING = 1; - /** The session is currently running. */ - public static final int STATUS_RUNNING = 2; - /** The session is finished. */ - public static final int STATUS_FINISHED = 3; - /** The session is paused. */ - public static final int STATUS_PAUSED = 4; - - /** @hide */ - @IntDef(prefix = { "STATUS_" }, value = { - STATUS_PENDING, - STATUS_RUNNING, - STATUS_FINISHED, - STATUS_PAUSED, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Status {} - - /** The session does not have a result yet. */ - public static final int RESULT_NONE = 1; - /** The session completed successfully. */ - public static final int RESULT_SUCCESS = 2; - /** The session encountered an error while running. */ - public static final int RESULT_ERROR = 3; - /** The session was canceled by the caller. */ - public static final int RESULT_CANCELED = 4; - - /** @hide */ - @IntDef(prefix = { "RESULT_" }, value = { - RESULT_NONE, - RESULT_SUCCESS, - RESULT_ERROR, - RESULT_CANCELED, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Result {} - - - // The error code exposed here should be in sync with: - // frameworks/av/media/libmediatranscoding/aidl/android/media/TranscodingErrorCode.aidl - /** @hide */ - @IntDef(prefix = { "TRANSCODING_SESSION_ERROR_" }, value = { - ERROR_NONE, - ERROR_DROPPED_BY_SERVICE, - ERROR_SERVICE_DIED}) - @Retention(RetentionPolicy.SOURCE) - public @interface TranscodingSessionErrorCode{} - /** - * Constant indicating that no error occurred. - */ - public static final int ERROR_NONE = 0; - - /** - * Constant indicating that the session is dropped by Transcoding service due to hitting - * the limit, e.g. too many back to back transcoding happen in a short time frame. - */ - public static final int ERROR_DROPPED_BY_SERVICE = 1; - - /** - * Constant indicating the backing transcoding service is died. Client should enqueue the - * the request again. - */ - public static final int ERROR_SERVICE_DIED = 2; - - /** Listener that gets notified when the progress changes. */ - @FunctionalInterface - public interface OnProgressUpdateListener { - /** - * Called when the progress changes. The progress is in percentage between 0 and 1, - * where 0 means the session has not yet started and 100 means that it has finished. - * - * @param session The session associated with the progress. - * @param progress The new progress ranging from 0 ~ 100 inclusive. - */ - void onProgressUpdate(@NonNull TranscodingSession session, - @IntRange(from = 0, to = 100) int progress); - } - - private final MediaTranscodingManager mManager; - private Executor mListenerExecutor; - private OnTranscodingFinishedListener mListener; - private int mSessionId = -1; - // Lock for internal state. - private final Object mLock = new Object(); - @GuardedBy("mLock") - private Executor mProgressUpdateExecutor = null; - @GuardedBy("mLock") - private OnProgressUpdateListener mProgressUpdateListener = null; - @GuardedBy("mLock") - private int mProgress = 0; - @GuardedBy("mLock") - private int mProgressUpdateInterval = 0; - @GuardedBy("mLock") - private @Status int mStatus = STATUS_PENDING; - @GuardedBy("mLock") - private @Result int mResult = RESULT_NONE; - @GuardedBy("mLock") - private @TranscodingSessionErrorCode int mErrorCode = ERROR_NONE; - @GuardedBy("mLock") - private boolean mHasRetried = false; - // The original request that associated with this session. - private final TranscodingRequest mRequest; - - private TranscodingSession( - @NonNull MediaTranscodingManager manager, - @NonNull TranscodingRequest request, - @NonNull TranscodingSessionParcel parcel, - @NonNull @CallbackExecutor Executor executor, - @NonNull OnTranscodingFinishedListener listener) { - Objects.requireNonNull(manager, "manager must not be null"); - Objects.requireNonNull(parcel, "parcel must not be null"); - Objects.requireNonNull(executor, "listenerExecutor must not be null"); - Objects.requireNonNull(listener, "listener must not be null"); - mManager = manager; - mSessionId = parcel.sessionId; - mListenerExecutor = executor; - mListener = listener; - mRequest = request; - } - - /** - * Set a progress listener. - * @param executor The executor on which listener will be invoked. - * @param listener The progress listener. - */ - public void setOnProgressUpdateListener( - @NonNull @CallbackExecutor Executor executor, - @Nullable OnProgressUpdateListener listener) { - synchronized (mLock) { - Objects.requireNonNull(executor, "listenerExecutor must not be null"); - Objects.requireNonNull(listener, "listener must not be null"); - mProgressUpdateExecutor = executor; - mProgressUpdateListener = listener; - } - } - - private void updateStatusAndResult(@Status int sessionStatus, - @Result int sessionResult, @TranscodingSessionErrorCode int errorCode) { - synchronized (mLock) { - mStatus = sessionStatus; - mResult = sessionResult; - mErrorCode = errorCode; - } - } - - /** - * Retrieve the error code associated with the RESULT_ERROR. - */ - public @TranscodingSessionErrorCode int getErrorCode() { - synchronized (mLock) { - return mErrorCode; - } - } - - /** - * Resubmit the transcoding session to the service. - * Note that only the session that fails or gets cancelled could be retried and each session - * could be retried only once. After that, Client need to enqueue a new request if they want - * to try again. - * - * @return true if successfully resubmit the job to service. False otherwise. - * @throws UnsupportedOperationException if the retry could not be fulfilled. - * @hide - */ - public boolean retry() { - return retryInternal(true /*setHasRetried*/); - } - - // TODO(hkuang): Add more test for it. - private boolean retryInternal(boolean setHasRetried) { - synchronized (mLock) { - if (mStatus == STATUS_PENDING || mStatus == STATUS_RUNNING) { - throw new UnsupportedOperationException( - "Failed to retry as session is in processing"); - } - - if (mHasRetried) { - throw new UnsupportedOperationException("Session has been retried already"); - } - - // Get the client interface. - ITranscodingClient client = mManager.getTranscodingClient(); - if (client == null) { - Log.e(TAG, "Service rebooting. Try again later"); - return false; - } - - synchronized (mManager.mPendingTranscodingSessions) { - try { - // Submits the request to MediaTranscoding service. - TranscodingSessionParcel sessionParcel = new TranscodingSessionParcel(); - if (!client.submitRequest(mRequest.writeToParcel(mManager.mContext), - sessionParcel)) { - mHasRetried = true; - throw new UnsupportedOperationException("Failed to enqueue request"); - } - - // Replace the old session id wit the new one. - mSessionId = sessionParcel.sessionId; - // Adds the new session back into pending sessions. - mManager.mPendingTranscodingSessions.put(mSessionId, this); - } catch (RemoteException re) { - return false; - } - mStatus = STATUS_PENDING; - mHasRetried = setHasRetried ? true : false; - } - } - return true; - } - - /** - * Cancels the transcoding session and notify the listener. - * If the session happened to finish before being canceled this call is effectively a no-op - * and will not update the result in that case. - */ - public void cancel() { - synchronized (mLock) { - // Check if the session is finished already. - if (mStatus != STATUS_FINISHED) { - try { - ITranscodingClient client = mManager.getTranscodingClient(); - // The client may be gone. - if (client != null) { - client.cancelSession(mSessionId); - } - } catch (RemoteException re) { - //TODO(hkuang): Find out what to do if failing to cancel the session. - Log.e(TAG, "Failed to cancel the session due to exception: " + re); - } - mStatus = STATUS_FINISHED; - mResult = RESULT_CANCELED; - - // Notifies client the session is canceled. - mListenerExecutor.execute(() -> mListener.onTranscodingFinished(this)); - } - } - } - - /** - * Gets the progress of the transcoding session. The progress is between 0 and 100, where 0 - * means that the session has not yet started and 100 means that it is finished. For the - * cancelled session, the progress will be the last updated progress before it is cancelled. - * @return The progress. - */ - @IntRange(from = 0, to = 100) - public int getProgress() { - synchronized (mLock) { - return mProgress; - } - } - - /** - * Gets the status of the transcoding session. - * @return The status. - */ - public @Status int getStatus() { - synchronized (mLock) { - return mStatus; - } - } - - /** - * Adds a client uid that is also waiting for this transcoding session. - * <p> - * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could add the - * uid. 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 the additional client uid to be added. - * @return true if successfully added, false otherwise. - */ - public boolean addClientUid(int uid) { - if (uid < 0) { - throw new IllegalArgumentException("Invalid Uid"); - } - - // Get the client interface. - ITranscodingClient client = mManager.getTranscodingClient(); - if (client == null) { - Log.e(TAG, "Service is dead..."); - return false; - } - - try { - if (!client.addClientUid(mSessionId, uid)) { - Log.e(TAG, "Failed to add client uid"); - return false; - } - } catch (Exception ex) { - Log.e(TAG, "Failed to get client uids due to " + ex); - return false; - } - return true; - } - - /** - * Query all the client that waiting for this transcoding session - * @return a list containing all the client uids. - */ - @NonNull - public List<Integer> getClientUids() { - List<Integer> uidList = new ArrayList<Integer>(); - - // Get the client interface. - ITranscodingClient client = mManager.getTranscodingClient(); - if (client == null) { - Log.e(TAG, "Service is dead..."); - return uidList; - } - - try { - int[] clientUids = client.getClientUids(mSessionId); - for (int i : clientUids) { - uidList.add(i); - } - } catch (Exception ex) { - Log.e(TAG, "Failed to get client uids due to " + ex); - } - - return uidList; - } - - /** - * Gets sessionId of the transcoding session. - * @return session id. - */ - public int getSessionId() { - return mSessionId; - } - - /** - * Gets the result of the transcoding session. - * @return The result. - */ - public @Result int getResult() { - synchronized (mLock) { - return mResult; - } - } - - @Override - public String toString() { - String result; - String status; - - switch (mResult) { - case RESULT_NONE: - result = "RESULT_NONE"; - break; - case RESULT_SUCCESS: - result = "RESULT_SUCCESS"; - break; - case RESULT_ERROR: - result = "RESULT_ERROR(" + mErrorCode + ")"; - break; - case RESULT_CANCELED: - result = "RESULT_CANCELED"; - break; - default: - result = String.valueOf(mResult); - break; - } - - switch (mStatus) { - case STATUS_PENDING: - status = "STATUS_PENDING"; - break; - case STATUS_PAUSED: - status = "STATUS_PAUSED"; - break; - case STATUS_RUNNING: - status = "STATUS_RUNNING"; - break; - case STATUS_FINISHED: - status = "STATUS_FINISHED"; - break; - default: - status = String.valueOf(mStatus); - break; - } - return String.format(" session: {id: %d, status: %s, result: %s, progress: %d}", - mSessionId, status, result, mProgress); - } - - private void updateProgress(int newProgress) { - synchronized (mLock) { - mProgress = newProgress; - } - } - - private void updateStatus(int newStatus) { - synchronized (mLock) { - mStatus = newStatus; - } - } - } - - private ITranscodingClient getTranscodingClient() { - synchronized (mLock) { - return mTranscodingClient; - } - } - - /** - * Enqueues a TranscodingRequest for execution. - * <p> Upon successfully accepting the request, MediaTranscodingManager will return a - * {@link TranscodingSession} to the client. Client should use {@link TranscodingSession} to - * track the progress and get the result. - * <p> MediaTranscodingManager will return null if fails to accept the request due to service - * rebooting. Client could retry again after receiving null. - * - * @param transcodingRequest The TranscodingRequest to enqueue. - * @param listenerExecutor Executor on which the listener is notified. - * @param listener Listener to get notified when the transcoding session is finished. - * @return A TranscodingSession for this operation. - * @throws UnsupportedOperationException if the request could not be fulfilled. - */ - @Nullable - public TranscodingSession enqueueRequest( - @NonNull TranscodingRequest transcodingRequest, - @NonNull @CallbackExecutor Executor listenerExecutor, - @NonNull OnTranscodingFinishedListener listener) { - Log.i(TAG, "enqueueRequest called."); - Objects.requireNonNull(transcodingRequest, "transcodingRequest must not be null"); - Objects.requireNonNull(listenerExecutor, "listenerExecutor must not be null"); - Objects.requireNonNull(listener, "listener must not be null"); - - // Converts the request to TranscodingRequestParcel. - TranscodingRequestParcel requestParcel = transcodingRequest.writeToParcel(mContext); - - Log.i(TAG, "Getting transcoding request " + transcodingRequest.getSourceUri()); - - // Submits the request to MediaTranscoding service. - try { - TranscodingSessionParcel sessionParcel = new TranscodingSessionParcel(); - // Synchronizes the access to mPendingTranscodingSessions to make sure the session Id is - // inserted in the mPendingTranscodingSessions in the callback handler. - synchronized (mPendingTranscodingSessions) { - synchronized (mLock) { - if (mTranscodingClient == null) { - // Try to register with the service again. - IMediaTranscodingService service = getService(false /*retry*/); - if (service == null) { - Log.w(TAG, "Service rebooting. Try again later"); - return null; - } - mTranscodingClient = registerClient(service); - // If still fails, throws an exception to tell client to try later. - if (mTranscodingClient == null) { - Log.w(TAG, "Service rebooting. Try again later"); - return null; - } - } - - if (!mTranscodingClient.submitRequest(requestParcel, sessionParcel)) { - throw new UnsupportedOperationException("Failed to enqueue request"); - } - } - - // Wraps the TranscodingSessionParcel into a TranscodingSession and returns it to - // client for tracking. - TranscodingSession session = new TranscodingSession(this, transcodingRequest, - sessionParcel, - listenerExecutor, - listener); - - // Adds the new session into pending sessions. - mPendingTranscodingSessions.put(session.getSessionId(), session); - return session; - } - } catch (RemoteException ex) { - Log.w(TAG, "Service rebooting. Try again later"); - return null; - } catch (ServiceSpecificException ex) { - throw new UnsupportedOperationException( - "Failed to submit request to Transcoding service. Error: " + ex); - } - } -} diff --git a/apex/media/framework/java/android/media/ProxyDataSourceCallback.java b/apex/media/framework/java/android/media/ProxyDataSourceCallback.java deleted file mode 100644 index 14d3ce87f03d..000000000000 --- a/apex/media/framework/java/android/media/ProxyDataSourceCallback.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.os.ParcelFileDescriptor; -import android.system.ErrnoException; -import android.system.Os; -import android.system.OsConstants; -import android.util.Log; - -import java.io.FileDescriptor; -import java.io.IOException; - -/** - * A DataSourceCallback that is backed by a ParcelFileDescriptor. - */ -class ProxyDataSourceCallback extends DataSourceCallback { - private static final String TAG = "TestDataSourceCallback"; - - ParcelFileDescriptor mPFD; - FileDescriptor mFD; - - ProxyDataSourceCallback(ParcelFileDescriptor pfd) throws IOException { - mPFD = pfd.dup(); - mFD = mPFD.getFileDescriptor(); - } - - @Override - public synchronized int readAt(long position, byte[] buffer, int offset, int size) - throws IOException { - try { - Os.lseek(mFD, position, OsConstants.SEEK_SET); - int ret = Os.read(mFD, buffer, offset, size); - return (ret == 0) ? END_OF_STREAM : ret; - } catch (ErrnoException e) { - throw new IOException(e); - } - } - - @Override - public synchronized long getSize() throws IOException { - return mPFD.getStatSize(); - } - - @Override - public synchronized void close() { - try { - mPFD.close(); - } catch (IOException e) { - Log.e(TAG, "Failed to close the PFD.", e); - } - } -} - diff --git a/apex/media/framework/java/android/media/RoutingDelegate.java b/apex/media/framework/java/android/media/RoutingDelegate.java deleted file mode 100644 index 23598130f391..000000000000 --- a/apex/media/framework/java/android/media/RoutingDelegate.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.os.Handler; - -class RoutingDelegate implements AudioRouting.OnRoutingChangedListener { - private AudioRouting mAudioRouting; - private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener; - private Handler mHandler; - - RoutingDelegate(final AudioRouting audioRouting, - final AudioRouting.OnRoutingChangedListener listener, - Handler handler) { - mAudioRouting = audioRouting; - mOnRoutingChangedListener = listener; - mHandler = handler; - } - - public AudioRouting.OnRoutingChangedListener getListener() { - return mOnRoutingChangedListener; - } - - public Handler getHandler() { - return mHandler; - } - - @Override - public void onRoutingChanged(AudioRouting router) { - if (mOnRoutingChangedListener != null) { - mOnRoutingChangedListener.onRoutingChanged(mAudioRouting); - } - } -} diff --git a/apex/media/framework/java/android/media/Session2Command.java b/apex/media/framework/java/android/media/Session2Command.java deleted file mode 100644 index 7e71591d05fb..000000000000 --- a/apex/media/framework/java/android/media/Session2Command.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import java.util.Objects; - -/** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Define a command that a {@link MediaController2} can send to a {@link MediaSession2}. - * <p> - * If {@link #getCommandCode()} isn't {@link #COMMAND_CODE_CUSTOM}), it's predefined command. - * If {@link #getCommandCode()} is {@link #COMMAND_CODE_CUSTOM}), it's custom command and - * {@link #getCustomAction()} shouldn't be {@code null}. - * <p> - * Refer to the <a href="{@docRoot}reference/androidx/media2/session/SessionCommand.html"> - * AndroidX SessionCommand</a> class for the list of valid commands. - */ -public final class Session2Command implements Parcelable { - /** - * Command code for the custom command which can be defined by string action in the - * {@link Session2Command}. - */ - public static final int COMMAND_CODE_CUSTOM = 0; - - public static final @android.annotation.NonNull Parcelable.Creator<Session2Command> CREATOR = - new Parcelable.Creator<Session2Command>() { - @Override - public Session2Command createFromParcel(Parcel in) { - return new Session2Command(in); - } - - @Override - public Session2Command[] newArray(int size) { - return new Session2Command[size]; - } - }; - - private final int mCommandCode; - // Nonnull if it's custom command - private final String mCustomAction; - private final Bundle mCustomExtras; - - /** - * Constructor for creating a command predefined in AndroidX media2. - * - * @param commandCode A command code for a command predefined in AndroidX media2. - */ - public Session2Command(int commandCode) { - if (commandCode == COMMAND_CODE_CUSTOM) { - throw new IllegalArgumentException("commandCode shouldn't be COMMAND_CODE_CUSTOM"); - } - mCommandCode = commandCode; - mCustomAction = null; - mCustomExtras = null; - } - - /** - * Constructor for creating a custom command. - * - * @param action The action of this custom command. - * @param extras An extra bundle for this custom command. - */ - public Session2Command(@NonNull String action, @Nullable Bundle extras) { - if (action == null) { - throw new IllegalArgumentException("action shouldn't be null"); - } - mCommandCode = COMMAND_CODE_CUSTOM; - mCustomAction = action; - mCustomExtras = extras; - } - - /** - * Used by parcelable creator. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - Session2Command(Parcel in) { - mCommandCode = in.readInt(); - mCustomAction = in.readString(); - mCustomExtras = in.readBundle(); - } - - /** - * Gets the command code of a predefined command. - * This will return {@link #COMMAND_CODE_CUSTOM} for a custom command. - */ - public int getCommandCode() { - return mCommandCode; - } - - /** - * Gets the action of a custom command. - * This will return {@code null} for a predefined command. - */ - @Nullable - public String getCustomAction() { - return mCustomAction; - } - - /** - * Gets the extra bundle of a custom command. - * This will return {@code null} for a predefined command. - */ - @Nullable - public Bundle getCustomExtras() { - return mCustomExtras; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - if (dest == null) { - throw new IllegalArgumentException("parcel shouldn't be null"); - } - dest.writeInt(mCommandCode); - dest.writeString(mCustomAction); - dest.writeBundle(mCustomExtras); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof Session2Command)) { - return false; - } - Session2Command other = (Session2Command) obj; - return mCommandCode == other.mCommandCode - && TextUtils.equals(mCustomAction, other.mCustomAction); - } - - @Override - public int hashCode() { - return Objects.hash(mCustomAction, mCommandCode); - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Contains the result of {@link Session2Command}. - */ - public static final class Result { - private final int mResultCode; - private final Bundle mResultData; - - /** - * Result code representing that the command is skipped or canceled. For an example, a seek - * command can be skipped if it is followed by another seek command. - */ - public static final int RESULT_INFO_SKIPPED = 1; - - /** - * Result code representing that the command is successfully completed. - */ - public static final int RESULT_SUCCESS = 0; - - /** - * Result code represents that call is ended with an unknown error. - */ - public static final int RESULT_ERROR_UNKNOWN_ERROR = -1; - - /** - * Constructor of {@link Result}. - * - * @param resultCode result code - * @param resultData result data - */ - public Result(int resultCode, @Nullable Bundle resultData) { - mResultCode = resultCode; - mResultData = resultData; - } - - /** - * Returns the result code. - */ - public int getResultCode() { - return mResultCode; - } - - /** - * Returns the result data. - */ - @Nullable - public Bundle getResultData() { - return mResultData; - } - } -} diff --git a/apex/media/framework/java/android/media/Session2CommandGroup.java b/apex/media/framework/java/android/media/Session2CommandGroup.java deleted file mode 100644 index af8184a27f0d..000000000000 --- a/apex/media/framework/java/android/media/Session2CommandGroup.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import static android.media.Session2Command.COMMAND_CODE_CUSTOM; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -/** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * A set of {@link Session2Command} which represents a command group. - */ -public final class Session2CommandGroup implements Parcelable { - private static final String TAG = "Session2CommandGroup"; - - public static final @android.annotation.NonNull Parcelable.Creator<Session2CommandGroup> - CREATOR = new Parcelable.Creator<Session2CommandGroup>() { - @Override - public Session2CommandGroup createFromParcel(Parcel in) { - return new Session2CommandGroup(in); - } - - @Override - public Session2CommandGroup[] newArray(int size) { - return new Session2CommandGroup[size]; - } - }; - - Set<Session2Command> mCommands = new HashSet<>(); - - /** - * Creates a new Session2CommandGroup with commands copied from another object. - * - * @param commands The collection of commands to copy. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - Session2CommandGroup(@Nullable Collection<Session2Command> commands) { - if (commands != null) { - mCommands.addAll(commands); - } - } - - /** - * Used by parcelable creator. - */ - @SuppressWarnings({"WeakerAccess", "UnsafeParcelApi"}) /* synthetic access */ - Session2CommandGroup(Parcel in) { - Parcelable[] commands = in.readParcelableArray(Session2Command.class.getClassLoader()); - if (commands != null) { - for (Parcelable command : commands) { - mCommands.add((Session2Command) command); - } - } - } - - /** - * Checks whether this command group has a command that matches given {@code command}. - * - * @param command A command to find. Shouldn't be {@code null}. - */ - public boolean hasCommand(@NonNull Session2Command command) { - if (command == null) { - throw new IllegalArgumentException("command shouldn't be null"); - } - return mCommands.contains(command); - } - - /** - * Checks whether this command group has a command that matches given {@code commandCode}. - * - * @param commandCode A command code to find. - * Shouldn't be {@link Session2Command#COMMAND_CODE_CUSTOM}. - */ - public boolean hasCommand(int commandCode) { - if (commandCode == COMMAND_CODE_CUSTOM) { - throw new IllegalArgumentException("Use hasCommand(Command) for custom command"); - } - for (Session2Command command : mCommands) { - if (command.getCommandCode() == commandCode) { - return true; - } - } - return false; - } - - /** - * Gets all commands of this command group. - */ - @NonNull - public Set<Session2Command> getCommands() { - return new HashSet<>(mCommands); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - if (dest == null) { - throw new IllegalArgumentException("parcel shouldn't be null"); - } - dest.writeParcelableArray(mCommands.toArray(new Session2Command[0]), 0); - } - - /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Builds a {@link Session2CommandGroup} object. - */ - public static final class Builder { - private Set<Session2Command> mCommands; - - public Builder() { - mCommands = new HashSet<>(); - } - - /** - * Creates a new builder for {@link Session2CommandGroup} with commands copied from another - * {@link Session2CommandGroup} object. - * @param commandGroup - */ - public Builder(@NonNull Session2CommandGroup commandGroup) { - if (commandGroup == null) { - throw new IllegalArgumentException("command group shouldn't be null"); - } - mCommands = commandGroup.getCommands(); - } - - /** - * Adds a command to this command group. - * - * @param command A command to add. Shouldn't be {@code null}. - */ - @NonNull - public Builder addCommand(@NonNull Session2Command command) { - if (command == null) { - throw new IllegalArgumentException("command shouldn't be null"); - } - mCommands.add(command); - return this; - } - - /** - * Removes a command from this group which matches given {@code command}. - * - * @param command A command to find. Shouldn't be {@code null}. - */ - @NonNull - public Builder removeCommand(@NonNull Session2Command command) { - if (command == null) { - throw new IllegalArgumentException("command shouldn't be null"); - } - mCommands.remove(command); - return this; - } - - /** - * Builds {@link Session2CommandGroup}. - * - * @return a new {@link Session2CommandGroup}. - */ - @NonNull - public Session2CommandGroup build() { - return new Session2CommandGroup(mCommands); - } - } -} diff --git a/apex/media/framework/java/android/media/Session2Link.java b/apex/media/framework/java/android/media/Session2Link.java deleted file mode 100644 index 6e550e86a9fe..000000000000 --- a/apex/media/framework/java/android/media/Session2Link.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.annotation.NonNull; -import android.os.Binder; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.util.Log; - -import java.util.Objects; - -/** - * Handles incoming commands from {@link MediaController2} to {@link MediaSession2}. - * @hide - */ -// @SystemApi -public final class Session2Link implements Parcelable { - private static final String TAG = "Session2Link"; - private static final boolean DEBUG = MediaSession2.DEBUG; - - public static final @android.annotation.NonNull Parcelable.Creator<Session2Link> CREATOR = - new Parcelable.Creator<Session2Link>() { - @Override - public Session2Link createFromParcel(Parcel in) { - return new Session2Link(in); - } - - @Override - public Session2Link[] newArray(int size) { - return new Session2Link[size]; - } - }; - - private final MediaSession2 mSession; - private final IMediaSession2 mISession; - - public Session2Link(MediaSession2 session) { - mSession = session; - mISession = new Session2Stub(); - } - - Session2Link(Parcel in) { - mSession = null; - mISession = IMediaSession2.Stub.asInterface(in.readStrongBinder()); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeStrongBinder(mISession.asBinder()); - } - - @Override - public int hashCode() { - return mISession.asBinder().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof Session2Link)) { - return false; - } - Session2Link other = (Session2Link) obj; - return Objects.equals(mISession.asBinder(), other.mISession.asBinder()); - } - - /** Link to death with mISession */ - public void linkToDeath(@NonNull IBinder.DeathRecipient recipient, int flags) { - if (mISession != null) { - try { - mISession.asBinder().linkToDeath(recipient, flags); - } catch (RemoteException e) { - if (DEBUG) { - Log.d(TAG, "Session died too early.", e); - } - } - } - } - - /** Unlink to death with mISession */ - public boolean unlinkToDeath(@NonNull IBinder.DeathRecipient recipient, int flags) { - if (mISession != null) { - return mISession.asBinder().unlinkToDeath(recipient, flags); - } - return true; - } - - /** Interface method for IMediaSession2.connect */ - public void connect(final Controller2Link caller, int seq, Bundle connectionRequest) { - try { - mISession.connect(caller, seq, connectionRequest); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Interface method for IMediaSession2.disconnect */ - public void disconnect(final Controller2Link caller, int seq) { - try { - mISession.disconnect(caller, seq); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Interface method for IMediaSession2.sendSessionCommand */ - public void sendSessionCommand(final Controller2Link caller, final int seq, - final Session2Command command, final Bundle args, ResultReceiver resultReceiver) { - try { - mISession.sendSessionCommand(caller, seq, command, args, resultReceiver); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Interface method for IMediaSession2.sendSessionCommand */ - public void cancelSessionCommand(final Controller2Link caller, final int seq) { - try { - mISession.cancelSessionCommand(caller, seq); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** Stub implementation for IMediaSession2.connect */ - public void onConnect(final Controller2Link caller, int pid, int uid, int seq, - Bundle connectionRequest) { - mSession.onConnect(caller, pid, uid, seq, connectionRequest); - } - - /** Stub implementation for IMediaSession2.disconnect */ - public void onDisconnect(final Controller2Link caller, int seq) { - mSession.onDisconnect(caller, seq); - } - - /** Stub implementation for IMediaSession2.sendSessionCommand */ - public void onSessionCommand(final Controller2Link caller, final int seq, - final Session2Command command, final Bundle args, ResultReceiver resultReceiver) { - mSession.onSessionCommand(caller, seq, command, args, resultReceiver); - } - - /** Stub implementation for IMediaSession2.cancelSessionCommand */ - public void onCancelCommand(final Controller2Link caller, final int seq) { - mSession.onCancelCommand(caller, seq); - } - - private class Session2Stub extends IMediaSession2.Stub { - @Override - public void connect(final Controller2Link caller, int seq, Bundle connectionRequest) { - if (caller == null || connectionRequest == null) { - return; - } - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - try { - Session2Link.this.onConnect(caller, pid, uid, seq, connectionRequest); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void disconnect(final Controller2Link caller, int seq) { - if (caller == null) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - Session2Link.this.onDisconnect(caller, seq); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void sendSessionCommand(final Controller2Link caller, final int seq, - final Session2Command command, final Bundle args, ResultReceiver resultReceiver) { - if (caller == null) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - Session2Link.this.onSessionCommand(caller, seq, command, args, resultReceiver); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void cancelSessionCommand(final Controller2Link caller, final int seq) { - if (caller == null) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - Session2Link.this.onCancelCommand(caller, seq); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } -} diff --git a/apex/media/framework/java/android/media/Session2Token.java b/apex/media/framework/java/android/media/Session2Token.java deleted file mode 100644 index aae2e1bcb6df..000000000000 --- a/apex/media/framework/java/android/media/Session2Token.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; -import java.util.Objects; - -/** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session - * Library</a> for consistent behavior across all devices. - * <p> - * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}. - * If it's representing a session service, it may not be ongoing. - * <p> - * This may be passed to apps by the session owner to allow them to create a - * {@link MediaController2} to communicate with the session. - * <p> - * It can be also obtained by {@link android.media.session.MediaSessionManager}. - */ -public final class Session2Token implements Parcelable { - private static final String TAG = "Session2Token"; - - public static final @android.annotation.NonNull Creator<Session2Token> CREATOR = - new Creator<Session2Token>() { - @Override - public Session2Token createFromParcel(Parcel p) { - return new Session2Token(p); - } - - @Override - public Session2Token[] newArray(int size) { - return new Session2Token[size]; - } - }; - - /** - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = "TYPE_", value = {TYPE_SESSION, TYPE_SESSION_SERVICE}) - public @interface TokenType { - } - - /** - * Type for {@link MediaSession2}. - */ - public static final int TYPE_SESSION = 0; - - /** - * Type for {@link MediaSession2Service}. - */ - public static final int TYPE_SESSION_SERVICE = 1; - - private final int mUid; - @TokenType - private final int mType; - private final String mPackageName; - private final String mServiceName; - private final Session2Link mSessionLink; - private final ComponentName mComponentName; - private final Bundle mExtras; - - /** - * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}. - * - * @param context The context. - * @param serviceComponent The component name of the service. - */ - public Session2Token(@NonNull Context context, @NonNull ComponentName serviceComponent) { - if (context == null) { - throw new IllegalArgumentException("context shouldn't be null"); - } - if (serviceComponent == null) { - throw new IllegalArgumentException("serviceComponent shouldn't be null"); - } - - final PackageManager manager = context.getPackageManager(); - final int uid = getUid(manager, serviceComponent.getPackageName()); - - if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE, - serviceComponent)) { - Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service."); - } - mComponentName = serviceComponent; - mPackageName = serviceComponent.getPackageName(); - mServiceName = serviceComponent.getClassName(); - mUid = uid; - mType = TYPE_SESSION_SERVICE; - mSessionLink = null; - mExtras = Bundle.EMPTY; - } - - Session2Token(int uid, int type, String packageName, Session2Link sessionLink, - @NonNull Bundle tokenExtras) { - mUid = uid; - mType = type; - mPackageName = packageName; - mServiceName = null; - mComponentName = null; - mSessionLink = sessionLink; - mExtras = tokenExtras; - } - - Session2Token(Parcel in) { - mUid = in.readInt(); - mType = in.readInt(); - mPackageName = in.readString(); - mServiceName = in.readString(); - mSessionLink = in.readParcelable(null); - mComponentName = ComponentName.unflattenFromString(in.readString()); - - Bundle extras = in.readBundle(); - if (extras == null) { - Log.w(TAG, "extras shouldn't be null."); - extras = Bundle.EMPTY; - } else if (MediaSession2.hasCustomParcelable(extras)) { - Log.w(TAG, "extras contain custom parcelable. Ignoring."); - extras = Bundle.EMPTY; - } - mExtras = extras; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mUid); - dest.writeInt(mType); - dest.writeString(mPackageName); - dest.writeString(mServiceName); - dest.writeParcelable(mSessionLink, flags); - dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString()); - dest.writeBundle(mExtras); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public int hashCode() { - return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof Session2Token)) { - return false; - } - Session2Token other = (Session2Token) obj; - return mUid == other.mUid - && TextUtils.equals(mPackageName, other.mPackageName) - && TextUtils.equals(mServiceName, other.mServiceName) - && mType == other.mType - && Objects.equals(mSessionLink, other.mSessionLink); - } - - @Override - public String toString() { - return "Session2Token {pkg=" + mPackageName + " type=" + mType - + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}"; - } - - /** - * @return uid of the session - */ - public int getUid() { - return mUid; - } - - /** - * @return package name of the session - */ - @NonNull - public String getPackageName() { - return mPackageName; - } - - /** - * @return service name of the session. Can be {@code null} for {@link #TYPE_SESSION}. - */ - @Nullable - public String getServiceName() { - return mServiceName; - } - - /** - * @return type of the token - * @see #TYPE_SESSION - * @see #TYPE_SESSION_SERVICE - */ - public @TokenType int getType() { - return mType; - } - - /** - * @return extras of the token - * @see MediaSession2.Builder#setExtras(Bundle) - */ - @NonNull - public Bundle getExtras() { - return new Bundle(mExtras); - } - - Session2Link getSessionLink() { - return mSessionLink; - } - - private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface, - ComponentName serviceComponent) { - Intent serviceIntent = new Intent(serviceInterface); - // Use queryIntentServices to find services with MediaSession2Service.SERVICE_INTERFACE. - // We cannot use resolveService with intent specified class name, because resolveService - // ignores actions if Intent.setClassName() is specified. - serviceIntent.setPackage(serviceComponent.getPackageName()); - - List<ResolveInfo> list = manager.queryIntentServices( - serviceIntent, PackageManager.GET_META_DATA); - if (list != null) { - for (int i = 0; i < list.size(); i++) { - ResolveInfo resolveInfo = list.get(i); - if (resolveInfo == null || resolveInfo.serviceInfo == null) { - continue; - } - if (TextUtils.equals( - resolveInfo.serviceInfo.name, serviceComponent.getClassName())) { - return true; - } - } - } - return false; - } - - private static int getUid(PackageManager manager, String packageName) { - try { - return manager.getApplicationInfo(packageName, 0).uid; - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException("Cannot find package " + packageName); - } - } -} diff --git a/apex/media/framework/jni/android_media_MediaParserJNI.cpp b/apex/media/framework/jni/android_media_MediaParserJNI.cpp deleted file mode 100644 index c81152c0954c..000000000000 --- a/apex/media/framework/jni/android_media_MediaParserJNI.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 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. - */ - -#include <jni.h> -#include <media/MediaMetrics.h> - -#define JNI_FUNCTION(RETURN_TYPE, NAME, ...) \ - extern "C" { \ - JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \ - ##__VA_ARGS__); \ - } \ - JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \ - ##__VA_ARGS__) - -namespace { - -constexpr char kMediaMetricsKey[] = "mediaparser"; - -constexpr char kAttributeLogSessionId[] = "android.media.mediaparser.logSessionId"; -constexpr char kAttributeParserName[] = "android.media.mediaparser.parserName"; -constexpr char kAttributeCreatedByName[] = "android.media.mediaparser.createdByName"; -constexpr char kAttributeParserPool[] = "android.media.mediaparser.parserPool"; -constexpr char kAttributeLastException[] = "android.media.mediaparser.lastException"; -constexpr char kAttributeResourceByteCount[] = "android.media.mediaparser.resourceByteCount"; -constexpr char kAttributeDurationMillis[] = "android.media.mediaparser.durationMillis"; -constexpr char kAttributeTrackMimeTypes[] = "android.media.mediaparser.trackMimeTypes"; -constexpr char kAttributeTrackCodecs[] = "android.media.mediaparser.trackCodecs"; -constexpr char kAttributeAlteredParameters[] = "android.media.mediaparser.alteredParameters"; -constexpr char kAttributeVideoWidth[] = "android.media.mediaparser.videoWidth"; -constexpr char kAttributeVideoHeight[] = "android.media.mediaparser.videoHeight"; - -// Util class to handle string resource management. -class JstringHandle { -public: - JstringHandle(JNIEnv* env, jstring value) : mEnv(env), mJstringValue(value) { - mCstringValue = env->GetStringUTFChars(value, /* isCopy= */ nullptr); - } - - ~JstringHandle() { - if (mCstringValue != nullptr) { - mEnv->ReleaseStringUTFChars(mJstringValue, mCstringValue); - } - } - - [[nodiscard]] const char* value() const { - return mCstringValue != nullptr ? mCstringValue : ""; - } - - JNIEnv* mEnv; - jstring mJstringValue; - const char* mCstringValue; -}; - -} // namespace - -JNI_FUNCTION(void, nativeSubmitMetrics, jstring logSessionIdJstring, jstring parserNameJstring, - jboolean createdByName, jstring parserPoolJstring, jstring lastExceptionJstring, - jlong resourceByteCount, jlong durationMillis, jstring trackMimeTypesJstring, - jstring trackCodecsJstring, jstring alteredParameters, jint videoWidth, - jint videoHeight) { - mediametrics_handle_t item(mediametrics_create(kMediaMetricsKey)); - mediametrics_setCString(item, kAttributeLogSessionId, - JstringHandle(env, logSessionIdJstring).value()); - mediametrics_setCString(item, kAttributeParserName, - JstringHandle(env, parserNameJstring).value()); - mediametrics_setInt32(item, kAttributeCreatedByName, createdByName ? 1 : 0); - mediametrics_setCString(item, kAttributeParserPool, - JstringHandle(env, parserPoolJstring).value()); - mediametrics_setCString(item, kAttributeLastException, - JstringHandle(env, lastExceptionJstring).value()); - mediametrics_setInt64(item, kAttributeResourceByteCount, resourceByteCount); - mediametrics_setInt64(item, kAttributeDurationMillis, durationMillis); - mediametrics_setCString(item, kAttributeTrackMimeTypes, - JstringHandle(env, trackMimeTypesJstring).value()); - mediametrics_setCString(item, kAttributeTrackCodecs, - JstringHandle(env, trackCodecsJstring).value()); - mediametrics_setCString(item, kAttributeAlteredParameters, - JstringHandle(env, alteredParameters).value()); - mediametrics_setInt32(item, kAttributeVideoWidth, videoWidth); - mediametrics_setInt32(item, kAttributeVideoHeight, videoHeight); - mediametrics_selfRecord(item); - mediametrics_delete(item); -} diff --git a/apex/media/framework/lint-baseline.xml b/apex/media/framework/lint-baseline.xml deleted file mode 100644 index 95eea45069ef..000000000000 --- a/apex/media/framework/lint-baseline.xml +++ /dev/null @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev"> - - <issue - id="DefaultLocale" - message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`." - errorLine1=" if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {" - errorLine2=" ~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java" - line="121" - column="57"/> - </issue> - - <issue - id="DefaultLocale" - message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead" - errorLine1=" return String.format(" session: {id: %d, status: %s, result: %s, progress: %d}"," - errorLine2=" ^"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaTranscodingManager.java" - line="1651" - column="20"/> - </issue> - - <issue - id="ParcelClassLoader" - message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead." - errorLine1=" Bundle out = parcel.readBundle(null);" - errorLine2=" ~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaSession2.java" - line="303" - column="33"/> - </issue> - - <issue - id="ParcelClassLoader" - message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead." - errorLine1=" mCustomExtras = in.readBundle();" - errorLine2=" ~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/Session2Command.java" - line="104" - column="28"/> - </issue> - - <issue - id="ParcelClassLoader" - message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead." - errorLine1=" mSessionLink = in.readParcelable(null);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java" - line="141" - column="27"/> - </issue> - - <issue - id="ParcelClassLoader" - message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead." - errorLine1=" Bundle extras = in.readBundle();" - errorLine2=" ~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java" - line="144" - column="28"/> - </issue> - -</issues> diff --git a/apex/media/framework/updatable-media-proguard.flags b/apex/media/framework/updatable-media-proguard.flags deleted file mode 100644 index 4e7d8422bf44..000000000000 --- a/apex/media/framework/updatable-media-proguard.flags +++ /dev/null @@ -1,2 +0,0 @@ -# Keep all symbols in android.media. --keep class android.media.* {*;} diff --git a/apex/media/service/Android.bp b/apex/media/service/Android.bp deleted file mode 100644 index 0e300bbb09c8..000000000000 --- a/apex/media/service/Android.bp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 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 { - // See: http://go/android-license-faq - default_applicable_licenses: ["Android-Apache-2.0"], -} - -filegroup { - name: "service-media-s-sources", - srcs: [ - "java/**/*.java", - ], - path: "java", - visibility: ["//visibility:private"], -} - -java_sdk_library { - name: "service-media-s", - permitted_packages: [ - "com.android.server.media", - ], - defaults: ["framework-system-server-module-defaults"], - srcs: [ - ":service-media-s-sources", - ], - libs: [ - "androidx.annotation_annotation", - "updatable-media", - "modules-annotation-minsdk", - "modules-utils-build", - ], - jarjar_rules: "jarjar_rules.txt", - sdk_version: "system_server_current", - min_sdk_version: "29", // TODO: We may need to bump this at some point. - lint: { - strict_updatability_linting: true, - }, - apex_available: [ - "com.android.media", - ], -} diff --git a/apex/media/service/api/current.txt b/apex/media/service/api/current.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/apex/media/service/api/current.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/apex/media/service/api/removed.txt b/apex/media/service/api/removed.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/apex/media/service/api/removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/apex/media/service/api/system-server-current.txt b/apex/media/service/api/system-server-current.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/apex/media/service/api/system-server-current.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/apex/media/service/api/system-server-removed.txt b/apex/media/service/api/system-server-removed.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/apex/media/service/api/system-server-removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/apex/media/service/jarjar_rules.txt b/apex/media/service/jarjar_rules.txt deleted file mode 100644 index 7e37c2be5b4e..000000000000 --- a/apex/media/service/jarjar_rules.txt +++ /dev/null @@ -1 +0,0 @@ -rule com.android.modules.** android.media.internal.@1 diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java deleted file mode 100644 index 4223fa65fd53..000000000000 --- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java +++ /dev/null @@ -1,685 +0,0 @@ -/* - * Copyright 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.server.media; - -import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; -import static android.os.UserHandle.ALL; -import static android.os.UserHandle.getUserHandleForUid; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.NotificationManager; -import android.content.Context; -import android.content.pm.PackageManager; -import android.media.IMediaCommunicationService; -import android.media.IMediaCommunicationServiceCallback; -import android.media.MediaController2; -import android.media.MediaParceledListSlice; -import android.media.Session2CommandGroup; -import android.media.Session2Token; -import android.media.session.MediaSessionManager; -import android.os.Binder; -import android.os.Build; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Process; -import android.os.RemoteException; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseIntArray; -import android.view.KeyEvent; - -import androidx.annotation.RequiresApi; - -import com.android.internal.annotations.GuardedBy; -import com.android.modules.annotation.MinSdk; -import com.android.server.SystemService; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * A system service that manages {@link android.media.MediaSession2} creations - * and their ongoing media playback state. - * @hide - */ -@MinSdk(Build.VERSION_CODES.S) -@RequiresApi(Build.VERSION_CODES.S) -public class MediaCommunicationService extends SystemService { - private static final String TAG = "MediaCommunicationSrv"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - final Context mContext; - - final Object mLock = new Object(); - final Handler mHandler = new Handler(Looper.getMainLooper()); - - @GuardedBy("mLock") - private final SparseIntArray mFullUserIds = new SparseIntArray(); - @GuardedBy("mLock") - private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<>(); - - final Executor mRecordExecutor = Executors.newSingleThreadExecutor(); - @GuardedBy("mLock") - final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<>(); - final NotificationManager mNotificationManager; - MediaSessionManager mSessionManager; - - public MediaCommunicationService(Context context) { - super(context); - mContext = context; - mNotificationManager = context.getSystemService(NotificationManager.class); - } - - @Override - public void onStart() { - publishBinderService(Context.MEDIA_COMMUNICATION_SERVICE, new Stub()); - updateUser(); - } - - @Override - public void onBootPhase(int phase) { - super.onBootPhase(phase); - switch (phase) { - // This ensures MediaSessionService is started - case PHASE_BOOT_COMPLETED: - mSessionManager = mContext.getSystemService(MediaSessionManager.class); - break; - } - } - - @Override - public void onUserStarting(@NonNull TargetUser user) { - if (DEBUG) Log.d(TAG, "onUserStarting: " + user); - updateUser(); - } - - @Override - public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { - if (DEBUG) Log.d(TAG, "onUserSwitching: " + to); - updateUser(); - } - - @Override - public void onUserStopped(@NonNull TargetUser targetUser) { - int userId = targetUser.getUserHandle().getIdentifier(); - - if (DEBUG) Log.d(TAG, "onUserStopped: " + userId); - synchronized (mLock) { - FullUserRecord user = getFullUserRecordLocked(userId); - if (user != null) { - if (user.getFullUserId() == userId) { - user.destroyAllSessions(); - mUserRecords.remove(userId); - } else { - user.destroySessionsForUser(userId); - } - } - } - updateUser(); - } - - @Nullable - CallbackRecord findCallbackRecordLocked(@Nullable IMediaCommunicationServiceCallback callback) { - if (callback == null) { - return null; - } - for (CallbackRecord record : mCallbackRecords) { - if (Objects.equals(callback.asBinder(), record.mCallback.asBinder())) { - return record; - } - } - return null; - } - - ArrayList<Session2Token> getSession2TokensLocked(int userId) { - ArrayList<Session2Token> list = new ArrayList<>(); - if (userId == ALL.getIdentifier()) { - int size = mUserRecords.size(); - for (int i = 0; i < size; i++) { - list.addAll(mUserRecords.valueAt(i).getAllSession2Tokens()); - } - } else { - FullUserRecord user = getFullUserRecordLocked(userId); - if (user != null) { - list.addAll(user.getSession2Tokens(userId)); - } - } - return list; - } - - private FullUserRecord getFullUserRecordLocked(int userId) { - int fullUserId = mFullUserIds.get(userId, -1); - if (fullUserId < 0) { - return null; - } - return mUserRecords.get(fullUserId); - } - - private boolean hasMediaControlPermission(int pid, int uid) { - // Check if it's system server or has MEDIA_CONTENT_CONTROL. - // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra - // check here. - if (uid == Process.SYSTEM_UID || mContext.checkPermission( - android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) - == PackageManager.PERMISSION_GRANTED) { - return true; - } else if (DEBUG) { - Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL"); - } - return false; - } - - private void updateUser() { - UserManager manager = mContext.getSystemService(UserManager.class); - List<UserHandle> allUsers = manager.getUserHandles(/*excludeDying=*/false); - - synchronized (mLock) { - mFullUserIds.clear(); - if (allUsers != null) { - for (UserHandle user : allUsers) { - UserHandle parent = manager.getProfileParent(user); - if (parent != null) { - mFullUserIds.put(user.getIdentifier(), parent.getIdentifier()); - } else { - mFullUserIds.put(user.getIdentifier(), user.getIdentifier()); - if (mUserRecords.get(user.getIdentifier()) == null) { - mUserRecords.put(user.getIdentifier(), - new FullUserRecord(user.getIdentifier())); - } - } - } - } - // Ensure that the current full user exists. - int currentFullUserId = ActivityManager.getCurrentUser(); - FullUserRecord currentFullUserRecord = mUserRecords.get(currentFullUserId); - if (currentFullUserRecord == null) { - Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId); - currentFullUserRecord = new FullUserRecord(currentFullUserId); - mUserRecords.put(currentFullUserId, currentFullUserRecord); - } - mFullUserIds.put(currentFullUserId, currentFullUserId); - } - } - - void dispatchSession2Created(Session2Token token) { - synchronized (mLock) { - for (CallbackRecord record : mCallbackRecords) { - if (record.mUserId != ALL.getIdentifier() - && record.mUserId != getUserHandleForUid(token.getUid()).getIdentifier()) { - continue; - } - try { - record.mCallback.onSession2Created(token); - } catch (RemoteException e) { - Log.w(TAG, "Failed to notify session2 token created " + record); - } - } - } - } - - void dispatchSession2Changed(int userId) { - ArrayList<Session2Token> allSession2Tokens; - ArrayList<Session2Token> userSession2Tokens; - - synchronized (mLock) { - allSession2Tokens = getSession2TokensLocked(ALL.getIdentifier()); - userSession2Tokens = getSession2TokensLocked(userId); - - for (CallbackRecord record : mCallbackRecords) { - if (record.mUserId == ALL.getIdentifier()) { - try { - MediaParceledListSlice<Session2Token> toSend = - new MediaParceledListSlice<>(allSession2Tokens); - toSend.setInlineCountLimit(0); - record.mCallback.onSession2Changed(toSend); - } catch (RemoteException e) { - Log.w(TAG, "Failed to notify session2 tokens changed " + record); - } - } else if (record.mUserId == userId) { - try { - MediaParceledListSlice<Session2Token> toSend = - new MediaParceledListSlice<>(userSession2Tokens); - toSend.setInlineCountLimit(0); - record.mCallback.onSession2Changed(toSend); - } catch (RemoteException e) { - Log.w(TAG, "Failed to notify session2 tokens changed " + record); - } - } - } - } - } - - void onSessionDied(Session2Record session) { - if (DEBUG) { - Log.d(TAG, "Destroying " + session); - } - if (session.isClosed()) { - Log.w(TAG, "Destroying already destroyed session. Ignoring."); - return; - } - - FullUserRecord user = session.getFullUser(); - if (user != null) { - user.removeSession(session); - } - session.close(); - } - - void onSessionPlaybackStateChanged(Session2Record session, boolean promotePriority) { - FullUserRecord user = session.getFullUser(); - if (user == null || !user.containsSession(session)) { - Log.d(TAG, "Unknown session changed playback state. Ignoring."); - return; - } - user.onPlaybackStateChanged(session, promotePriority); - } - - - static boolean isMediaSessionKey(int keyCode) { - switch (keyCode) { - case KeyEvent.KEYCODE_MEDIA_PLAY: - case KeyEvent.KEYCODE_MEDIA_PAUSE: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_MUTE: - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_STOP: - case KeyEvent.KEYCODE_MEDIA_NEXT: - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - case KeyEvent.KEYCODE_MEDIA_REWIND: - case KeyEvent.KEYCODE_MEDIA_RECORD: - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - return true; - } - return false; - } - - private class Stub extends IMediaCommunicationService.Stub { - @Override - public void notifySession2Created(Session2Token sessionToken) { - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - - try { - if (DEBUG) { - Log.d(TAG, "Session2 is created " + sessionToken); - } - if (uid != sessionToken.getUid()) { - throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid - + " but actually=" + sessionToken.getUid()); - } - FullUserRecord user; - int userId = getUserHandleForUid(sessionToken.getUid()).getIdentifier(); - synchronized (mLock) { - user = getFullUserRecordLocked(userId); - } - if (user == null) { - Log.w(TAG, "notifySession2Created: Ignore session of an unknown user"); - return; - } - user.addSession(new Session2Record(MediaCommunicationService.this, - user, sessionToken, mRecordExecutor)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL - * permission or an enabled notification listener) - * - * @param controllerPackageName package name of the controller app - * @param controllerPid pid of the controller app - * @param controllerUid uid of the controller app - */ - @Override - public boolean isTrusted(String controllerPackageName, int controllerPid, - int controllerUid) { - final int uid = Binder.getCallingUid(); - final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); - final long token = Binder.clearCallingIdentity(); - try { - // Don't perform check between controllerPackageName and controllerUid. - // When an (activity|service) runs on the another apps process by specifying - // android:process in the AndroidManifest.xml, then PID and UID would have the - // running process' information instead of the (activity|service) that has created - // MediaController. - // Note that we can use Context#getOpPackageName() instead of - // Context#getPackageName() for getting package name that matches with the PID/UID, - // but it doesn't tell which package has created the MediaController, so useless. - return hasMediaControlPermission(controllerPid, controllerUid) - || hasEnabledNotificationListener( - userId, controllerPackageName, controllerUid); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public MediaParceledListSlice getSession2Tokens(int userId) { - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - - try { - // Check that they can make calls on behalf of the user and get the final user id - int resolvedUserId = handleIncomingUser(pid, uid, userId, null); - ArrayList<Session2Token> result; - synchronized (mLock) { - result = getSession2TokensLocked(resolvedUserId); - } - MediaParceledListSlice parceledListSlice = new MediaParceledListSlice<>(result); - parceledListSlice.setInlineCountLimit(1); - return parceledListSlice; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void dispatchMediaKeyEvent(String packageName, KeyEvent keyEvent, - boolean asSystemService) { - if (keyEvent == null || !isMediaSessionKey(keyEvent.getKeyCode())) { - Log.w(TAG, "Attempted to dispatch null or non-media key event."); - return; - } - - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - try { - //TODO: Dispatch key event to media session 2 if required - mSessionManager.dispatchMediaKeyEvent(keyEvent, asSystemService); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void registerCallback(IMediaCommunicationServiceCallback callback, - String packageName) throws RemoteException { - Objects.requireNonNull(callback, "callback should not be null"); - Objects.requireNonNull(packageName, "packageName should not be null"); - - synchronized (mLock) { - if (findCallbackRecordLocked(callback) == null) { - - CallbackRecord record = new CallbackRecord(callback, packageName, - Binder.getCallingUid(), Binder.getCallingPid()); - mCallbackRecords.add(record); - try { - callback.asBinder().linkToDeath(record, 0); - } catch (RemoteException e) { - Log.w(TAG, "Failed to register callback", e); - mCallbackRecords.remove(record); - } - } else { - Log.e(TAG, "registerCallback is called with already registered callback. " - + "packageName=" + packageName); - } - } - } - - @Override - public void unregisterCallback(IMediaCommunicationServiceCallback callback) - throws RemoteException { - synchronized (mLock) { - CallbackRecord existingRecord = findCallbackRecordLocked(callback); - if (existingRecord != null) { - mCallbackRecords.remove(existingRecord); - callback.asBinder().unlinkToDeath(existingRecord, 0); - } else { - Log.e(TAG, "unregisterCallback is called with unregistered callback."); - } - } - } - - private boolean hasEnabledNotificationListener(int callingUserId, - String controllerPackageName, int controllerUid) { - int controllerUserId = UserHandle.getUserHandleForUid(controllerUid).getIdentifier(); - if (callingUserId != controllerUserId) { - // Enabled notification listener only works within the same user. - return false; - } - - if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName, - UserHandle.getUserHandleForUid(controllerUid))) { - return true; - } - if (DEBUG) { - Log.d(TAG, controllerPackageName + " (uid=" + controllerUid - + ") doesn't have an enabled notification listener"); - } - return false; - } - - // Handles incoming user by checking whether the caller has permission to access the - // given user id's information or not. Permission is not necessary if the given user id is - // equal to the caller's user id, but if not, the caller needs to have the - // INTERACT_ACROSS_USERS_FULL permission. Otherwise, a security exception will be thrown. - // The return value will be the given user id, unless the given user id is - // UserHandle.CURRENT, which will return the ActivityManager.getCurrentUser() value instead. - private int handleIncomingUser(int pid, int uid, int userId, String packageName) { - int callingUserId = UserHandle.getUserHandleForUid(uid).getIdentifier(); - if (userId == callingUserId) { - return userId; - } - - boolean canInteractAcrossUsersFull = mContext.checkPermission( - INTERACT_ACROSS_USERS_FULL, pid, uid) == PackageManager.PERMISSION_GRANTED; - if (canInteractAcrossUsersFull) { - if (userId == UserHandle.CURRENT.getIdentifier()) { - return ActivityManager.getCurrentUser(); - } - return userId; - } - - throw new SecurityException("Permission denied while calling from " + packageName - + " with user id: " + userId + "; Need to run as either the calling user id (" - + callingUserId + "), or with " + INTERACT_ACROSS_USERS_FULL + " permission"); - } - } - - final class CallbackRecord implements IBinder.DeathRecipient { - private final IMediaCommunicationServiceCallback mCallback; - private final String mPackageName; - private final int mUid; - private int mPid; - private final int mUserId; - - CallbackRecord(IMediaCommunicationServiceCallback callback, - String packageName, int uid, int pid) { - mCallback = callback; - mPackageName = packageName; - mUid = uid; - mPid = pid; - mUserId = (mContext.checkPermission( - INTERACT_ACROSS_USERS_FULL, pid, uid) == PackageManager.PERMISSION_GRANTED) - ? ALL.getIdentifier() : UserHandle.getUserHandleForUid(mUid).getIdentifier(); - } - - @Override - public String toString() { - return "CallbackRecord[callback=" + mCallback + ", pkg=" + mPackageName - + ", uid=" + mUid + ", pid=" + mPid + "]"; - } - - @Override - public void binderDied() { - synchronized (mLock) { - mCallbackRecords.remove(this); - } - } - } - - final class FullUserRecord { - private final int mFullUserId; - private final SessionPriorityList mSessionPriorityList = new SessionPriorityList(); - - FullUserRecord(int fullUserId) { - mFullUserId = fullUserId; - } - - public void addSession(Session2Record record) { - mSessionPriorityList.addSession(record); - mHandler.post(() -> dispatchSession2Created(record.mSessionToken)); - mHandler.post(() -> dispatchSession2Changed(mFullUserId)); - } - - private void removeSession(Session2Record record) { - mSessionPriorityList.removeSession(record); - mHandler.post(() -> dispatchSession2Changed(mFullUserId)); - //TODO: Handle if the removed session was the media button session. - } - - public int getFullUserId() { - return mFullUserId; - } - - public List<Session2Token> getAllSession2Tokens() { - return mSessionPriorityList.getAllTokens(); - } - - public List<Session2Token> getSession2Tokens(int userId) { - return mSessionPriorityList.getTokensByUserId(userId); - } - - public void destroyAllSessions() { - mSessionPriorityList.destroyAllSessions(); - mHandler.post(() -> dispatchSession2Changed(mFullUserId)); - } - - public void destroySessionsForUser(int userId) { - if (mSessionPriorityList.destroySessionsByUserId(userId)) { - mHandler.post(() -> dispatchSession2Changed(mFullUserId)); - } - } - - public boolean containsSession(Session2Record session) { - return mSessionPriorityList.contains(session); - } - - public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) { - mSessionPriorityList.onPlaybackStateChanged(session, promotePriority); - } - } - - static final class Session2Record { - final Session2Token mSessionToken; - final Object mSession2RecordLock = new Object(); - final WeakReference<MediaCommunicationService> mServiceRef; - final WeakReference<FullUserRecord> mFullUserRef; - @GuardedBy("mSession2RecordLock") - private final MediaController2 mController; - - @GuardedBy("mSession2RecordLock") - boolean mIsConnected; - @GuardedBy("mSession2RecordLock") - private boolean mIsClosed; - - //TODO: introduce policy (See MediaSessionPolicyProvider) - Session2Record(MediaCommunicationService service, FullUserRecord fullUser, - Session2Token token, Executor controllerExecutor) { - mServiceRef = new WeakReference<>(service); - mFullUserRef = new WeakReference<>(fullUser); - mSessionToken = token; - mController = new MediaController2.Builder(service.getContext(), token) - .setControllerCallback(controllerExecutor, new Controller2Callback()) - .build(); - } - - public int getUserId() { - return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier(); - } - - public FullUserRecord getFullUser() { - return mFullUserRef.get(); - } - - public boolean isClosed() { - synchronized (mSession2RecordLock) { - return mIsClosed; - } - } - - public void close() { - synchronized (mSession2RecordLock) { - mIsClosed = true; - mController.close(); - } - } - - public Session2Token getSessionToken() { - return mSessionToken; - } - - public boolean checkPlaybackActiveState(boolean expected) { - synchronized (mSession2RecordLock) { - return mIsConnected && mController.isPlaybackActive() == expected; - } - } - - private class Controller2Callback extends MediaController2.ControllerCallback { - @Override - public void onConnected(MediaController2 controller, - Session2CommandGroup allowedCommands) { - if (DEBUG) { - Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands); - } - synchronized (mSession2RecordLock) { - mIsConnected = true; - } - } - - @Override - public void onDisconnected(MediaController2 controller) { - if (DEBUG) { - Log.d(TAG, "disconnected from " + mSessionToken); - } - synchronized (mSession2RecordLock) { - mIsConnected = false; - } - MediaCommunicationService service = mServiceRef.get(); - if (service != null) { - service.onSessionDied(Session2Record.this); - } - } - - @Override - public void onPlaybackActiveChanged( - @NonNull MediaController2 controller, - boolean playbackActive) { - if (DEBUG) { - Log.d(TAG, "playback active changed, " + mSessionToken + ", active=" - + playbackActive); - } - MediaCommunicationService service = mServiceRef.get(); - if (service != null) { - service.onSessionPlaybackStateChanged(Session2Record.this, playbackActive); - } - } - } - } -} diff --git a/apex/media/service/java/com/android/server/media/SessionPriorityList.java b/apex/media/service/java/com/android/server/media/SessionPriorityList.java deleted file mode 100644 index 814586139123..000000000000 --- a/apex/media/service/java/com/android/server/media/SessionPriorityList.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 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.media; - -import android.annotation.Nullable; -import android.media.Session2Token; -import android.os.Build; -import android.util.Log; - -import androidx.annotation.RequiresApi; - -import com.android.internal.annotations.GuardedBy; -import com.android.modules.annotation.MinSdk; -import com.android.server.media.MediaCommunicationService.Session2Record; - -import java.util.ArrayList; -import java.util.List; - -//TODO: Define the priority specifically. -/** - * Keeps track of media sessions and their priority for notifications, media - * button dispatch, etc. - * Higher priority session has more chance to be selected as media button session, - * which receives the media button events. - */ -@MinSdk(Build.VERSION_CODES.S) -@RequiresApi(Build.VERSION_CODES.S) -class SessionPriorityList { - private static final String TAG = "SessionPriorityList"; - private final Object mLock = new Object(); - - @GuardedBy("mLock") - private final List<Session2Record> mSessions = new ArrayList<>(); - - @Nullable - private Session2Record mMediaButtonSession; - @Nullable - private Session2Record mCachedVolumeSession; - - //TODO: integrate AudioPlayerStateMonitor - - public void addSession(Session2Record record) { - synchronized (mLock) { - mSessions.add(record); - } - } - - public void removeSession(Session2Record record) { - synchronized (mLock) { - mSessions.remove(record); - } - if (record == mMediaButtonSession) { - updateMediaButtonSession(null); - } - } - - public void destroyAllSessions() { - synchronized (mLock) { - for (Session2Record session : mSessions) { - session.close(); - } - mSessions.clear(); - } - } - - public boolean destroySessionsByUserId(int userId) { - boolean changed = false; - synchronized (mLock) { - for (int i = mSessions.size() - 1; i >= 0; i--) { - Session2Record session = mSessions.get(i); - if (session.getUserId() == userId) { - mSessions.remove(i); - session.close(); - changed = true; - } - } - } - return changed; - } - - public List<Session2Token> getAllTokens() { - List<Session2Token> sessions = new ArrayList<>(); - synchronized (mLock) { - for (Session2Record session : mSessions) { - sessions.add(session.getSessionToken()); - } - } - return sessions; - } - - public List<Session2Token> getTokensByUserId(int userId) { - List<Session2Token> sessions = new ArrayList<>(); - synchronized (mLock) { - for (Session2Record session : mSessions) { - if (session.getUserId() == userId) { - sessions.add(session.getSessionToken()); - } - } - } - return sessions; - } - - /** Gets the media button session which receives the media button events. */ - @Nullable - public Session2Record getMediaButtonSession() { - return mMediaButtonSession; - } - - /** Gets the media volume session which receives the volume key events. */ - @Nullable - public Session2Record getMediaVolumeSession() { - //TODO: if null, calculate it. - return mCachedVolumeSession; - } - - public boolean contains(Session2Record session) { - synchronized (mLock) { - return mSessions.contains(session); - } - } - - public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) { - if (promotePriority) { - synchronized (mLock) { - if (mSessions.remove(session)) { - mSessions.add(0, session); - } else { - Log.w(TAG, "onPlaybackStateChanged: Ignoring unknown session"); - return; - } - } - } else if (session.checkPlaybackActiveState(false)) { - // Just clear the cached volume session when a state goes inactive - mCachedVolumeSession = null; - } - - // In most cases, playback state isn't needed for finding the media button session, - // but we only use it as a hint if an app has multiple local media sessions. - // In that case, we pick the media session whose PlaybackState matches - // the audio playback configuration. - if (mMediaButtonSession != null - && mMediaButtonSession.getSessionToken().getUid() - == session.getSessionToken().getUid()) { - Session2Record newMediaButtonSession = - findMediaButtonSession(mMediaButtonSession.getSessionToken().getUid()); - if (newMediaButtonSession != mMediaButtonSession) { - // Check if the policy states that this session should not be updated as a media - // button session. - updateMediaButtonSession(newMediaButtonSession); - } - } - } - - private void updateMediaButtonSession(@Nullable Session2Record newSession) { - mMediaButtonSession = newSession; - //TODO: invoke callbacks for media button session changed listeners - } - - /** - * Finds the media button session with the given {@param uid}. - * If the app has multiple media sessions, the media session whose playback state is not null - * and matches the audio playback state becomes the media button session. Otherwise the top - * priority session becomes the media button session. - * - * @return The media button session. Returns {@code null} if the app doesn't have a media - * session. - */ - @Nullable - private Session2Record findMediaButtonSession(int uid) { - Session2Record mediaButtonSession = null; - synchronized (mLock) { - for (Session2Record session : mSessions) { - if (uid != session.getSessionToken().getUid()) { - continue; - } - // TODO: check audio player state monitor - if (mediaButtonSession == null) { - // Pick the top priority session as a default. - mediaButtonSession = session; - } - } - } - return mediaButtonSession; - } -} diff --git a/apex/media/service/lint-baseline.xml b/apex/media/service/lint-baseline.xml deleted file mode 100644 index def6baf0ff4f..000000000000 --- a/apex/media/service/lint-baseline.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev"> - -</issues> diff --git a/core/api/current.txt b/core/api/current.txt index 330089dcc6d4..c8d99d57c142 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4550,6 +4550,7 @@ package android.app { method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int); + method @NonNull public static android.app.ActivityOptions makeLaunchIntoPip(@NonNull android.app.PictureInPictureParams); method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, String); method @java.lang.SafeVarargs public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View,java.lang.String>...); @@ -6635,10 +6636,12 @@ package android.app { public static class PictureInPictureParams.Builder { ctor public PictureInPictureParams.Builder(); + ctor public PictureInPictureParams.Builder(@NonNull android.app.PictureInPictureParams); method public android.app.PictureInPictureParams build(); method public android.app.PictureInPictureParams.Builder setActions(java.util.List<android.app.RemoteAction>); method public android.app.PictureInPictureParams.Builder setAspectRatio(android.util.Rational); method @NonNull public android.app.PictureInPictureParams.Builder setAutoEnterEnabled(boolean); + method @NonNull public android.app.PictureInPictureParams.Builder setCloseAction(@Nullable android.app.RemoteAction); method @NonNull public android.app.PictureInPictureParams.Builder setExpandedAspectRatio(@Nullable android.util.Rational); method @NonNull public android.app.PictureInPictureParams.Builder setSeamlessResizeEnabled(boolean); method public android.app.PictureInPictureParams.Builder setSourceRectHint(android.graphics.Rect); @@ -16994,6 +16997,7 @@ package android.hardware { public class SensorEvent { field public int accuracy; + field public boolean firstEventAfterDiscontinuity; field public android.hardware.Sensor sensor; field public long timestamp; field public final float[] values; @@ -17005,7 +17009,6 @@ package android.hardware { method public void onFlushCompleted(android.hardware.Sensor); method public void onSensorAdditionalInfo(android.hardware.SensorAdditionalInfo); method public void onSensorChanged(android.hardware.SensorEvent); - method public void onSensorDiscontinuity(@NonNull android.hardware.Sensor); } public interface SensorEventListener { @@ -39510,7 +39513,7 @@ package android.speech { method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback); method protected abstract void onStartListening(android.content.Intent, android.speech.RecognitionService.Callback); method protected abstract void onStopListening(android.speech.RecognitionService.Callback); - method public void triggerModelDownload(@NonNull android.content.Intent); + method public void onTriggerModelDownload(@NonNull android.content.Intent); field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService"; field public static final String SERVICE_META_DATA = "android.speech"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 273f59ce4dca..67091d81727c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2254,13 +2254,20 @@ package android.app.smartspace.uitemplatedata { public class BaseTemplateData implements android.os.Parcelable { method public int describeContents(); + method public int getLayoutWeight(); + method @Nullable public android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo getPrimaryLoggingInfo(); method @Nullable public android.app.smartspace.uitemplatedata.TapAction getPrimaryTapAction(); method @Nullable public android.app.smartspace.uitemplatedata.Icon getSubtitleIcon(); method @Nullable public android.app.smartspace.uitemplatedata.Text getSubtitleText(); method @Nullable public android.app.smartspace.uitemplatedata.Text getSupplementalAlarmText(); + method @Nullable public android.app.smartspace.uitemplatedata.Icon getSupplementalIcon(); + method @Nullable public android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo getSupplementalLoggingInfo(); method @Nullable public android.app.smartspace.uitemplatedata.Icon getSupplementalSubtitleIcon(); + method @Nullable public android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo getSupplementalSubtitleLoggingInfo(); method @Nullable public android.app.smartspace.uitemplatedata.TapAction getSupplementalSubtitleTapAction(); method @Nullable public android.app.smartspace.uitemplatedata.Text getSupplementalSubtitleText(); + method @Nullable public android.app.smartspace.uitemplatedata.TapAction getSupplementalTapAction(); + method @Nullable public android.app.smartspace.uitemplatedata.Text getSupplementalText(); method public int getTemplateType(); method @Nullable public android.app.smartspace.uitemplatedata.Icon getTitleIcon(); method @Nullable public android.app.smartspace.uitemplatedata.Text getTitleText(); @@ -2271,17 +2278,37 @@ package android.app.smartspace.uitemplatedata { public static class BaseTemplateData.Builder { ctor public BaseTemplateData.Builder(int); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData build(); + method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setLayoutWeight(int); + method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setPrimaryLoggingInfo(@NonNull android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setPrimaryTapAction(@NonNull android.app.smartspace.uitemplatedata.TapAction); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSubtitleIcon(@NonNull android.app.smartspace.uitemplatedata.Icon); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSubtitleText(@NonNull android.app.smartspace.uitemplatedata.Text); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalAlarmText(@NonNull android.app.smartspace.uitemplatedata.Text); + method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalIcon(@NonNull android.app.smartspace.uitemplatedata.Icon); + method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalLoggingInfo(@NonNull android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalSubtitleIcon(@NonNull android.app.smartspace.uitemplatedata.Icon); + method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalSubtitleLoggingInfo(@NonNull android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalSubtitleTapAction(@NonNull android.app.smartspace.uitemplatedata.TapAction); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalSubtitleText(@NonNull android.app.smartspace.uitemplatedata.Text); + method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalTapAction(@NonNull android.app.smartspace.uitemplatedata.TapAction); + method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalText(@NonNull android.app.smartspace.uitemplatedata.Text); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setTitleIcon(@NonNull android.app.smartspace.uitemplatedata.Icon); method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setTitleText(@NonNull android.app.smartspace.uitemplatedata.Text); } + public static final class BaseTemplateData.SubItemLoggingInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getFeatureType(); + method public int getInstanceId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo> CREATOR; + } + + public static final class BaseTemplateData.SubItemLoggingInfo.Builder { + ctor public BaseTemplateData.SubItemLoggingInfo.Builder(int, int); + method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo build(); + } + public final class CarouselTemplateData extends android.app.smartspace.uitemplatedata.BaseTemplateData { method @Nullable public android.app.smartspace.uitemplatedata.TapAction getCarouselAction(); method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.CarouselTemplateData.CarouselItem> getCarouselItems(); @@ -2408,6 +2435,7 @@ package android.app.smartspace.uitemplatedata { method @Nullable public android.content.Intent getIntent(); method @Nullable public android.app.PendingIntent getPendingIntent(); method @Nullable public android.os.UserHandle getUserHandle(); + method public boolean shouldShowOnLockscreen(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.TapAction> CREATOR; } @@ -2418,6 +2446,7 @@ package android.app.smartspace.uitemplatedata { method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setIntent(@NonNull android.content.Intent); method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setPendingIntent(@NonNull android.app.PendingIntent); + method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setShouldShowOnLockscreen(@NonNull boolean); method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setUserHandle(@Nullable android.os.UserHandle); } @@ -2432,7 +2461,6 @@ package android.app.smartspace.uitemplatedata { public static final class Text.Builder { ctor public Text.Builder(@NonNull CharSequence); - ctor public Text.Builder(@NonNull CharSequence, @NonNull android.text.TextUtils.TruncateAt); method @NonNull public android.app.smartspace.uitemplatedata.Text build(); method @NonNull public android.app.smartspace.uitemplatedata.Text.Builder setMaxLines(int); method @NonNull public android.app.smartspace.uitemplatedata.Text.Builder setTruncateAtType(@NonNull android.text.TextUtils.TruncateAt); @@ -2507,9 +2535,10 @@ package android.app.time { package android.app.usage { public final class BroadcastResponseStats implements android.os.Parcelable { - ctor public BroadcastResponseStats(@NonNull String); + ctor public BroadcastResponseStats(@NonNull String, @IntRange(from=1) long); method public int describeContents(); method @IntRange(from=0) public int getBroadcastsDispatchedCount(); + method @IntRange(from=1) public long getId(); method @IntRange(from=0) public int getNotificationsCancelledCount(); method @IntRange(from=0) public int getNotificationsPostedCount(); method @IntRange(from=0) public int getNotificationsUpdatedCount(); @@ -2566,13 +2595,13 @@ package android.app.usage { } public final class UsageStatsManager { - method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void clearBroadcastResponseStats(@NonNull String, @IntRange(from=1) long); + method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void clearBroadcastResponseStats(@Nullable String, @IntRange(from=0) long); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets(); method @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long getLastTimeAnyComponentUsed(@NonNull String); method public int getUsageSource(); method @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES) public void onCarrierPrivilegedAppsChanged(); - method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.BroadcastResponseStats queryBroadcastResponseStats(@NonNull String, @IntRange(from=1) long); + method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.List<android.app.usage.BroadcastResponseStats> queryBroadcastResponseStats(@Nullable String, @IntRange(from=0) long); method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @Nullable android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent); @@ -5849,8 +5878,8 @@ package android.location { method @IntRange(from=0, to=1023) public int getIssueOfDataClock(); method @IntRange(from=0, to=255) public int getIssueOfDataEphemeris(); method @Nullable public android.location.SatellitePvt.PositionEcef getPositionEcef(); - method @IntRange(from=0, to=604784) public int getTimeOfClock(); - method @IntRange(from=0, to=604784) public int getTimeOfEphemeris(); + method @IntRange(from=0) public long getTimeOfClock(); + method @IntRange(from=0) public long getTimeOfEphemeris(); method @FloatRange public double getTropoDelayMeters(); method @Nullable public android.location.SatellitePvt.VelocityEcef getVelocityEcef(); method public boolean hasIono(); @@ -5877,8 +5906,8 @@ package android.location { method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataClock(@IntRange(from=0, to=1023) int); method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataEphemeris(@IntRange(from=0, to=255) int); method @NonNull public android.location.SatellitePvt.Builder setPositionEcef(@NonNull android.location.SatellitePvt.PositionEcef); - method @NonNull public android.location.SatellitePvt.Builder setTimeOfClock(@IntRange(from=0, to=604784) int); - method @NonNull public android.location.SatellitePvt.Builder setTimeOfEphemeris(@IntRange(from=0, to=604784) int); + method @NonNull public android.location.SatellitePvt.Builder setTimeOfClock(@IntRange(from=0) long); + method @NonNull public android.location.SatellitePvt.Builder setTimeOfEphemeris(@IntRange(from=0) int); method @NonNull public android.location.SatellitePvt.Builder setTropoDelayMeters(@FloatRange(from=0.0f, to=100.0f) double); method @NonNull public android.location.SatellitePvt.Builder setVelocityEcef(@NonNull android.location.SatellitePvt.VelocityEcef); } @@ -6075,7 +6104,7 @@ package android.media { method public boolean isAudioServerRunning(); method public boolean isHdmiSystemAudioSupported(); method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable(); - method public static boolean isUltrasoundSupported(); + method @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public boolean isUltrasoundSupported(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback); @@ -10847,7 +10876,7 @@ package android.service.attention { method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onCancelAttentionCheck(@NonNull android.service.attention.AttentionService.AttentionCallback); method public abstract void onCheckAttention(@NonNull android.service.attention.AttentionService.AttentionCallback); - method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityCallback); + method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityUpdateCallback); method public void onStopProximityUpdates(); field public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; // 0x6 field public static final int ATTENTION_FAILURE_CANCELLED = 3; // 0x3 @@ -10865,7 +10894,7 @@ package android.service.attention { method public void onSuccess(int, long); } - public static final class AttentionService.ProximityCallback { + public static final class AttentionService.ProximityUpdateCallback { method public void onProximityUpdate(double); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 9455f8f8856b..e5165449b4bb 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -357,6 +357,7 @@ package android.app { public final class PictureInPictureParams implements android.os.Parcelable { method public java.util.List<android.app.RemoteAction> getActions(); method public float getAspectRatio(); + method @Nullable public android.app.RemoteAction getCloseAction(); method public float getExpandedAspectRatio(); method public android.graphics.Rect getSourceRectHint(); method @Nullable public CharSequence getSubtitle(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 50121218cb4e..5ddaa80b1a82 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -350,6 +350,10 @@ public class ActivityOptions extends ComponentOptions { /** See {@link #setTransientLaunch()}. */ private static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch"; + /** see {@link #makeLaunchIntoPip(PictureInPictureParams)}. */ + private static final String KEY_LAUNCH_INTO_PIP_PARAMS = + "android.activity.launchIntoPipParams"; + /** * @see #setLaunchCookie * @hide @@ -444,6 +448,7 @@ public class ActivityOptions extends ComponentOptions { private boolean mRemoveWithTaskOrganizer; private boolean mLaunchedFromBubble; private boolean mTransientLaunch; + private PictureInPictureParams mLaunchIntoPipParams; /** * Create an ActivityOptions specifying a custom animation to run when @@ -1106,6 +1111,24 @@ public class ActivityOptions extends ComponentOptions { return opts; } + /** + * Creates an {@link ActivityOptions} instance that launch into picture-in-picture. + * This is normally used by a Host activity to start another activity that will directly enter + * picture-in-picture upon its creation. + * @param pictureInPictureParams {@link PictureInPictureParams} for launching the Activity to + * picture-in-picture mode. + */ + @NonNull + public static ActivityOptions makeLaunchIntoPip( + @NonNull PictureInPictureParams pictureInPictureParams) { + final ActivityOptions opts = new ActivityOptions(); + opts.mLaunchIntoPipParams = new PictureInPictureParams.Builder(pictureInPictureParams) + .setIsLaunchIntoPip(true) + .build(); + opts.mLaunchBounds = new Rect(pictureInPictureParams.getSourceRectHint()); + return opts; + } + /** @hide */ public boolean getLaunchTaskBehind() { return mAnimationType == ANIM_LAUNCH_TASK_BEHIND; @@ -1219,6 +1242,7 @@ public class ActivityOptions extends ComponentOptions { mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE); mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH); mSplashScreenStyle = opts.getInt(KEY_SPLASH_SCREEN_STYLE); + mLaunchIntoPipParams = opts.getParcelable(KEY_LAUNCH_INTO_PIP_PARAMS); } /** @@ -1556,6 +1580,23 @@ public class ActivityOptions extends ComponentOptions { mLaunchWindowingMode = windowingMode; } + /** + * @return {@link PictureInPictureParams} used to launch into PiP mode. + * @hide + */ + public PictureInPictureParams getLaunchIntoPipParams() { + return mLaunchIntoPipParams; + } + + /** + * @return {@code true} if this instance is used to launch into PiP mode. + * @hide + */ + public boolean isLaunchIntoPip() { + return mLaunchIntoPipParams != null + && mLaunchIntoPipParams.isLaunchIntoPip(); + } + /** @hide */ public int getLaunchActivityType() { return mLaunchActivityType; @@ -1867,6 +1908,7 @@ public class ActivityOptions extends ComponentOptions { mAnimationFinishedListener = otherOptions.mAnimationFinishedListener; mSpecsFuture = otherOptions.mSpecsFuture; mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter; + mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams; } /** @@ -2039,6 +2081,9 @@ public class ActivityOptions extends ComponentOptions { if (mSplashScreenStyle != 0) { b.putInt(KEY_SPLASH_SCREEN_STYLE, mSplashScreenStyle); } + if (mLaunchIntoPipParams != null) { + b.putParcelable(KEY_LAUNCH_INTO_PIP_PARAMS, mLaunchIntoPipParams); + } return b; } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0d1bc05df67b..7c7c7ef382c1 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2687,7 +2687,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS - getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW + AppOpsManager.MODE_DEFAULT, // SYSTEM_ALERT_WINDOW /*Overridden in opToDefaultMode()*/ AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS AppOpsManager.MODE_ALLOWED, // CAMERA AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO @@ -3011,6 +3011,8 @@ public class AppOpsManager { private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops"; private static final String DEBUG_LOGGING_TAG = "AppOpsManager"; + private static volatile Integer sOpSystemAlertWindowDefaultMode; + /** * Retrieve the op switch that controls the given operation. * @hide @@ -3109,6 +3111,9 @@ public class AppOpsManager { * @hide */ public static @Mode int opToDefaultMode(int op) { + if (op == OP_SYSTEM_ALERT_WINDOW) { + return getSystemAlertWindowDefault(); + } return sOpDefaultMode[op]; } @@ -10113,6 +10118,11 @@ public class AppOpsManager { } private static int getSystemAlertWindowDefault() { + // This is indeed racy but we aren't expecting the result to change so it's not worth + // the synchronization. + if (sOpSystemAlertWindowDefaultMode != null) { + return sOpSystemAlertWindowDefaultMode; + } final Context context = ActivityThread.currentApplication(); if (context == null) { return AppOpsManager.MODE_DEFAULT; @@ -10123,10 +10133,11 @@ public class AppOpsManager { // TVs are constantly plugged in and has less concern for memory/power if (ActivityManager.isLowRamDeviceStatic() && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) { - return AppOpsManager.MODE_IGNORED; + sOpSystemAlertWindowDefaultMode = AppOpsManager.MODE_IGNORED; + } else { + sOpSystemAlertWindowDefaultMode = AppOpsManager.MODE_DEFAULT; } - - return AppOpsManager.MODE_DEFAULT; + return sOpSystemAlertWindowDefaultMode; } /** diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index 0a74bba8fee3..2d2788ca91a3 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -51,6 +51,9 @@ public final class PictureInPictureParams implements Parcelable { private List<RemoteAction> mUserActions; @Nullable + private RemoteAction mCloseAction; + + @Nullable private Rect mSourceRectHint; private Boolean mAutoEnterEnabled; @@ -61,6 +64,27 @@ public final class PictureInPictureParams implements Parcelable { private CharSequence mSubtitle; + private Boolean mIsLaunchIntoPip; + + /** Default constructor */ + public Builder() {} + + /** + * Copy constructor + * @param original {@link PictureInPictureParams} instance this builder is built upon. + */ + public Builder(@NonNull PictureInPictureParams original) { + mAspectRatio = original.mAspectRatio; + mUserActions = original.mUserActions; + mCloseAction = original.mCloseAction; + mSourceRectHint = original.mSourceRectHint; + mAutoEnterEnabled = original.mAutoEnterEnabled; + mSeamlessResizeEnabled = original.mSeamlessResizeEnabled; + mTitle = original.mTitle; + mSubtitle = original.mSubtitle; + mIsLaunchIntoPip = original.mIsLaunchIntoPip; + } + /** * Sets the aspect ratio. This aspect ratio is defined as the desired width / height, and * does not change upon device rotation. @@ -114,6 +138,25 @@ public final class PictureInPictureParams implements Parcelable { } /** + * Sets a close action that should be invoked before the default close PiP action. The + * custom action must close the activity quickly using {@link Activity#finish()}. + * Otherwise, the system will forcibly close the PiP as if no custom close action was + * provided. + * + * If the action matches one set via {@link PictureInPictureParams.Builder#setActions(List)} + * it may be shown in place of that custom action in the menu. + * + * @param action to replace the system close action + * @return this builder instance. + * @see RemoteAction + */ + @NonNull + public Builder setCloseAction(@Nullable RemoteAction action) { + mCloseAction = action; + return this; + } + + /** * Sets the source bounds hint. These bounds are only used when an activity first enters * picture-in-picture, and describe the bounds in window coordinates of activity entering * picture-in-picture that will be visible following the transition. For the best effect, @@ -199,6 +242,20 @@ public final class PictureInPictureParams implements Parcelable { return this; } + /** + * Sets whether the built {@link PictureInPictureParams} represents a launch into + * picture-in-picture request. + * + * This property is {@code false} by default. + * @param isLaunchIntoPip {@code true} if the built instance represents a launch into + * picture-in-picture request + * @return this builder instance. + */ + @NonNull + Builder setIsLaunchIntoPip(boolean isLaunchIntoPip) { + mIsLaunchIntoPip = isLaunchIntoPip; + return this; + } /** * @return an immutable {@link PictureInPictureParams} to be used when entering or updating @@ -209,8 +266,9 @@ public final class PictureInPictureParams implements Parcelable { */ public PictureInPictureParams build() { PictureInPictureParams params = new PictureInPictureParams(mAspectRatio, - mExpandedAspectRatio, mUserActions, - mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle); + mExpandedAspectRatio, mUserActions, mCloseAction, mSourceRectHint, + mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle, + mIsLaunchIntoPip); return params; } } @@ -234,6 +292,12 @@ public final class PictureInPictureParams implements Parcelable { private List<RemoteAction> mUserActions; /** + * Action to replace the system close action. + */ + @Nullable + private RemoteAction mCloseAction; + + /** * The source bounds hint used when entering picture-in-picture, relative to the window bounds. * We can use this internally for the transition into picture-in-picture to ensure that a * particular source rect is visible throughout the whole transition. @@ -266,6 +330,13 @@ public final class PictureInPictureParams implements Parcelable { @Nullable private CharSequence mSubtitle; + /** + * Whether this {@link PictureInPictureParams} represents a launch into + * picture-in-picture request. + * {@link #isLaunchIntoPip()} defaults to {@code false} is this is not set. + */ + private Boolean mIsLaunchIntoPip; + /** {@hide} */ PictureInPictureParams() { } @@ -278,6 +349,7 @@ public final class PictureInPictureParams implements Parcelable { mUserActions = new ArrayList<>(); in.readTypedList(mUserActions, RemoteAction.CREATOR); } + mCloseAction = in.readTypedObject(RemoteAction.CREATOR); if (in.readInt() != 0) { mSourceRectHint = Rect.CREATOR.createFromParcel(in); } @@ -293,20 +365,26 @@ public final class PictureInPictureParams implements Parcelable { if (in.readInt() != 0) { mSubtitle = in.readCharSequence(); } + if (in.readInt() != 0) { + mIsLaunchIntoPip = in.readBoolean(); + } } /** {@hide} */ PictureInPictureParams(Rational aspectRatio, Rational expandedAspectRatio, - List<RemoteAction> actions, Rect sourceRectHint, Boolean autoEnterEnabled, - Boolean seamlessResizeEnabled, CharSequence title, CharSequence subtitle) { + List<RemoteAction> actions, RemoteAction closeAction, Rect sourceRectHint, + Boolean autoEnterEnabled, Boolean seamlessResizeEnabled, CharSequence title, + CharSequence subtitle, Boolean isLaunchIntoPip) { mAspectRatio = aspectRatio; mExpandedAspectRatio = expandedAspectRatio; mUserActions = actions; + mCloseAction = closeAction; mSourceRectHint = sourceRectHint; mAutoEnterEnabled = autoEnterEnabled; mSeamlessResizeEnabled = seamlessResizeEnabled; mTitle = title; mSubtitle = subtitle; + mIsLaunchIntoPip = isLaunchIntoPip; } /** @@ -314,10 +392,10 @@ public final class PictureInPictureParams implements Parcelable { * @hide */ public PictureInPictureParams(PictureInPictureParams other) { - this(other.mAspectRatio, other.mExpandedAspectRatio, other.mUserActions, + this(other.mAspectRatio, other.mExpandedAspectRatio, other.mUserActions, other.mCloseAction, other.hasSourceBoundsHint() ? new Rect(other.getSourceRectHint()) : null, - other.mAutoEnterEnabled, other.mSeamlessResizeEnabled, other.mTitle, - other.mSubtitle); + other.mAutoEnterEnabled, other.mSeamlessResizeEnabled, + other.mTitle, other.mSubtitle, other.mIsLaunchIntoPip); } /** @@ -335,6 +413,9 @@ public final class PictureInPictureParams implements Parcelable { if (otherArgs.hasSetActions()) { mUserActions = otherArgs.mUserActions; } + if (otherArgs.hasSetCloseAction()) { + mCloseAction = otherArgs.mCloseAction; + } if (otherArgs.hasSourceBoundsHint()) { mSourceRectHint = new Rect(otherArgs.getSourceRectHint()); } @@ -350,6 +431,9 @@ public final class PictureInPictureParams implements Parcelable { if (otherArgs.hasSetSubtitle()) { mSubtitle = otherArgs.mSubtitle; } + if (otherArgs.mIsLaunchIntoPip != null) { + mIsLaunchIntoPip = otherArgs.mIsLaunchIntoPip; + } } /** @@ -415,7 +499,26 @@ public final class PictureInPictureParams implements Parcelable { } /** + * @return the close action. + * @hide + */ + @TestApi + @Nullable + public RemoteAction getCloseAction() { + return mCloseAction; + } + + /** + * @return whether the close action was set. + * @hide + */ + public boolean hasSetCloseAction() { + return mCloseAction != null; + } + + /** * Truncates the set of actions to the given {@param size}. + * * @hide */ public void truncateActions(int size) { @@ -495,14 +598,22 @@ public final class PictureInPictureParams implements Parcelable { } /** + * @return whether this {@link PictureInPictureParams} represents a launch into pip request. + * @hide + */ + public boolean isLaunchIntoPip() { + return mIsLaunchIntoPip == null ? false : mIsLaunchIntoPip; + } + + /** * @return True if no parameters are set * @hide */ public boolean empty() { - return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio() - && !hasSetExpandedAspectRatio() && mAutoEnterEnabled != null - && mSeamlessResizeEnabled != null && !hasSetTitle() - && !hasSetSubtitle(); + return !hasSourceBoundsHint() && !hasSetActions() && !hasSetCloseAction() + && !hasSetAspectRatio() && !hasSetExpandedAspectRatio() && mAutoEnterEnabled == null + && mSeamlessResizeEnabled == null && !hasSetTitle() + && !hasSetSubtitle() && mIsLaunchIntoPip == null; } @Override @@ -515,15 +626,18 @@ public final class PictureInPictureParams implements Parcelable { && Objects.equals(mAspectRatio, that.mAspectRatio) && Objects.equals(mExpandedAspectRatio, that.mExpandedAspectRatio) && Objects.equals(mUserActions, that.mUserActions) + && Objects.equals(mCloseAction, that.mCloseAction) && Objects.equals(mSourceRectHint, that.mSourceRectHint) && Objects.equals(mTitle, that.mTitle) - && Objects.equals(mSubtitle, that.mSubtitle); + && Objects.equals(mSubtitle, that.mSubtitle) + && Objects.equals(mIsLaunchIntoPip, that.mIsLaunchIntoPip); } @Override public int hashCode() { - return Objects.hash(mAspectRatio, mExpandedAspectRatio, mUserActions, mSourceRectHint, - mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle); + return Objects.hash(mAspectRatio, mExpandedAspectRatio, mUserActions, mCloseAction, + mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle, + mIsLaunchIntoPip); } @Override @@ -541,6 +655,9 @@ public final class PictureInPictureParams implements Parcelable { } else { out.writeInt(0); } + + out.writeTypedObject(mCloseAction, 0); + if (mSourceRectHint != null) { out.writeInt(1); mSourceRectHint.writeToParcel(out, 0); @@ -571,6 +688,12 @@ public final class PictureInPictureParams implements Parcelable { } else { out.writeInt(0); } + if (mIsLaunchIntoPip != null) { + out.writeInt(1); + out.writeBoolean(mIsLaunchIntoPip); + } else { + out.writeInt(0); + } } private void writeRationalToParcel(Rational rational, Parcel out) { @@ -597,10 +720,12 @@ public final class PictureInPictureParams implements Parcelable { + " expandedAspectRatio=" + mExpandedAspectRatio + " sourceRectHint=" + getSourceRectHint() + " hasSetActions=" + hasSetActions() + + " hasSetCloseAction=" + hasSetCloseAction() + " isAutoPipEnabled=" + isAutoEnterEnabled() + " isSeamlessResizeEnabled=" + isSeamlessResizeEnabled() + " title=" + getTitle() + " subtitle=" + getSubtitle() + + " isLaunchIntoPip=" + isLaunchIntoPip() + ")"; } diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index eca4170b5cd2..5c7c73c2d683 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -191,6 +191,13 @@ public class TaskInfo { public boolean preferDockBigOverlays; /** + * The task id of the host Task of the launch-into-pip Activity, i.e., it points to the Task + * the launch-into-pip Activity is originated from. + * @hide + */ + public int launchIntoPipHostTaskId; + + /** * The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of * (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS), * {@code null} otherwise. @@ -516,6 +523,7 @@ public class TaskInfo { topActivityType = source.readInt(); pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR); preferDockBigOverlays = source.readBoolean(); + launchIntoPipHostTaskId = source.readInt(); displayCutoutInsets = source.readTypedObject(Rect.CREATOR); topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); isResizeable = source.readBoolean(); @@ -562,6 +570,7 @@ public class TaskInfo { dest.writeInt(topActivityType); dest.writeTypedObject(pictureInPictureParams, flags); dest.writeBoolean(preferDockBigOverlays); + dest.writeInt(launchIntoPipHostTaskId); dest.writeTypedObject(displayCutoutInsets, flags); dest.writeTypedObject(topActivityInfo, flags); dest.writeBoolean(isResizeable); @@ -602,6 +611,7 @@ public class TaskInfo { + " topActivityType=" + topActivityType + " pictureInPictureParams=" + pictureInPictureParams + " preferDockBigOverlays=" + preferDockBigOverlays + + " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId + " displayCutoutSafeInsets=" + displayCutoutInsets + " topActivityInfo=" + topActivityInfo + " launchCookies=" + launchCookies diff --git a/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java index a07af689e06b..584b17605a87 100644 --- a/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java +++ b/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java @@ -33,6 +33,9 @@ import java.util.Objects; * <li> title_text (may contain a start drawable) </li> * <li> subtitle_text (may contain a start drawable) . supplemental_subtitle_text (may * contain a start drawable) </li> + * + * <li> supplemental_text (contain a start drawable) . do_not_disturb_view </li> + * Or * <li> next_alarm_text (contain a start drawable) + supplemental_alarm_text . * do_not_disturb_view </li> * </ul> @@ -77,6 +80,14 @@ public class BaseTemplateData implements Parcelable { private final TapAction mPrimaryTapAction; /** + * Primary logging info for the entire card. This will only be used when rendering a sub card + * within the base card. For the base card itself, BcSmartspaceCardLoggingInfo should be used, + * which has the display-specific info (e.g. display surface). + */ + @Nullable + private final SubItemLoggingInfo mPrimaryLoggingInfo; + + /** * Supplemental subtitle text and icon are shown at the second row following the subtitle text. * Mainly used for weather info on non-weather card. */ @@ -87,19 +98,47 @@ public class BaseTemplateData implements Parcelable { private final Icon mSupplementalSubtitleIcon; /** - * Tap action for the supplemental subtitle's text and icon. Will use the primary tap action if + * Tap action for the supplemental subtitle's text and icon. Uses the primary tap action if * not being set. */ @Nullable private final TapAction mSupplementalSubtitleTapAction; /** + * Logging info for the supplemental subtitle's are. Uses the primary logging info if not being + * set. + */ + @Nullable + private final SubItemLoggingInfo mSupplementalSubtitleLoggingInfo; + + @Nullable + private final Text mSupplementalText; + + @Nullable + private final Icon mSupplementalIcon; + + @Nullable + private final TapAction mSupplementalTapAction; + + /** + * Logging info for the supplemental line. Uses the primary logging info if not being set. + */ + @Nullable + private final SubItemLoggingInfo mSupplementalLoggingInfo; + + /** * Supplemental alarm text is specifically used for holiday alarm, which is appended to "next * alarm". */ @Nullable private final Text mSupplementalAlarmText; + /** + * The layout weight info for the card, which indicates how much space it should occupy on the + * screen. Default weight is 0. + */ + private final int mLayoutWeight; + BaseTemplateData(@NonNull Parcel in) { mTemplateType = in.readInt(); mTitleText = in.readTypedObject(Text.CREATOR); @@ -107,10 +146,17 @@ public class BaseTemplateData implements Parcelable { mSubtitleText = in.readTypedObject(Text.CREATOR); mSubtitleIcon = in.readTypedObject(Icon.CREATOR); mPrimaryTapAction = in.readTypedObject(TapAction.CREATOR); + mPrimaryLoggingInfo = in.readTypedObject(SubItemLoggingInfo.CREATOR); mSupplementalSubtitleText = in.readTypedObject(Text.CREATOR); mSupplementalSubtitleIcon = in.readTypedObject(Icon.CREATOR); mSupplementalSubtitleTapAction = in.readTypedObject(TapAction.CREATOR); + mSupplementalSubtitleLoggingInfo = in.readTypedObject(SubItemLoggingInfo.CREATOR); + mSupplementalText = in.readTypedObject(Text.CREATOR); + mSupplementalIcon = in.readTypedObject(Icon.CREATOR); + mSupplementalTapAction = in.readTypedObject(TapAction.CREATOR); + mSupplementalLoggingInfo = in.readTypedObject(SubItemLoggingInfo.CREATOR); mSupplementalAlarmText = in.readTypedObject(Text.CREATOR); + mLayoutWeight = in.readInt(); } /** @@ -123,20 +169,34 @@ public class BaseTemplateData implements Parcelable { @Nullable Text subtitleText, @Nullable Icon subtitleIcon, @Nullable TapAction primaryTapAction, + @Nullable SubItemLoggingInfo primaryLoggingInfo, @Nullable Text supplementalSubtitleText, @Nullable Icon supplementalSubtitleIcon, @Nullable TapAction supplementalSubtitleTapAction, - @Nullable Text supplementalAlarmText) { + @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo, + @Nullable Text supplementalText, + @Nullable Icon supplementalIcon, + @Nullable TapAction supplementalTapAction, + @Nullable SubItemLoggingInfo supplementalLoggingInfo, + @Nullable Text supplementalAlarmText, + int layoutWeight) { mTemplateType = templateType; mTitleText = titleText; mTitleIcon = titleIcon; mSubtitleText = subtitleText; mSubtitleIcon = subtitleIcon; mPrimaryTapAction = primaryTapAction; + mPrimaryLoggingInfo = primaryLoggingInfo; mSupplementalSubtitleText = supplementalSubtitleText; mSupplementalSubtitleIcon = supplementalSubtitleIcon; mSupplementalSubtitleTapAction = supplementalSubtitleTapAction; + mSupplementalSubtitleLoggingInfo = supplementalSubtitleLoggingInfo; + mSupplementalText = supplementalText; + mSupplementalIcon = supplementalIcon; + mSupplementalTapAction = supplementalTapAction; + mSupplementalLoggingInfo = supplementalLoggingInfo; mSupplementalAlarmText = supplementalAlarmText; + mLayoutWeight = layoutWeight; } /** Returns the template type. By default is UNDEFINED. */ @@ -175,6 +235,12 @@ public class BaseTemplateData implements Parcelable { return mPrimaryTapAction; } + /** Returns the card's primary logging info. */ + @Nullable + public SubItemLoggingInfo getPrimaryLoggingInfo() { + return mPrimaryLoggingInfo; + } + /** Returns the supplemental subtitle's text. */ @Nullable public Text getSupplementalSubtitleText() { @@ -193,12 +259,47 @@ public class BaseTemplateData implements Parcelable { return mSupplementalSubtitleTapAction; } + /** Returns the card's supplemental title's logging info. */ + @Nullable + public SubItemLoggingInfo getSupplementalSubtitleLoggingInfo() { + return mSupplementalSubtitleLoggingInfo; + } + + /** Returns the supplemental text. */ + @Nullable + public Text getSupplementalText() { + return mSupplementalText; + } + + /** Returns the supplemental icon. */ + @Nullable + public Icon getSupplementalIcon() { + return mSupplementalIcon; + } + + /** Returns the supplemental line's tap action. Can be null if not being set. */ + @Nullable + public TapAction getSupplementalTapAction() { + return mSupplementalTapAction; + } + + /** Returns the card's supplemental line logging info. */ + @Nullable + public SubItemLoggingInfo getSupplementalLoggingInfo() { + return mSupplementalLoggingInfo; + } + /** Returns the supplemental alarm text. */ @Nullable public Text getSupplementalAlarmText() { return mSupplementalAlarmText; } + /** Returns the card layout weight info. Default weight is 0. */ + public int getLayoutWeight() { + return mLayoutWeight; + } + /** * @see Parcelable.Creator */ @@ -229,10 +330,17 @@ public class BaseTemplateData implements Parcelable { out.writeTypedObject(mSubtitleText, flags); out.writeTypedObject(mSubtitleIcon, flags); out.writeTypedObject(mPrimaryTapAction, flags); + out.writeTypedObject(mPrimaryLoggingInfo, flags); out.writeTypedObject(mSupplementalSubtitleText, flags); out.writeTypedObject(mSupplementalSubtitleIcon, flags); out.writeTypedObject(mSupplementalSubtitleTapAction, flags); + out.writeTypedObject(mSupplementalSubtitleLoggingInfo, flags); + out.writeTypedObject(mSupplementalText, flags); + out.writeTypedObject(mSupplementalIcon, flags); + out.writeTypedObject(mSupplementalTapAction, flags); + out.writeTypedObject(mSupplementalLoggingInfo, flags); out.writeTypedObject(mSupplementalAlarmText, flags); + out.writeInt(mLayoutWeight); } @Override @@ -246,19 +354,31 @@ public class BaseTemplateData implements Parcelable { && SmartspaceUtils.isEqual(mSubtitleText, that.mSubtitleText) && Objects.equals(mSubtitleIcon, that.mSubtitleIcon) && Objects.equals(mPrimaryTapAction, that.mPrimaryTapAction) + && Objects.equals(mPrimaryLoggingInfo, that.mPrimaryLoggingInfo) && SmartspaceUtils.isEqual(mSupplementalSubtitleText, that.mSupplementalSubtitleText) && Objects.equals(mSupplementalSubtitleIcon, that.mSupplementalSubtitleIcon) && Objects.equals(mSupplementalSubtitleTapAction, that.mSupplementalSubtitleTapAction) - && SmartspaceUtils.isEqual(mSupplementalAlarmText, that.mSupplementalAlarmText); + && Objects.equals(mSupplementalSubtitleLoggingInfo, + that.mSupplementalSubtitleLoggingInfo) + && SmartspaceUtils.isEqual(mSupplementalText, + that.mSupplementalText) + && Objects.equals(mSupplementalIcon, that.mSupplementalIcon) + && Objects.equals(mSupplementalTapAction, that.mSupplementalTapAction) + && Objects.equals(mSupplementalLoggingInfo, that.mSupplementalLoggingInfo) + && SmartspaceUtils.isEqual(mSupplementalAlarmText, that.mSupplementalAlarmText) + && mLayoutWeight == that.mLayoutWeight; } @Override public int hashCode() { return Objects.hash(mTemplateType, mTitleText, mTitleIcon, mSubtitleText, mSubtitleIcon, - mPrimaryTapAction, mSupplementalSubtitleText, mSupplementalSubtitleIcon, - mSupplementalSubtitleTapAction, mSupplementalAlarmText); + mPrimaryTapAction, mPrimaryLoggingInfo, mSupplementalSubtitleText, + mSupplementalSubtitleIcon, mSupplementalSubtitleTapAction, + mSupplementalSubtitleLoggingInfo, + mSupplementalText, mSupplementalIcon, mSupplementalTapAction, + mSupplementalLoggingInfo, mSupplementalAlarmText, mLayoutWeight); } @Override @@ -270,10 +390,17 @@ public class BaseTemplateData implements Parcelable { + ", mSubtitleText=" + mSubtitleText + ", mSubTitleIcon=" + mSubtitleIcon + ", mPrimaryTapAction=" + mPrimaryTapAction + + ", mPrimaryLoggingInfo=" + mPrimaryLoggingInfo + ", mSupplementalSubtitleText=" + mSupplementalSubtitleText + ", mSupplementalSubtitleIcon=" + mSupplementalSubtitleIcon + ", mSupplementalSubtitleTapAction=" + mSupplementalSubtitleTapAction + + ", mSupplementalSubtitleLoggingInfo=" + mSupplementalSubtitleLoggingInfo + + ", mSupplementalText=" + mSupplementalText + + ", mSupplementalIcon=" + mSupplementalIcon + + ", mSupplementalTapAction=" + mSupplementalTapAction + + ", mSupplementalLoggingInfo=" + mSupplementalLoggingInfo + ", mSupplementalAlarmText=" + mSupplementalAlarmText + + ", mLayoutWeight=" + mLayoutWeight + '}'; } @@ -292,18 +419,26 @@ public class BaseTemplateData implements Parcelable { private Text mSubtitleText; private Icon mSubtitleIcon; private TapAction mPrimaryTapAction; + private SubItemLoggingInfo mPrimaryLoggingInfo; private Text mSupplementalSubtitleText; private Icon mSupplementalSubtitleIcon; private TapAction mSupplementalSubtitleTapAction; + private SubItemLoggingInfo mSupplementalSubtitleLoggingInfo; + private Text mSupplementalText; + private Icon mSupplementalIcon; + private TapAction mSupplementalTapAction; + private SubItemLoggingInfo mSupplementalLoggingInfo; private Text mSupplementalAlarmText; + private int mLayoutWeight; /** - * A builder for {@link BaseTemplateData}. + * A builder for {@link BaseTemplateData}. By default sets the layout weight to be 0. * * @param templateType the {@link UiTemplateType} of this template data. */ public Builder(@UiTemplateType int templateType) { mTemplateType = templateType; + mLayoutWeight = 0; } /** Should ONLY be used by the subclasses */ @@ -351,6 +486,13 @@ public class BaseTemplateData implements Parcelable { /** Should ONLY be used by the subclasses */ @Nullable @SuppressLint("GetterOnBuilder") + SubItemLoggingInfo getPrimaryLoggingInfo() { + return mPrimaryLoggingInfo; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") Text getSupplementalSubtitleText() { return mSupplementalSubtitleText; } @@ -372,10 +514,51 @@ public class BaseTemplateData implements Parcelable { /** Should ONLY be used by the subclasses */ @Nullable @SuppressLint("GetterOnBuilder") + SubItemLoggingInfo getSupplementalSubtitleLoggingInfo() { + return mSupplementalSubtitleLoggingInfo; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + Text getSupplementalText() { + return mSupplementalText; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + Icon getSupplementalIcon() { + return mSupplementalIcon; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + TapAction getSupplementalTapAction() { + return mSupplementalTapAction; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + SubItemLoggingInfo getSupplementalLoggingInfo() { + return mSupplementalLoggingInfo; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") Text getSupplementalAlarmText() { return mSupplementalAlarmText; } + /** Should ONLY be used by the subclasses */ + @SuppressLint("GetterOnBuilder") + int getLayoutWeight() { + return mLayoutWeight; + } + /** * Sets the card title. */ @@ -422,6 +605,15 @@ public class BaseTemplateData implements Parcelable { } /** + * Sets the card primary logging info. + */ + @NonNull + public Builder setPrimaryLoggingInfo(@NonNull SubItemLoggingInfo primaryLoggingInfo) { + mPrimaryLoggingInfo = primaryLoggingInfo; + return this; + } + + /** * Sets the supplemental subtitle text. */ @NonNull @@ -443,8 +635,7 @@ public class BaseTemplateData implements Parcelable { /** * Sets the supplemental subtitle tap action. {@code mPrimaryTapAction} will be used if not - * being - * set. + * being set. */ @NonNull public Builder setSupplementalSubtitleTapAction( @@ -454,6 +645,54 @@ public class BaseTemplateData implements Parcelable { } /** + * Sets the card supplemental title's logging info. + */ + @NonNull + public Builder setSupplementalSubtitleLoggingInfo( + @NonNull SubItemLoggingInfo supplementalSubtitleLoggingInfo) { + mSupplementalSubtitleLoggingInfo = supplementalSubtitleLoggingInfo; + return this; + } + + /** + * Sets the supplemental text. + */ + @NonNull + public Builder setSupplementalText(@NonNull Text supplementalText) { + mSupplementalText = supplementalText; + return this; + } + + /** + * Sets the supplemental icon. + */ + @NonNull + public Builder setSupplementalIcon(@NonNull Icon supplementalIcon) { + mSupplementalIcon = supplementalIcon; + return this; + } + + /** + * Sets the supplemental line tap action. {@code mPrimaryTapAction} will be used if not + * being set. + */ + @NonNull + public Builder setSupplementalTapAction(@NonNull TapAction supplementalTapAction) { + mSupplementalTapAction = supplementalTapAction; + return this; + } + + /** + * Sets the card supplemental line's logging info. + */ + @NonNull + public Builder setSupplementalLoggingInfo( + @NonNull SubItemLoggingInfo supplementalLoggingInfo) { + mSupplementalLoggingInfo = supplementalLoggingInfo; + return this; + } + + /** * Sets the supplemental alarm text. */ @NonNull @@ -463,14 +702,136 @@ public class BaseTemplateData implements Parcelable { } /** + * Sets the layout weight. + */ + @NonNull + public Builder setLayoutWeight(int layoutWeight) { + mLayoutWeight = layoutWeight; + return this; + } + + /** * Builds a new SmartspaceDefaultUiTemplateData instance. */ @NonNull public BaseTemplateData build() { return new BaseTemplateData(mTemplateType, mTitleText, mTitleIcon, - mSubtitleText, mSubtitleIcon, mPrimaryTapAction, mSupplementalSubtitleText, - mSupplementalSubtitleIcon, mSupplementalSubtitleTapAction, - mSupplementalAlarmText); + mSubtitleText, mSubtitleIcon, mPrimaryTapAction, + mPrimaryLoggingInfo, + mSupplementalSubtitleText, mSupplementalSubtitleIcon, + mSupplementalSubtitleTapAction, mSupplementalSubtitleLoggingInfo, + mSupplementalText, mSupplementalIcon, + mSupplementalTapAction, mSupplementalLoggingInfo, + mSupplementalAlarmText, mLayoutWeight); + } + } + + /** + * Holds all the logging info needed for a sub item within the base card. For example, the + * supplemental-subtitle part should have its own logging info. + */ + public static final class SubItemLoggingInfo implements Parcelable { + + /** A unique instance id for the sub item. */ + private final int mInstanceId; + + /** The feature type for this sub item. */ + private final int mFeatureType; + + SubItemLoggingInfo(@NonNull Parcel in) { + mInstanceId = in.readInt(); + mFeatureType = in.readInt(); + } + + private SubItemLoggingInfo(int instanceId, int featureType) { + mInstanceId = instanceId; + mFeatureType = featureType; + } + + public int getInstanceId() { + return mInstanceId; + } + + public int getFeatureType() { + return mFeatureType; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<SubItemLoggingInfo> CREATOR = + new Creator<SubItemLoggingInfo>() { + @Override + public SubItemLoggingInfo createFromParcel(Parcel in) { + return new SubItemLoggingInfo(in); + } + + @Override + public SubItemLoggingInfo[] newArray(int size) { + return new SubItemLoggingInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mInstanceId); + out.writeInt(mFeatureType); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SubItemLoggingInfo)) return false; + SubItemLoggingInfo that = (SubItemLoggingInfo) o; + return mInstanceId == that.mInstanceId && mFeatureType == that.mFeatureType; + } + + @Override + public int hashCode() { + return Objects.hash(mInstanceId, mFeatureType); + } + + @Override + public String toString() { + return "SubItemLoggingInfo{" + + "mInstanceId=" + mInstanceId + + ", mFeatureType=" + mFeatureType + + '}'; + } + + /** + * A builder for {@link SubItemLoggingInfo} object. + * + * @hide + */ + @SystemApi + public static final class Builder { + + private final int mInstanceId; + private final int mFeatureType; + + /** + * A builder for {@link SubItemLoggingInfo}. + * + * @param instanceId A unique instance id for the sub item + * @param featureType The feature type for this sub item + */ + public Builder(int instanceId, int featureType) { + mInstanceId = instanceId; + mFeatureType = featureType; + } + + /** Builds a new {@link SubItemLoggingInfo} instance. */ + @NonNull + public SubItemLoggingInfo build() { + return new SubItemLoggingInfo(mInstanceId, mFeatureType); + } } } } diff --git a/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java index feb1c34337fb..fbdb7be3de7d 100644 --- a/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java +++ b/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java @@ -59,17 +59,29 @@ public final class CarouselTemplateData extends BaseTemplateData { @Nullable Text titleText, @Nullable Icon titleIcon, @Nullable Text subtitleText, - @Nullable Icon subTitleIcon, + @Nullable Icon subtitleIcon, @Nullable TapAction primaryTapAction, + @Nullable SubItemLoggingInfo primaryLoggingInfo, @Nullable Text supplementalSubtitleText, @Nullable Icon supplementalSubtitleIcon, @Nullable TapAction supplementalSubtitleTapAction, + @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo, + @Nullable Text supplementalText, + @Nullable Icon supplementalIcon, + @Nullable TapAction supplementalTapAction, + @Nullable SubItemLoggingInfo supplementalLoggingInfo, @Nullable Text supplementalAlarmText, + int layoutWeight, @NonNull List<CarouselItem> carouselItems, @Nullable TapAction carouselAction) { - super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, - supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, - supplementalAlarmText); + super(templateType, titleText, titleIcon, subtitleText, subtitleIcon, + primaryTapAction, primaryLoggingInfo, + supplementalSubtitleText, supplementalSubtitleIcon, + supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo, + supplementalText, supplementalIcon, + supplementalTapAction, supplementalLoggingInfo, + supplementalAlarmText, layoutWeight); + mCarouselItems = carouselItems; mCarouselAction = carouselAction; } @@ -179,10 +191,14 @@ public final class CarouselTemplateData extends BaseTemplateData { } return new CarouselTemplateData(getTemplateType(), getTitleText(), - getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(), + getTitleIcon(), getSubtitleText(), getSubtitleIcon(), + getPrimaryTapAction(), getPrimaryLoggingInfo(), getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), - getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mCarouselItems, - mCarouselAction); + getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(), + getSupplementalText(), getSupplementalIcon(), + getSupplementalTapAction(), getSupplementalLoggingInfo(), + getSupplementalAlarmText(), getLayoutWeight(), + mCarouselItems, mCarouselAction); } } diff --git a/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java index 13091e2c530d..1d1306619d11 100644 --- a/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java +++ b/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java @@ -54,16 +54,27 @@ public final class CombinedCardsTemplateData extends BaseTemplateData { @Nullable Text titleText, @Nullable Icon titleIcon, @Nullable Text subtitleText, - @Nullable Icon subTitleIcon, + @Nullable Icon subtitleIcon, @Nullable TapAction primaryTapAction, + @Nullable SubItemLoggingInfo primaryLoggingInfo, @Nullable Text supplementalSubtitleText, @Nullable Icon supplementalSubtitleIcon, @Nullable TapAction supplementalSubtitleTapAction, + @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo, + @Nullable Text supplementalText, + @Nullable Icon supplementalIcon, + @Nullable TapAction supplementalTapAction, + @Nullable SubItemLoggingInfo supplementalLoggingInfo, @Nullable Text supplementalAlarmText, + int layoutWeight, @NonNull List<BaseTemplateData> combinedCardDataList) { - super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, - supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, - supplementalAlarmText); + super(templateType, titleText, titleIcon, subtitleText, subtitleIcon, + primaryTapAction, primaryLoggingInfo, + supplementalSubtitleText, supplementalSubtitleIcon, + supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo, + supplementalText, supplementalIcon, + supplementalTapAction, supplementalLoggingInfo, + supplementalAlarmText, layoutWeight); mCombinedCardDataList = combinedCardDataList; } @@ -151,9 +162,13 @@ public final class CombinedCardsTemplateData extends BaseTemplateData { throw new IllegalStateException("Please assign a value to all @NonNull args."); } return new CombinedCardsTemplateData(getTemplateType(), getTitleText(), - getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(), + getTitleIcon(), getSubtitleText(), getSubtitleIcon(), + getPrimaryTapAction(), getPrimaryLoggingInfo(), getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), - getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), + getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(), + getSupplementalText(), getSupplementalIcon(), + getSupplementalTapAction(), getSupplementalLoggingInfo(), + getSupplementalAlarmText(), getLayoutWeight(), mCombinedCardDataList); } } diff --git a/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java index eb56e93e6c1b..19177dfc7649 100644 --- a/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java +++ b/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java @@ -69,21 +69,33 @@ public final class HeadToHeadTemplateData extends BaseTemplateData { @Nullable Text titleText, @Nullable Icon titleIcon, @Nullable Text subtitleText, - @Nullable Icon subTitleIcon, + @Nullable Icon subtitleIcon, @Nullable TapAction primaryTapAction, + @Nullable SubItemLoggingInfo primaryLoggingInfo, @Nullable Text supplementalSubtitleText, @Nullable Icon supplementalSubtitleIcon, @Nullable TapAction supplementalSubtitleTapAction, + @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo, + @Nullable Text supplementalText, + @Nullable Icon supplementalIcon, + @Nullable TapAction supplementalTapAction, + @Nullable SubItemLoggingInfo supplementalLoggingInfo, @Nullable Text supplementalAlarmText, + int layoutWeight, @Nullable Text headToHeadTitle, @Nullable Icon headToHeadFirstCompetitorIcon, @Nullable Icon headToHeadSecondCompetitorIcon, @Nullable Text headToHeadFirstCompetitorText, @Nullable Text headToHeadSecondCompetitorText, @Nullable TapAction headToHeadAction) { - super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, - supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, - supplementalAlarmText); + super(templateType, titleText, titleIcon, subtitleText, subtitleIcon, + primaryTapAction, primaryLoggingInfo, + supplementalSubtitleText, supplementalSubtitleIcon, + supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo, + supplementalText, supplementalIcon, + supplementalTapAction, supplementalLoggingInfo, + supplementalAlarmText, layoutWeight); + mHeadToHeadTitle = headToHeadTitle; mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon; mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon; @@ -285,9 +297,13 @@ public final class HeadToHeadTemplateData extends BaseTemplateData { @NonNull public HeadToHeadTemplateData build() { return new HeadToHeadTemplateData(getTemplateType(), getTitleText(), - getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(), + getTitleIcon(), getSubtitleText(), getSubtitleIcon(), + getPrimaryTapAction(), getPrimaryLoggingInfo(), getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), - getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), + getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(), + getSupplementalText(), getSupplementalIcon(), + getSupplementalTapAction(), getSupplementalLoggingInfo(), + getSupplementalAlarmText(), getLayoutWeight(), mHeadToHeadTitle, mHeadToHeadFirstCompetitorIcon, mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText, diff --git a/core/java/android/app/smartspace/uitemplatedata/Icon.java b/core/java/android/app/smartspace/uitemplatedata/Icon.java index 2b1f420f0d1d..6bdc926e7cf3 100644 --- a/core/java/android/app/smartspace/uitemplatedata/Icon.java +++ b/core/java/android/app/smartspace/uitemplatedata/Icon.java @@ -69,7 +69,10 @@ public final class Icon implements Parcelable { return mContentDescription; } - /** Return shouldTint value. The default value is true. */ + /** + * Return shouldTint value, which means whether should tint the icon with the system's theme + * color. The default value is true. + */ public boolean shouldTint() { return mShouldTint; } @@ -155,7 +158,7 @@ public final class Icon implements Parcelable { } /** - * Sets should tint icon. + * Sets should tint icon with the system's theme color. */ @NonNull public Builder setShouldTint(boolean shouldTint) { diff --git a/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java index 9c8330d1fcc6..48af9c173f1d 100644 --- a/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java +++ b/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java @@ -62,18 +62,30 @@ public final class SubCardTemplateData extends BaseTemplateData { @Nullable Text titleText, @Nullable Icon titleIcon, @Nullable Text subtitleText, - @Nullable Icon subTitleIcon, + @Nullable Icon subtitleIcon, @Nullable TapAction primaryTapAction, + @Nullable SubItemLoggingInfo primaryLoggingInfo, @Nullable Text supplementalSubtitleText, @Nullable Icon supplementalSubtitleIcon, @Nullable TapAction supplementalSubtitleTapAction, + @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo, + @Nullable Text supplementalText, + @Nullable Icon supplementalIcon, + @Nullable TapAction supplementalTapAction, + @Nullable SubItemLoggingInfo supplementalLoggingInfo, @Nullable Text supplementalAlarmText, + int layoutWeight, @NonNull Icon subCardIcon, @Nullable Text subCardText, @Nullable TapAction subCardAction) { - super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, - supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, - supplementalAlarmText); + super(templateType, titleText, titleIcon, subtitleText, subtitleIcon, + primaryTapAction, primaryLoggingInfo, + supplementalSubtitleText, supplementalSubtitleIcon, + supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo, + supplementalText, supplementalIcon, + supplementalTapAction, supplementalLoggingInfo, + supplementalAlarmText, layoutWeight); + mSubCardIcon = subCardIcon; mSubCardText = subCardText; mSubCardAction = subCardAction; @@ -196,9 +208,14 @@ public final class SubCardTemplateData extends BaseTemplateData { @NonNull public SubCardTemplateData build() { return new SubCardTemplateData(getTemplateType(), getTitleText(), - getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(), + getTitleIcon(), getSubtitleText(), getSubtitleIcon(), + getPrimaryTapAction(), getPrimaryLoggingInfo(), getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), - getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubCardIcon, + getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(), + getSupplementalText(), getSupplementalIcon(), + getSupplementalTapAction(), getSupplementalLoggingInfo(), + getSupplementalAlarmText(), getLayoutWeight(), + mSubCardIcon, mSubCardText, mSubCardAction); } diff --git a/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java index 7df523858a76..38692cd19df4 100644 --- a/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java +++ b/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java @@ -63,18 +63,30 @@ public final class SubImageTemplateData extends BaseTemplateData { @Nullable Text titleText, @Nullable Icon titleIcon, @Nullable Text subtitleText, - @Nullable Icon subTitleIcon, + @Nullable Icon subtitleIcon, @Nullable TapAction primaryTapAction, + @Nullable SubItemLoggingInfo primaryLoggingInfo, @Nullable Text supplementalSubtitleText, @Nullable Icon supplementalSubtitleIcon, @Nullable TapAction supplementalSubtitleTapAction, + @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo, + @Nullable Text supplementalText, + @Nullable Icon supplementalIcon, + @Nullable TapAction supplementalTapAction, + @Nullable SubItemLoggingInfo supplementalLoggingInfo, @Nullable Text supplementalAlarmText, + int layoutWeight, @NonNull List<Text> subImageTexts, @NonNull List<Icon> subImages, @Nullable TapAction subImageAction) { - super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, - supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, - supplementalAlarmText); + super(templateType, titleText, titleIcon, subtitleText, subtitleIcon, + primaryTapAction, primaryLoggingInfo, + supplementalSubtitleText, supplementalSubtitleIcon, + supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo, + supplementalText, supplementalIcon, + supplementalTapAction, supplementalLoggingInfo, + supplementalAlarmText, layoutWeight); + mSubImageTexts = subImageTexts; mSubImages = subImages; mSubImageAction = subImageAction; @@ -193,9 +205,14 @@ public final class SubImageTemplateData extends BaseTemplateData { @NonNull public SubImageTemplateData build() { return new SubImageTemplateData(getTemplateType(), getTitleText(), - getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(), + getTitleIcon(), getSubtitleText(), getSubtitleIcon(), + getPrimaryTapAction(), getPrimaryLoggingInfo(), getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), - getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubImageTexts, + getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(), + getSupplementalText(), getSupplementalIcon(), + getSupplementalTapAction(), getSupplementalLoggingInfo(), + getSupplementalAlarmText(), getLayoutWeight(), + mSubImageTexts, mSubImages, mSubImageAction); } diff --git a/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java index 6f6034da55dd..b1535f1e3924 100644 --- a/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java +++ b/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java @@ -62,18 +62,30 @@ public final class SubListTemplateData extends BaseTemplateData { @Nullable Text titleText, @Nullable Icon titleIcon, @Nullable Text subtitleText, - @Nullable Icon subTitleIcon, + @Nullable Icon subtitleIcon, @Nullable TapAction primaryTapAction, + @Nullable SubItemLoggingInfo primaryLoggingInfo, @Nullable Text supplementalSubtitleText, @Nullable Icon supplementalSubtitleIcon, @Nullable TapAction supplementalSubtitleTapAction, + @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo, + @Nullable Text supplementalText, + @Nullable Icon supplementalIcon, + @Nullable TapAction supplementalTapAction, + @Nullable SubItemLoggingInfo supplementalLoggingInfo, @Nullable Text supplementalAlarmText, + int layoutWeight, @Nullable Icon subListIcon, @NonNull List<Text> subListTexts, @Nullable TapAction subListAction) { - super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, - supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, - supplementalAlarmText); + super(templateType, titleText, titleIcon, subtitleText, subtitleIcon, + primaryTapAction, primaryLoggingInfo, + supplementalSubtitleText, supplementalSubtitleIcon, + supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo, + supplementalText, supplementalIcon, + supplementalTapAction, supplementalLoggingInfo, + supplementalAlarmText, layoutWeight); + mSubListIcon = subListIcon; mSubListTexts = subListTexts; mSubListAction = subListAction; @@ -196,9 +208,14 @@ public final class SubListTemplateData extends BaseTemplateData { @NonNull public SubListTemplateData build() { return new SubListTemplateData(getTemplateType(), getTitleText(), - getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(), + getTitleIcon(), getSubtitleText(), getSubtitleIcon(), + getPrimaryTapAction(), getPrimaryLoggingInfo(), getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), - getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubListIcon, + getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(), + getSupplementalText(), getSupplementalIcon(), + getSupplementalTapAction(), getSupplementalLoggingInfo(), + getSupplementalAlarmText(), getLayoutWeight(), + mSubListIcon, mSubListTexts, mSubListAction); } diff --git a/core/java/android/app/smartspace/uitemplatedata/TapAction.java b/core/java/android/app/smartspace/uitemplatedata/TapAction.java index 83ff6abfa7dd..b8e1afb20ea3 100644 --- a/core/java/android/app/smartspace/uitemplatedata/TapAction.java +++ b/core/java/android/app/smartspace/uitemplatedata/TapAction.java @@ -58,7 +58,13 @@ public final class TapAction implements Parcelable { private final UserHandle mUserHandle; @Nullable - private Bundle mExtras; + private final Bundle mExtras; + + /** + * Whether the tap action's result should be shown on the lockscreen (e.g. turn off the + * flashlight can be done on LS bypassing the keyguard). Default value is false. + */ + private final boolean mShouldShowOnLockscreen; TapAction(@NonNull Parcel in) { mId = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); @@ -66,16 +72,18 @@ public final class TapAction implements Parcelable { mPendingIntent = in.readTypedObject(PendingIntent.CREATOR); mUserHandle = in.readTypedObject(UserHandle.CREATOR); mExtras = in.readBundle(); + mShouldShowOnLockscreen = in.readBoolean(); } private TapAction(@Nullable CharSequence id, @Nullable Intent intent, @Nullable PendingIntent pendingIntent, @Nullable UserHandle userHandle, - @Nullable Bundle extras) { + @Nullable Bundle extras, boolean shouldShowOnLockscreen) { mId = id; mIntent = intent; mPendingIntent = pendingIntent; mUserHandle = userHandle; mExtras = extras; + mShouldShowOnLockscreen = shouldShowOnLockscreen; } /** Returns the unique id of the tap action. */ @@ -110,6 +118,14 @@ public final class TapAction implements Parcelable { return mExtras; } + /** + * Whether the tap action's result should be shown on the lockscreen. If true, the tap action's + * handling should bypass the keyguard. Default value is false. + */ + public boolean shouldShowOnLockscreen() { + return mShouldShowOnLockscreen; + } + @Override public void writeToParcel(@NonNull Parcel out, int flags) { TextUtils.writeToParcel(mId, out, flags); @@ -117,6 +133,7 @@ public final class TapAction implements Parcelable { out.writeTypedObject(mPendingIntent, flags); out.writeTypedObject(mUserHandle, flags); out.writeBundle(mExtras); + out.writeBoolean(mShouldShowOnLockscreen); } @Override @@ -158,6 +175,7 @@ public final class TapAction implements Parcelable { + ", mPendingIntent=" + mPendingIntent + ", mUserHandle=" + mUserHandle + ", mExtras=" + mExtras + + ", mShouldShowOnLockscreen=" + mShouldShowOnLockscreen + '}'; } @@ -174,14 +192,16 @@ public final class TapAction implements Parcelable { private PendingIntent mPendingIntent; private UserHandle mUserHandle; private Bundle mExtras; + private boolean mShouldShowOnLockScreen; /** - * A builder for {@link TapAction}. + * A builder for {@link TapAction}. By default sets should_show_on_lockscreen to false. * * @param id A unique Id of this {@link TapAction}. */ public Builder(@NonNull CharSequence id) { mId = Objects.requireNonNull(id); + mShouldShowOnLockScreen = false; } /** @@ -222,6 +242,16 @@ public final class TapAction implements Parcelable { } /** + * Sets whether the tap action's result should be shown on the lockscreen, to bypass the + * keyguard when the tap action is triggered. + */ + @NonNull + public Builder setShouldShowOnLockscreen(@NonNull boolean shouldShowOnLockScreen) { + mShouldShowOnLockScreen = shouldShowOnLockScreen; + return this; + } + + /** * Builds a new SmartspaceTapAction instance. * * @throws IllegalStateException if the tap action is empty. @@ -231,7 +261,8 @@ public final class TapAction implements Parcelable { if (mIntent == null && mPendingIntent == null && mExtras == null) { throw new IllegalStateException("Please assign at least 1 valid tap field"); } - return new TapAction(mId, mIntent, mPendingIntent, mUserHandle, mExtras); + return new TapAction(mId, mIntent, mPendingIntent, mUserHandle, mExtras, + mShouldShowOnLockScreen); } } } diff --git a/core/java/android/app/smartspace/uitemplatedata/Text.java b/core/java/android/app/smartspace/uitemplatedata/Text.java index b733394a1ae6..e1afce7148ae 100644 --- a/core/java/android/app/smartspace/uitemplatedata/Text.java +++ b/core/java/android/app/smartspace/uitemplatedata/Text.java @@ -131,16 +131,6 @@ public final class Text implements Parcelable { } /** - * A builder for {@link Text} with specifying {@link TextUtils.TruncateAt} type, and by - * default set the max lines to 1. - */ - public Builder(@NonNull CharSequence text, @NonNull TextUtils.TruncateAt truncateAtType) { - mText = Objects.requireNonNull(text); - mTruncateAtType = Objects.requireNonNull(truncateAtType); - mMaxLines = 1; - } - - /** * Sets truncateAtType, where the text content should be truncated if not all the content * can be presented. */ diff --git a/core/java/android/app/usage/BroadcastResponseStats.java b/core/java/android/app/usage/BroadcastResponseStats.java index 5acc3dda9e48..e1d37e1b1ae0 100644 --- a/core/java/android/app/usage/BroadcastResponseStats.java +++ b/core/java/android/app/usage/BroadcastResponseStats.java @@ -23,6 +23,8 @@ import android.app.BroadcastOptions; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * Class containing a collection of stats related to response events started from an app * after receiving a broadcast. @@ -32,17 +34,30 @@ import android.os.Parcelable; @SystemApi public final class BroadcastResponseStats implements Parcelable { private final String mPackageName; + private final long mId; private int mBroadcastsDispatchedCount; private int mNotificationsPostedCount; private int mNotificationsUpdatedCount; private int mNotificationsCancelledCount; - public BroadcastResponseStats(@NonNull String packageName) { + /** + * Creates a new {@link BroadcastResponseStats} object that contain the stats for broadcasts + * with {@code id} (specified using + * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)} by the sender) that + * were sent to {@code packageName}. + * + * @param packageName the name of the package that broadcasts were sent to. + * @param id the ID specified by the sender using + * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}. + */ + public BroadcastResponseStats(@NonNull String packageName, @IntRange(from = 1) long id) { mPackageName = packageName; + mId = id; } private BroadcastResponseStats(@NonNull Parcel in) { mPackageName = in.readString8(); + mId = in.readLong(); mBroadcastsDispatchedCount = in.readInt(); mNotificationsPostedCount = in.readInt(); mNotificationsUpdatedCount = in.readInt(); @@ -58,6 +73,14 @@ public final class BroadcastResponseStats implements Parcelable { } /** + * @return the ID of the broadcasts that the stats in this object correspond to. + */ + @IntRange(from = 1) + public long getId() { + return mId; + } + + /** * Returns the total number of broadcasts that were dispatched to the app by the caller. * * <b> Note that the returned count will only include the broadcasts that the caller explicitly @@ -148,9 +171,35 @@ public final class BroadcastResponseStats implements Parcelable { } @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || !(obj instanceof BroadcastResponseStats)) { + return false; + } + final BroadcastResponseStats other = (BroadcastResponseStats) obj; + return this.mBroadcastsDispatchedCount == other.mBroadcastsDispatchedCount + && this.mNotificationsPostedCount == other.mNotificationsPostedCount + && this.mNotificationsUpdatedCount == other.mNotificationsUpdatedCount + && this.mNotificationsCancelledCount == other.mNotificationsCancelledCount + && this.mId == other.mId + && this.mPackageName.equals(other.mPackageName); + } + + @Override + public int hashCode() { + return Objects.hash(mPackageName, mId, mBroadcastsDispatchedCount, + mNotificationsPostedCount, mNotificationsUpdatedCount, + mNotificationsCancelledCount); + } + + @Override public @NonNull String toString() { return "stats {" - + "broadcastsSent=" + mBroadcastsDispatchedCount + + "package=" + mPackageName + + ",id=" + mId + + ",broadcastsSent=" + mBroadcastsDispatchedCount + ",notificationsPosted=" + mNotificationsPostedCount + ",notificationsUpdated=" + mNotificationsUpdatedCount + ",notificationsCancelled=" + mNotificationsCancelledCount @@ -165,6 +214,7 @@ public final class BroadcastResponseStats implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) { dest.writeString8(mPackageName); + dest.writeLong(mId); dest.writeInt(mBroadcastsDispatchedCount); dest.writeInt(mNotificationsPostedCount); dest.writeInt(mNotificationsUpdatedCount); diff --git a/apex/media/aidl/stable/android/media/Session2Token.aidl b/core/java/android/app/usage/BroadcastResponseStatsList.aidl index c5980e9e77fd..73803599c19e 100644 --- a/apex/media/aidl/stable/android/media/Session2Token.aidl +++ b/core/java/android/app/usage/BroadcastResponseStatsList.aidl @@ -1,5 +1,5 @@ /* - * Copyright 2019 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ * limitations under the License. */ -package android.media; +package android.app.usage; -parcelable Session2Token; +/** {@hide} */ +parcelable BroadcastResponseStatsList;
\ No newline at end of file diff --git a/core/java/android/app/usage/BroadcastResponseStatsList.java b/core/java/android/app/usage/BroadcastResponseStatsList.java new file mode 100644 index 000000000000..4d2ff2866d0e --- /dev/null +++ b/core/java/android/app/usage/BroadcastResponseStatsList.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 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.usage; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** @hide */ +public final class BroadcastResponseStatsList implements Parcelable { + private List<BroadcastResponseStats> mBroadcastResponseStats; + + public BroadcastResponseStatsList( + @NonNull List<BroadcastResponseStats> broadcastResponseStats) { + mBroadcastResponseStats = broadcastResponseStats; + } + + private BroadcastResponseStatsList(@NonNull Parcel in) { + mBroadcastResponseStats = new ArrayList<>(); + final byte[] bytes = in.readBlob(); + final Parcel data = Parcel.obtain(); + try { + data.unmarshall(bytes, 0, bytes.length); + data.setDataPosition(0); + data.readTypedList(mBroadcastResponseStats, BroadcastResponseStats.CREATOR); + } finally { + data.recycle(); + } + } + + @NonNull + public List<BroadcastResponseStats> getList() { + return mBroadcastResponseStats == null ? Collections.emptyList() : mBroadcastResponseStats; + } + + @Override + public @ContentsFlags int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) { + final Parcel data = Parcel.obtain(); + try { + data.writeTypedList(mBroadcastResponseStats); + dest.writeBlob(data.marshall()); + } finally { + data.recycle(); + } + } + + public static final @NonNull Creator<BroadcastResponseStatsList> CREATOR = + new Creator<BroadcastResponseStatsList>() { + @Override + public @NonNull BroadcastResponseStatsList createFromParcel( + @NonNull Parcel source) { + return new BroadcastResponseStatsList(source); + } + + @Override + public @NonNull BroadcastResponseStatsList[] newArray(int size) { + return new BroadcastResponseStatsList[size]; + } + }; +} diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 6f8fea15f200..a43071418b2f 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -18,6 +18,7 @@ package android.app.usage; import android.app.PendingIntent; import android.app.usage.BroadcastResponseStats; +import android.app.usage.BroadcastResponseStatsList; import android.app.usage.UsageEvents; import android.content.pm.ParceledListSlice; @@ -73,9 +74,11 @@ interface IUsageStatsManager { void forceUsageSourceSettingRead(); long getLastTimeAnyComponentUsed(String packageName, String callingPackage); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") - BroadcastResponseStats queryBroadcastResponseStats( + BroadcastResponseStatsList queryBroadcastResponseStats( String packageName, long id, String callingPackage, int userId); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") void clearBroadcastResponseStats(String packageName, long id, String callingPackage, int userId); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") + void clearBroadcastEvents(String callingPackage, int userId); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index b81c62d29076..d7152b3676df 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -1397,29 +1397,46 @@ public final class UsageStatsManager { * Returns the broadcast response stats since the last boot corresponding to * {@code packageName} and {@code id}. * - * <p>Broadcast response stats will include the aggregated data of what actions an app took upon - * receiving a broadcast. This data will consider the broadcasts that the caller sent to + * <p> Broadcast response stats will include the aggregated data of what actions an app took + * upon receiving a broadcast. This data will consider the broadcasts that the caller sent to * {@code packageName} and explicitly requested to record the response events using * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}. * - * @param packageName The name of the package that the caller wants to query for. - * @param id The ID corresponding to the broadcasts that the caller wants to query for. This is - * the ID the caller specifies when requesting a broadcast response event to be - * recorded using {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}. + * <p> The returned list could one or more {@link BroadcastResponseStats} objects or be empty + * depending on the {@code packageName} and {@code id} and whether there is any data + * corresponding to these. If the {@code packageName} is not {@code null} and {@code id} is + * {@code > 0}, then the returned list would contain at most one {@link BroadcastResponseStats} + * object. Otherwise, the returned list could contain more than one + * {@link BroadcastResponseStats} object in no particular order. * - * @return the broadcast response stats corresponding to {@code packageName} and {@code id}. + * <p> Note: It is possible that same {@code id} was used for broadcasts sent to different + * packages. So, callers can query the data corresponding to + * all broadcasts with a particular {@code id} by passing {@code packageName} as {@code null}. * + * @param packageName The name of the package that the caller wants to query for + * or {@code null} to indicate that data corresponding to all packages + * should be returned. + * @param id The ID corresponding to the broadcasts that the caller wants to query for, or + * {@code 0} to indicate that data corresponding to all IDs should be returned. + * This is the ID the caller specifies when requesting a broadcast response event + * to be recorded using + * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}. + * + * @return the list of broadcast response stats corresponding to {@code packageName} + * and {@code id}. + * + * @see #clearBroadcastResponseStats(String, long) * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) @UserHandleAware @NonNull - public BroadcastResponseStats queryBroadcastResponseStats( - @NonNull String packageName, @IntRange(from = 1) long id) { + public List<BroadcastResponseStats> queryBroadcastResponseStats( + @Nullable String packageName, @IntRange(from = 0) long id) { try { return mService.queryBroadcastResponseStats(packageName, id, - mContext.getOpPackageName(), mContext.getUserId()); + mContext.getOpPackageName(), mContext.getUserId()).getList(); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -1428,12 +1445,15 @@ public final class UsageStatsManager { /** * Clears the broadcast response stats corresponding to {@code packageName} and {@code id}. * - * When a caller uses this API, stats related to the events occurring till that point will be - * cleared and subsequent calls to {@link #queryBroadcastResponseStats(String, long)} will + * <p> When a caller uses this API, stats related to the events occurring till that point will + * be cleared and subsequent calls to {@link #queryBroadcastResponseStats(String, long)} will * return stats related to events occurring after this. * - * @param packageName The name of the package that the caller wants to clear the data for. - * @param id The ID corresponding to the broadcasts that the caller wants to clear the data for. + * @param packageName The name of the package that the caller wants to clear the data for or + * {@code null} to indicate that data corresponding to all packages should + * be cleared. + * @param id The ID corresponding to the broadcasts that the caller wants to clear the data + * for, or {code 0} to indicate that data corresponding to all IDs should be deleted. * This is the ID the caller specifies when requesting a broadcast response event * to be recorded using * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}. @@ -1444,8 +1464,8 @@ public final class UsageStatsManager { @SystemApi @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) @UserHandleAware - public void clearBroadcastResponseStats(@NonNull String packageName, - @IntRange(from = 1) long id) { + public void clearBroadcastResponseStats(@Nullable String packageName, + @IntRange(from = 0) long id) { try { mService.clearBroadcastResponseStats(packageName, id, mContext.getOpPackageName(), mContext.getUserId()); @@ -1453,4 +1473,19 @@ public final class UsageStatsManager { throw re.rethrowFromSystemServer(); } } + + /** + * Clears the broadcast events that were sent by the caller uid. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) + @UserHandleAware + public void clearBroadcastEvents() { + try { + mService.clearBroadcastEvents(mContext.getOpPackageName(), mContext.getUserId()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java index 4e00da1b8c10..47bec618cfd9 100644 --- a/core/java/android/attention/AttentionManagerInternal.java +++ b/core/java/android/attention/AttentionManagerInternal.java @@ -49,21 +49,19 @@ public abstract class AttentionManagerInternal { /** * Requests the continuous updates of proximity signal via the provided callback, * until the given callback is unregistered. Currently, AttentionManagerService only - * anticipates one client and updates one client at a time. If a new client wants to - * onboard to receiving Proximity updates, please make a feature request to make proximity - * feature multi-client before depending on this feature. + * anticipates one client and updates one client at a time. * * @param callback a callback that receives the proximity updates * @return {@code true} if the registration should succeed. */ - public abstract boolean onStartProximityUpdates(ProximityCallbackInternal callback); + public abstract boolean onStartProximityUpdates(ProximityUpdateCallbackInternal callback); /** * Requests to stop providing continuous updates until the callback is registered. * * @param callback a callback that was used in {@link #onStartProximityUpdates} */ - public abstract void onStopProximityUpdates(ProximityCallbackInternal callback); + public abstract void onStopProximityUpdates(ProximityUpdateCallbackInternal callback); /** Internal interface for attention callback. */ public abstract static class AttentionCallbackInternal { @@ -85,7 +83,7 @@ public abstract class AttentionManagerInternal { } /** Internal interface for proximity callback. */ - public abstract static class ProximityCallbackInternal { + public abstract static class ProximityUpdateCallbackInternal { /** * @param distance the estimated distance of the user (in meter) * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive. diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index d50a6ba3b56d..99ce14743a6f 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -148,6 +148,8 @@ public final class VirtualDeviceManager { } } }; + @Nullable + private VirtualAudioDevice mVirtualAudioDevice; private VirtualDevice( IVirtualDeviceManager service, @@ -255,8 +257,8 @@ public final class VirtualDeviceManager { } /** - * Closes the virtual device, stopping and tearing down any virtual displays, - * audio policies, and event injection that's currently in progress. + * Closes the virtual device, stopping and tearing down any virtual displays, associated + * virtual audio device, and event injection that's currently in progress. */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { @@ -265,6 +267,10 @@ public final class VirtualDeviceManager { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + if (mVirtualAudioDevice != null) { + mVirtualAudioDevice.close(); + mVirtualAudioDevice = null; + } } /** @@ -351,8 +357,10 @@ public final class VirtualDeviceManager { * Creates a VirtualAudioDevice, capable of recording audio emanating from this device, * or injecting audio from another device. * - * <p>Note: This object does not support capturing privileged playback, such as voice call - * audio. + * <p>Note: One {@link VirtualDevice} can only create one {@link VirtualAudioDevice}, so + * calling this method multiple times will return the same instance. When + * {@link VirtualDevice#close()} is called, the associated {@link VirtualAudioDevice} will + * also be closed automatically. * * @param display The target virtual display to capture from and inject into. * @param executor The {@link Executor} object for the thread on which to execute @@ -368,7 +376,11 @@ public final class VirtualDeviceManager { @NonNull VirtualDisplay display, @Nullable Executor executor, @Nullable AudioConfigurationChangeCallback callback) { - return new VirtualAudioDevice(mContext, mVirtualDevice, display, executor, callback); + if (mVirtualAudioDevice == null) { + mVirtualAudioDevice = new VirtualAudioDevice( + mContext, mVirtualDevice, display, executor, callback); + } + return mVirtualAudioDevice; } /** diff --git a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java index 5c246d365751..c816da72b0ff 100644 --- a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java +++ b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java @@ -60,7 +60,6 @@ final class UserRestrictionsDetector extends BroadcastReceiver { /** Registers user restrictions change. */ void register(@NonNull UserRestrictionsCallback callback) { mUserRestrictionsCallback = callback; - IntentFilter filter = new IntentFilter(); filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED); mContext.registerReceiver(/* receiver= */ this, filter); @@ -73,8 +72,10 @@ final class UserRestrictionsDetector extends BroadcastReceiver { /** Unregisters user restrictions change. */ void unregister() { - mUserRestrictionsCallback = null; - mContext.unregisterReceiver(/* receiver= */ this); + if (mUserRestrictionsCallback != null) { + mUserRestrictionsCallback = null; + mContext.unregisterReceiver(/* receiver= */ this); + } } @Override diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java index 38e37ec0c316..3f7299fbb09e 100644 --- a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java +++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java @@ -24,6 +24,7 @@ import android.companion.virtual.IVirtualDevice; import android.content.Context; import android.hardware.display.VirtualDisplay; import android.media.AudioFormat; +import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.os.RemoteException; @@ -96,7 +97,7 @@ public final class VirtualAudioDevice implements Closeable { if (mOngoingSession != null && mOngoingSession.getAudioInjection() != null) { throw new IllegalStateException("Cannot start an audio injection while a session is " - + "ongoing. Call close() on this device first to end the previous injection."); + + "ongoing. Call close() on this device first to end the previous session."); } if (mOngoingSession == null) { mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor); @@ -114,6 +115,9 @@ public final class VirtualAudioDevice implements Closeable { /** * Begins recording audio emanating from this device. * + * <p>Note: This method does not support capturing privileged playback, which means the + * application can opt out of capturing by {@link AudioManager#setAllowedCapturePolicy(int)}. + * * @return An {@link AudioCapture} containing the recorded audio. */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) @@ -150,6 +154,7 @@ public final class VirtualAudioDevice implements Closeable { return mOngoingSession != null ? mOngoingSession.getAudioInjection() : null; } + /** Stops audio capture and injection then releases all the resources */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) @Override public void close() { diff --git a/core/java/android/companion/virtual/audio/VirtualAudioSession.java b/core/java/android/companion/virtual/audio/VirtualAudioSession.java index bc71bd65e944..c6a10456c331 100644 --- a/core/java/android/companion/virtual/audio/VirtualAudioSession.java +++ b/core/java/android/companion/virtual/audio/VirtualAudioSession.java @@ -120,7 +120,7 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem @NonNull public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) { Objects.requireNonNull(injectionFormat, "injectionFormat must not be null"); - mUserRestrictionsDetector.register(/* callback= */ this); + synchronized (mLock) { if (mAudioInjection != null) { throw new IllegalStateException( @@ -130,6 +130,8 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem mInjectionFormat = injectionFormat; mAudioInjection = new AudioInjection(); mAudioInjection.play(); + + mUserRestrictionsDetector.register(/* callback= */ this); mAudioInjection.setSilent(mUserRestrictionsDetector.isUnmuteMicrophoneDisallowed()); return mAudioInjection; } diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java index 45d4c09921a6..611393274285 100644 --- a/core/java/android/hardware/SensorEvent.java +++ b/core/java/android/hardware/SensorEvent.java @@ -17,6 +17,7 @@ package android.hardware; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; /** @@ -664,16 +665,20 @@ public class SensorEvent { * The first three elements provide the transform from the (arbitrary, possibly slowly drifting) * reference frame to the head frame. The magnitude of this vector is in range [0, π] * radians, while the value of individual axes is in range [-π, π]. The next three - * elements provide the estimated rotational velocity of the user's head relative to itself, in - * radians per second. + * elements optionally provide the estimated rotational velocity of the user's head relative to + * itself, in radians per second. If a given sensor does not support determining velocity, these + * elements are set to 0. * * <ul> * <li> values[0] : X component of Euler vector representing rotation</li> * <li> values[1] : Y component of Euler vector representing rotation</li> * <li> values[2] : Z component of Euler vector representing rotation</li> - * <li> values[3] : X component of Euler vector representing angular velocity</li> - * <li> values[4] : Y component of Euler vector representing angular velocity</li> - * <li> values[5] : Z component of Euler vector representing angular velocity</li> + * <li> values[3] : X component of Euler vector representing angular velocity (if + * supported, otherwise 0)</li> + * <li> values[4] : Y component of Euler vector representing angular velocity (if + * supported, otherwise 0)</li> + * <li> values[5] : Z component of Euler vector representing angular velocity (if + * supported, otherwise 0)</li> * </ul> * * <h4>{@link android.hardware.Sensor#TYPE_ACCELEROMETER_LIMITED_AXES @@ -820,6 +825,21 @@ public class SensorEvent { */ public long timestamp; + /** + * Set to true when this is the first sensor event after a discontinuity. + * + * The exact meaning of discontinuity depends on the sensor type. For + * {@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER}, this means that + * the reference frame has suddenly and significantly changed, for example if the head tracking + * device was removed then put back. + * + * Note that this concept is either not relevant to or not supported by most sensor types, + * {@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER} being the notable + * exception. + */ + @SuppressLint("MutableBareField") + public boolean firstEventAfterDiscontinuity; + @UnsupportedAppUsage SensorEvent(int valueSize) { values = new float[valueSize]; diff --git a/core/java/android/hardware/SensorEventCallback.java b/core/java/android/hardware/SensorEventCallback.java index 7b0092da1196..bac212ab5b20 100644 --- a/core/java/android/hardware/SensorEventCallback.java +++ b/core/java/android/hardware/SensorEventCallback.java @@ -16,8 +16,6 @@ package android.hardware; -import android.annotation.NonNull; - /** * Used for receiving sensor additional information frames. */ @@ -54,21 +52,4 @@ public abstract class SensorEventCallback implements SensorEventListener2 { * reported from sensor hardware. */ public void onSensorAdditionalInfo(SensorAdditionalInfo info) {} - - /** - * Called when the next {@link android.hardware.SensorEvent SensorEvent} to be delivered via the - * {@link #onSensorChanged(SensorEvent) onSensorChanged} method represents the first event after - * a discontinuity. - * - * The exact meaning of discontinuity depends on the sensor type. For {@link - * android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER}, this means that the - * reference frame has suddenly and significantly changed. - * - * Note that this concept is either not relevant to or not supported by most sensor types, - * {@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER} being the notable - * exception. - * - * @param sensor The {@link android.hardware.Sensor Sensor} which experienced the discontinuity. - */ - public void onSensorDiscontinuity(@NonNull Sensor sensor) {} } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 32a5ee77508e..18d86d69206f 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -881,13 +881,13 @@ public class SystemSensorManager extends SensorManager { mListener.onAccuracyChanged(t.sensor, t.accuracy); } - // call onSensorDiscontinuity() if the discontinuity counter changed - if (t.sensor.getType() == Sensor.TYPE_HEAD_TRACKER - && mListener instanceof SensorEventCallback) { + // Indicate if the discontinuity count changed + if (t.sensor.getType() == Sensor.TYPE_HEAD_TRACKER) { final int lastCount = mSensorDiscontinuityCounts.get(handle); final int curCount = Float.floatToIntBits(values[6]); if (lastCount >= 0 && lastCount != curCount) { - ((SensorEventCallback) mListener).onSensorDiscontinuity(t.sensor); + mSensorDiscontinuityCounts.put(handle, curCount); + t.firstEventAfterDiscontinuity = true; } } diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java index f5c59b597c3a..462ac142ce77 100644 --- a/core/java/android/service/attention/AttentionService.java +++ b/core/java/android/service/attention/AttentionService.java @@ -128,9 +128,9 @@ public abstract class AttentionService extends Service { /** {@inheritDoc} */ @Override - public void onStartProximityUpdates(IProximityCallback callback) { + public void onStartProximityUpdates(IProximityUpdateCallback callback) { Objects.requireNonNull(callback); - AttentionService.this.onStartProximityUpdates(new ProximityCallback(callback)); + AttentionService.this.onStartProximityUpdates(new ProximityUpdateCallback(callback)); } @@ -166,11 +166,11 @@ public abstract class AttentionService extends Service { /** * Requests the continuous updates of proximity signal via the provided callback, - * until the given callback is unregistered. + * until {@link #onStopProximityUpdates} is called. * * @param callback the callback to return the result to */ - public void onStartProximityUpdates(@NonNull ProximityCallback callback) { + public void onStartProximityUpdates(@NonNull ProximityUpdateCallback callback) { Slog.w(LOG_TAG, "Override this method."); } @@ -213,22 +213,24 @@ public abstract class AttentionService extends Service { } } - /** Callbacks for ProximityCallback results. */ - public static final class ProximityCallback { - @NonNull private final WeakReference<IProximityCallback> mCallback; + /** Callbacks for ProximityUpdateCallback results. */ + public static final class ProximityUpdateCallback { + @NonNull private final WeakReference<IProximityUpdateCallback> mCallback; - private ProximityCallback(@NonNull IProximityCallback callback) { + private ProximityUpdateCallback(@NonNull IProximityUpdateCallback callback) { mCallback = new WeakReference<>(callback); } /** * @param distance the estimated distance of the user (in meter) - * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive. - * + * The distance will be {@link #PROXIMITY_UNKNOWN} if the proximity sensing + * was inconclusive. */ public void onProximityUpdate(double distance) { try { - mCallback.get().onProximityUpdate(distance); + if (mCallback.get() != null) { + mCallback.get().onProximityUpdate(distance); + } } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/service/attention/IAttentionService.aidl b/core/java/android/service/attention/IAttentionService.aidl index 8bb881ba1708..e3ce114c5441 100644 --- a/core/java/android/service/attention/IAttentionService.aidl +++ b/core/java/android/service/attention/IAttentionService.aidl @@ -17,7 +17,7 @@ package android.service.attention; import android.service.attention.IAttentionCallback; -import android.service.attention.IProximityCallback; +import android.service.attention.IProximityUpdateCallback; /** * Interface for a concrete implementation to provide to the AttentionManagerService. @@ -27,6 +27,6 @@ import android.service.attention.IProximityCallback; oneway interface IAttentionService { void checkAttention(IAttentionCallback callback); void cancelAttentionCheck(IAttentionCallback callback); - void onStartProximityUpdates(IProximityCallback callback); + void onStartProximityUpdates(IProximityUpdateCallback callback); void onStopProximityUpdates(); }
\ No newline at end of file diff --git a/core/java/android/service/attention/IProximityCallback.aidl b/core/java/android/service/attention/IProximityUpdateCallback.aidl index 9ecf9bc28e84..26daa5cb93ae 100644 --- a/core/java/android/service/attention/IProximityCallback.aidl +++ b/core/java/android/service/attention/IProximityUpdateCallback.aidl @@ -5,6 +5,6 @@ package android.service.attention; * * @hide */ -oneway interface IProximityCallback { +oneway interface IProximityUpdateCallback { void onProximityUpdate(double distance); } diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java index 179d39de2cfb..9a1192326f4f 100644 --- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java +++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java @@ -1318,6 +1318,7 @@ final class RemoteSelectionToolbar { contentContainer.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG); + contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG); contentContainer.setClipToOutline(true); return contentContainer; } diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index 22cffc1579d1..6a65efb891ca 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -199,7 +199,7 @@ public abstract class RecognitionService extends Service { } private void dispatchTriggerModelDownload(Intent intent) { - RecognitionService.this.triggerModelDownload(intent); + RecognitionService.this.onTriggerModelDownload(intent); } private class StartListeningArgs { @@ -283,7 +283,7 @@ public abstract class RecognitionService extends Service { /** * Requests the download of the recognizer support for {@code recognizerIntent}. */ - public void triggerModelDownload(@NonNull Intent recognizerIntent) { + public void onTriggerModelDownload(@NonNull Intent recognizerIntent) { if (DBG) { Log.i(TAG, String.format("#downloadModel [%s]", recognizerIntent)); } diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java index ff04825f788f..c5ab82dbf0c5 100644 --- a/core/java/android/util/PackageUtils.java +++ b/core/java/android/util/PackageUtils.java @@ -18,13 +18,17 @@ package android.util; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.content.pm.Signature; import android.text.TextUtils; import libcore.util.HexEncoding; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -35,6 +39,9 @@ import java.util.Arrays; */ public final class PackageUtils { + private static final int LOW_RAM_BUFFER_SIZE_BYTES = 1 * 1000; // 1 kB + private static final int HIGH_RAM_BUFFER_SIZE_BYTES = 1 * 1000 * 1000; // 1 MB + private PackageUtils() { /* hide constructor */ } @@ -162,4 +169,55 @@ public final class PackageUtils { return TextUtils.join(separator, pieces); } + + /** + * @see #computeSha256DigestForLargeFile(String, String) + */ + public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath) { + return computeSha256DigestForLargeFile(filePath, null); + } + + /** + * Computes the SHA256 digest of large files. + * @param filePath The path to which the file's content is to be hashed. + * @param separator Separator between each pair of characters, such as colon, or null to omit. + * @return The digest or null if an error occurs. + */ + public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, + @Nullable String separator) { + MessageDigest messageDigest; + try { + messageDigest = MessageDigest.getInstance("SHA256"); + messageDigest.reset(); + } catch (NoSuchAlgorithmException e) { + // this shouldn't happen! + return null; + } + + boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); + int bufferSize = isLowRamDevice ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES; + + File f = new File(filePath); + try { + DigestInputStream digestStream = new DigestInputStream(new FileInputStream(f), + messageDigest); + byte[] buffer = new byte[bufferSize]; + while (digestStream.read(buffer) != -1); + } catch (IOException e) { + return null; + } + + byte[] resultBytes = messageDigest.digest(); + + if (separator == null) { + return HexEncoding.encodeToString(resultBytes, true); + } + + int length = resultBytes.length; + String[] pieces = new String[length]; + for (int index = 0; index < length; index++) { + pieces[index] = HexEncoding.encodeToString(resultBytes[index], true); + } + return TextUtils.join(separator, pieces); + } } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index c08177e2f71e..98cef95885bd 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -389,9 +389,7 @@ public final class SurfaceControl implements Parcelable { public static final int JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED = 0x4; // Either App or GPU took too long on the frame public static final int JANK_APP_DEADLINE_MISSED = 0x8; - // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a - // jank - // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame. + // Vsync predictions have drifted beyond the threshold from the actual HWVsync public static final int PREDICTION_ERROR = 0x10; // Latching a buffer early might cause an early present of the frame public static final int SURFACE_FLINGER_SCHEDULING = 0x20; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 179f6ee83227..4dc1fca24f36 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11785,7 +11785,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * user and that ideally it should not be covered. Setting this is only appropriate for UI * where the user would likely take action to uncover it. * <p> - * The system will try to respect this, but when not possible will ignore it. + * The system will try to respect this preference, but when not possible will ignore it. + * <p> + * Note: while this is set to {@code true}, the system will ignore the {@code Rect}s provided + * through {@link #setPreferKeepClearRects} (but not clear them). * <p> * @see #setPreferKeepClearRects * @see #isPreferKeepClear @@ -11817,11 +11820,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * user and that ideally they should not be covered. Setting this is only appropriate for UI * where the user would likely take action to uncover it. * <p> - * If the whole view is preferred to be clear ({@link #isPreferKeepClear}), the rects set here - * will be ignored. - * <p> * The system will try to respect this preference, but when not possible will ignore it. * <p> + * Note: While {@link #isPreferKeepClear} is {@code true}, the {@code Rect}s set here are + * ignored. + * <p> * @see #setPreferKeepClear * @see #getPreferKeepClearRects */ diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java index 6de031628768..ef04b2c52282 100644 --- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java +++ b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java @@ -105,7 +105,7 @@ public final class SelectionToolbarManager { private boolean isRemoteSelectionToolbarEnabled() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SELECTION_TOOLBAR, - REMOTE_SELECTION_TOOLBAR_ENABLED, false); + REMOTE_SELECTION_TOOLBAR_ENABLED, true); } /** diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index aaaf7293931f..7718a3b7fc67 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -364,8 +364,13 @@ public final class TransitionInfo implements Parcelable { private final Point mEndRelOffset = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; private boolean mAllowEnterPip; - private int mStartRotation = ROTATION_UNDEFINED; - private int mEndRotation = ROTATION_UNDEFINED; + private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED; + private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED; + /** + * The end rotation of the top activity after fixed rotation is finished. If the top + * activity is not in fixed rotation, it will be {@link ROTATION_UNDEFINED}. + */ + private @Surface.Rotation int mEndFixedRotation = ROTATION_UNDEFINED; private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; private @ColorInt int mBackgroundColor; @@ -388,6 +393,7 @@ public final class TransitionInfo implements Parcelable { mAllowEnterPip = in.readBoolean(); mStartRotation = in.readInt(); mEndRotation = in.readInt(); + mEndFixedRotation = in.readInt(); mRotationAnimation = in.readInt(); mBackgroundColor = in.readInt(); } @@ -441,6 +447,11 @@ public final class TransitionInfo implements Parcelable { mEndRotation = end; } + /** Sets end rotation that top activity will be launched to after fixed rotation. */ + public void setEndFixedRotation(@Surface.Rotation int endFixedRotation) { + mEndFixedRotation = endFixedRotation; + } + /** * Sets the app-requested animation type for rotation. Will be one of the * ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams}; @@ -521,14 +532,21 @@ public final class TransitionInfo implements Parcelable { return mAllowEnterPip; } + @Surface.Rotation public int getStartRotation() { return mStartRotation; } + @Surface.Rotation public int getEndRotation() { return mEndRotation; } + @Surface.Rotation + public int getEndFixedRotation() { + return mEndFixedRotation; + } + /** @return the rotation animation. */ public int getRotationAnimation() { return mRotationAnimation; @@ -555,6 +573,7 @@ public final class TransitionInfo implements Parcelable { dest.writeBoolean(mAllowEnterPip); dest.writeInt(mStartRotation); dest.writeInt(mEndRotation); + dest.writeInt(mEndFixedRotation); dest.writeInt(mRotationAnimation); dest.writeInt(mBackgroundColor); } @@ -584,7 +603,8 @@ public final class TransitionInfo implements Parcelable { return "{" + mContainer + "(" + mParent + ") leash=" + mLeash + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb=" + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r=" - + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}"; + + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + + " endFixedRotation=" + mEndFixedRotation + "}"; } } diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 17b675f93f86..5007df574ec1 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -16,16 +16,22 @@ package android.window; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.os.Bundle; import android.os.IBinder; +import android.util.Log; import android.view.IWindowManager; import android.view.WindowManager.LayoutParams.WindowType; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; + /** * The controller to manage {@link WindowContext}, such as attaching to a window manager node or * detaching from the current attached node. The user must call @@ -35,13 +41,43 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public class WindowContextController { + // TODO(220049234): Disable attach debug logging before shipping. + private static final boolean DEBUG_ATTACH = true; + private static final String TAG = "WindowContextController"; + /** - * {@code true} to indicate that the {@code mToken} is associated with a + * {@link AttachStatus.STATUS_ATTACHED} to indicate that the {@code mToken} is associated with a * {@link com.android.server.wm.DisplayArea}. Note that {@code mToken} is able to attach a - * WindowToken after this flag sets to {@code true}. + * WindowToken after this flag sets to {@link AttachStatus.STATUS_ATTACHED}. */ @VisibleForTesting - public boolean mAttachedToDisplayArea; + public int mAttachedToDisplayArea = AttachStatus.STATUS_INITIALIZED; + + /** + * Status to indicate that the Window Context attach to a + * {@link com.android.server.wm.DisplayArea}. + */ + @Retention(SOURCE) + @IntDef({AttachStatus.STATUS_INITIALIZED, AttachStatus.STATUS_ATTACHED, + AttachStatus.STATUS_DETACHED, AttachStatus.STATUS_FAILED}) + public @interface AttachStatus{ + /** + * The Window Context haven't attached to a {@link com.android.server.wm.DisplayArea}. + */ + int STATUS_INITIALIZED = 0; + /** + * The Window Context has already attached to a {@link com.android.server.wm.DisplayArea}. + */ + int STATUS_ATTACHED = 1; + /** + * The Window Context has detached from a {@link com.android.server.wm.DisplayArea}. + */ + int STATUS_DETACHED = 2; + /** + * The Window Context fails to attach to a {@link com.android.server.wm.DisplayArea}. + */ + int STATUS_FAILED = 3; + } @NonNull private final WindowTokenClient mToken; @@ -65,11 +101,19 @@ public class WindowContextController { * DisplayArea. */ public void attachToDisplayArea(@WindowType int type, int displayId, @Nullable Bundle options) { - if (mAttachedToDisplayArea) { + if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) { throw new IllegalStateException("A Window Context can be only attached to " + "a DisplayArea once."); } - mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options); + mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options) + ? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED; + if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) { + Log.w(TAG, "attachToDisplayArea fail, type:" + type + ", displayId:" + + displayId); + } else if (DEBUG_ATTACH) { + Log.d(TAG, "attachToDisplayArea success, type:" + type + ", displayId:" + + displayId); + } } /** @@ -93,18 +137,21 @@ public class WindowContextController { * @see IWindowManager#attachWindowContextToWindowToken(IBinder, IBinder) */ public void attachToWindowToken(IBinder windowToken) { - if (!mAttachedToDisplayArea) { + if (mAttachedToDisplayArea != AttachStatus.STATUS_ATTACHED) { throw new IllegalStateException("The Window Context should have been attached" - + " to a DisplayArea."); + + " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea); } mToken.attachToWindowToken(windowToken); } /** Detaches the window context from the node it's currently associated with. */ public void detachIfNeeded() { - if (mAttachedToDisplayArea) { + if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) { mToken.detachFromWindowContainerIfNeeded(); - mAttachedToDisplayArea = false; + mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED; + if (DEBUG_ATTACH) { + Log.d(TAG, "Detach Window Context."); + } } } } diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java index 0443ad03b6ea..0835824f6b39 100644 --- a/core/java/com/android/internal/infra/AndroidFuture.java +++ b/core/java/com/android/internal/infra/AndroidFuture.java @@ -455,7 +455,14 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable if (mSourceU != null) { // T done mResultT = (T) res; - mSourceU.whenComplete(this); + + // Subscribe to the second job completion. + mSourceU.whenComplete((r, e) -> { + // Mark the first job completion by setting mSourceU to null, so that next time + // the execution flow goes to the else case below. + mSourceU = null; + accept(r, e); + }); } else { // U done try { diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index e1a67d861bcb..34c47ede99f0 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -98,6 +98,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private final Handler mHandler; private final ChoreographerWrapper mChoreographer; private final Object mLock = InteractionJankMonitor.getInstance().getLock(); + private final boolean mDeferMonitoring; @VisibleForTesting public final boolean mSurfaceOnly; @@ -153,6 +154,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mHandler = handler; mChoreographer = choreographer; mSurfaceControlWrapper = surfaceControlWrapper; + mDeferMonitoring = config.shouldDeferMonitor(); // HWUI instrumentation init. mRendererWrapper = mSurfaceOnly ? null : renderer; @@ -228,12 +230,25 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ public void begin() { synchronized (mLock) { - mBeginVsyncId = mChoreographer.getVsyncId() + 1; + final long currentVsync = mChoreographer.getVsyncId(); + // In normal case, we should begin at the next frame, + // the id of the next frame is not simply increased by 1, + // but we can exclude the current frame at least. + mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync; if (DEBUG) { - Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId); + Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId + + ", defer=" + mDeferMonitoring); } if (mSurfaceControl != null) { - postTraceStartMarker(); + if (mDeferMonitoring) { + // Normal case, we begin the instrument from the very beginning, + // except the first frame. + postTraceStartMarker(); + } else { + // If we don't begin the instrument from the very beginning, + // there is no need to skip the frame where the begin invocation happens. + beginInternal(); + } mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); } if (!mSurfaceOnly) { @@ -247,15 +262,18 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ @VisibleForTesting public void postTraceStartMarker() { - mChoreographer.mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, () -> { - synchronized (mLock) { - if (mCancelled || mEndVsyncId != INVALID_ID) { - return; - } - mTracingStarted = true; - Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId); + mChoreographer.mChoreographer.postCallback( + Choreographer.CALLBACK_INPUT, this::beginInternal, null); + } + + private void beginInternal() { + synchronized (mLock) { + if (mCancelled || mEndVsyncId != INVALID_ID) { + return; } - }, null); + mTracingStarted = true; + Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId); + } } /** @@ -464,8 +482,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (info.surfaceControlCallbackFired) { totalFramesCount++; boolean missedFrame = false; - if ((info.jankType & PREDICTION_ERROR) != 0 - || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0)) { + if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) { Log.w(TAG, "Missed App frame:" + info.jankType); missedAppFramesCount++; missedFrame = true; @@ -473,7 +490,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if ((info.jankType & DISPLAY_HAL) != 0 || (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0 || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0 - || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0) { + || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0 + || (info.jankType & PREDICTION_ERROR) != 0) { Log.w(TAG, "Missed SF frame:" + info.jankType); missedSfFramesCount++; missedFrame = true; diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 5a66e9aec3e7..3746bfdae781 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -717,6 +717,7 @@ public class InteractionJankMonitor { private final boolean mSurfaceOnly; private final SurfaceControl mSurfaceControl; private final @CujType int mCujType; + private final boolean mDeferMonitor; /** * A builder for building Configuration. {@link #setView(View)} is essential @@ -733,6 +734,7 @@ public class InteractionJankMonitor { private boolean mAttrSurfaceOnly; private SurfaceControl mAttrSurfaceControl; private @CujType int mAttrCujType; + private boolean mAttrDeferMonitor = true; /** * Creates a builder which instruments only surface. @@ -823,6 +825,16 @@ public class InteractionJankMonitor { } /** + * Indicates if the instrument should be deferred to the next frame. + * @param defer true if the instrument should be deferred to the next frame. + * @return builder + */ + public Builder setDeferMonitorForAnimationStart(boolean defer) { + mAttrDeferMonitor = defer; + return this; + } + + /** * Builds the {@link Configuration} instance * @return the instance of {@link Configuration} * @throws IllegalArgumentException if any invalid attribute is set @@ -830,12 +842,14 @@ public class InteractionJankMonitor { public Configuration build() throws IllegalArgumentException { return new Configuration( mAttrCujType, mAttrView, mAttrTag, mAttrTimeout, - mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl); + mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl, + mAttrDeferMonitor); } } private Configuration(@CujType int cuj, View view, String tag, long timeout, - boolean surfaceOnly, Context context, SurfaceControl surfaceControl) { + boolean surfaceOnly, Context context, SurfaceControl surfaceControl, + boolean deferMonitor) { mCujType = cuj; mTag = tag; mTimeout = timeout; @@ -845,6 +859,7 @@ public class InteractionJankMonitor { ? context : (view != null ? view.getContext().getApplicationContext() : null); mSurfaceControl = surfaceControl; + mDeferMonitor = deferMonitor; validate(); } @@ -901,6 +916,13 @@ public class InteractionJankMonitor { Context getContext() { return mContext; } + + /** + * @return true if the monitoring should be deferred to the next frame, false otherwise. + */ + public boolean shouldDeferMonitor() { + return mDeferMonitor; + } } /** diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java index c7585046cf9c..f3af52819eb8 100644 --- a/core/java/com/android/internal/util/PerfettoTrigger.java +++ b/core/java/com/android/internal/util/PerfettoTrigger.java @@ -18,6 +18,7 @@ package com.android.internal.util; import android.os.SystemClock; import android.util.Log; +import android.util.SparseLongArray; import java.io.IOException; @@ -28,8 +29,9 @@ import java.io.IOException; public class PerfettoTrigger { private static final String TAG = "PerfettoTrigger"; private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto"; - private static final long THROTTLE_MILLIS = 60000; - private static volatile long sLastTriggerTime = -THROTTLE_MILLIS; + private static final long THROTTLE_MILLIS = 300000; + private static final SparseLongArray sLastInvocationPerTrigger = new SparseLongArray(100); + private static final Object sLock = new Object(); /** * @param triggerName The name of the trigger. Must match the value defined in the AOT @@ -38,18 +40,23 @@ public class PerfettoTrigger { public static void trigger(String triggerName) { // Trace triggering has a non-negligible cost (fork+exec). // To mitigate potential excessive triggering by the API client we ignore calls that happen - // too quickl after the most recent trigger. - long sinceLastTrigger = SystemClock.elapsedRealtime() - sLastTriggerTime; - if (sinceLastTrigger < THROTTLE_MILLIS) { - Log.v(TAG, "Not triggering " + triggerName + " - not enough time since last trigger"); - return; + // too quickly after the most recent trigger. + synchronized (sLock) { + long lastTrigger = sLastInvocationPerTrigger.get(triggerName.hashCode()); + long sinceLastTrigger = SystemClock.elapsedRealtime() - lastTrigger; + if (sinceLastTrigger < THROTTLE_MILLIS) { + Log.v(TAG, "Not triggering " + triggerName + + " - not enough time since last trigger"); + return; + } + + sLastInvocationPerTrigger.put(triggerName.hashCode(), SystemClock.elapsedRealtime()); } try { ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName); Log.v(TAG, "Triggering " + String.join(" ", pb.command())); pb.start(); - sLastTriggerTime = SystemClock.elapsedRealtime(); } catch (IOException e) { Log.w(TAG, "Failed to trigger " + triggerName, e); } diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java index 17b84ffc2f3f..d9c1e571292b 100644 --- a/core/java/com/android/internal/util/UserIcons.java +++ b/core/java/com/android/internal/util/UserIcons.java @@ -46,11 +46,21 @@ public class UserIcons { * Converts a given drawable to a bitmap. */ public static Bitmap convertToBitmap(Drawable icon) { + return convertToBitmapAtSize(icon, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + } + + /** + * Converts a given drawable to a bitmap, with width and height equal to the default icon size. + */ + public static Bitmap convertToBitmapAtUserIconSize(Resources res, Drawable icon) { + int size = res.getDimensionPixelSize(R.dimen.user_icon_size); + return convertToBitmapAtSize(icon, size, size); + } + + private static Bitmap convertToBitmapAtSize(Drawable icon, int width, int height) { if (icon == null) { return null; } - final int width = icon.getIntrinsicWidth(); - final int height = icon.getIntrinsicHeight(); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); icon.setBounds(0, 0, width, height); diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 78bb53d33539..5fa4a652e176 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -150,6 +150,7 @@ public class ConversationLayout extends FrameLayout private Icon mShortcutIcon; private View mAppNameDivider; private TouchDelegateComposite mTouchDelegate = new TouchDelegateComposite(this); + private ArrayList<MessagingGroup> mToRecycle = new ArrayList<>(); public ConversationLayout(@NonNull Context context) { super(context); @@ -472,6 +473,12 @@ public class ConversationLayout extends FrameLayout updateTitleAndNamesDisplay(); updateConversationLayout(); + + // Recycle everything at the end of the update, now that we know it's no longer needed. + for (MessagingGroup group : mToRecycle) { + group.recycle(); + } + mToRecycle.clear(); } /** @@ -745,18 +752,18 @@ public class ConversationLayout extends FrameLayout MessagingGroup group = oldGroups.get(i); if (!mGroups.contains(group)) { List<MessagingMessage> messages = group.getMessages(); - Runnable endRunnable = () -> { - mMessagingLinearLayout.removeTransientView(group); - group.recycle(); - }; - boolean wasShown = group.isShown(); mMessagingLinearLayout.removeView(group); if (wasShown && !MessagingLinearLayout.isGone(group)) { mMessagingLinearLayout.addTransientView(group, 0); - group.removeGroupAnimated(endRunnable); + group.removeGroupAnimated(() -> { + mMessagingLinearLayout.removeTransientView(group); + group.recycle(); + }); } else { - endRunnable.run(); + // Defer recycling until after the update is done, since we may still need the + // old group around to perform other updates. + mToRecycle.add(group); } mMessages.removeAll(messages); mHistoricMessages.removeAll(messages); diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java index 80d8bd78e746..8c61a12b47e6 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java @@ -1475,6 +1475,7 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup { contentContainer.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG); + contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG); contentContainer.setClipToOutline(true); return contentContainer; } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 47cb754f4b5a..4aa00f6117b9 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -64,7 +64,6 @@ cc_library_shared { "libbase", "libcutils", "libharfbuzz_ng", - "libhwui", "liblog", "libminikin", "libz", @@ -266,6 +265,7 @@ cc_library_shared { "libui", "libgraphicsenv", "libgui", + "libhwui", "libmediandk", "libpermission", "libsensor", @@ -344,9 +344,21 @@ cc_library_shared { ], static_libs: [ "libandroidfw", - "libcompiler_rt", - "libutils", + "libbinary_parse", + "libdng_sdk", + "libft2", "libhostgraphics", + "libhwui", + "libimage_type_recognition", + "libjpeg", + "libpiex", + "libpng", + "libtiff_directory", + "libui-types", + "libutils", + "libwebp-decode", + "libwebp-encode", + "libwuffs_mirror_release_c", ], }, linux_glibc: { diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 3e513df98f14..93ba23bfdf84 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -14,15 +14,31 @@ * limitations under the License. */ -#include "jni.h" -#include "core_jni_helpers.h" - +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android/graphics/jni_runtime.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/jni_macros.h> #include <unicode/putil.h> +#include <unicode/udata.h> + #include <clocale> #include <sstream> #include <unordered_map> #include <vector> +#include "core_jni_helpers.h" +#include "jni.h" +#ifdef _WIN32 +#include <windows.h> +#else +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#endif + +#include <iostream> + using namespace std; /* @@ -33,6 +49,33 @@ using namespace std; */ static JavaVM* javaVM; +static jclass bridge; +static jclass layoutLog; +static jmethodID getLogId; +static jmethodID logMethodId; + +extern int register_android_os_Binder(JNIEnv* env); +extern int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env); + +typedef void (*FreeFunction)(void*); + +static void NativeAllocationRegistry_Delegate_nativeApplyFreeFunction(JNIEnv*, jclass, + jlong freeFunction, + jlong ptr) { + void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr)); + FreeFunction nativeFreeFunction = + reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction)); + nativeFreeFunction(nativePtr); +} + +static JNINativeMethod gMethods[] = { + NATIVE_METHOD(NativeAllocationRegistry_Delegate, nativeApplyFreeFunction, "(JJ)V"), +}; + +int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env) { + return jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry_Delegate", gMethods, + NELEM(gMethods)); +} namespace android { @@ -47,6 +90,7 @@ extern int register_android_database_SQLiteGlobal(JNIEnv* env); extern int register_android_database_SQLiteDebug(JNIEnv* env); extern int register_android_os_FileObserver(JNIEnv* env); extern int register_android_os_MessageQueue(JNIEnv* env); +extern int register_android_os_Parcel(JNIEnv* env); extern int register_android_os_SystemClock(JNIEnv* env); extern int register_android_os_SystemProperties(JNIEnv* env); extern int register_android_os_Trace(JNIEnv* env); @@ -54,6 +98,11 @@ extern int register_android_text_AndroidCharacter(JNIEnv* env); extern int register_android_util_EventLog(JNIEnv* env); extern int register_android_util_Log(JNIEnv* env); extern int register_android_util_jar_StrictJarFile(JNIEnv* env); +extern int register_android_view_KeyCharacterMap(JNIEnv* env); +extern int register_android_view_KeyEvent(JNIEnv* env); +extern int register_android_view_MotionEvent(JNIEnv* env); +extern int register_android_view_ThreadedRenderer(JNIEnv* env); +extern int register_android_view_VelocityTracker(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); #define REG_JNI(name) { name } @@ -78,8 +127,10 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)}, {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)}, #ifdef __linux__ + {"android.os.Binder", REG_JNI(register_android_os_Binder)}, {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)}, {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)}, + {"android.os.Parcel", REG_JNI(register_android_os_Parcel)}, #endif {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)}, {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)}, @@ -88,11 +139,15 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.util.EventLog", REG_JNI(register_android_util_EventLog)}, {"android.util.Log", REG_JNI(register_android_util_Log)}, {"android.util.jar.StrictJarFile", REG_JNI(register_android_util_jar_StrictJarFile)}, + {"android.view.KeyCharacterMap", REG_JNI(register_android_view_KeyCharacterMap)}, + {"android.view.KeyEvent", REG_JNI(register_android_view_KeyEvent)}, + {"android.view.MotionEvent", REG_JNI(register_android_view_MotionEvent)}, + {"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)}, {"com.android.internal.util.VirtualRefBasePtr", REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)}, + {"libcore.util.NativeAllocationRegistry_Delegate", + REG_JNI(register_libcore_util_NativeAllocationRegistry_Delegate)}, }; -// Vector to store the names of classes that need delegates of their native methods -static vector<string> classesToDelegate; static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap, const vector<string>& classesToRegister, JNIEnv* env) { @@ -102,36 +157,17 @@ static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& return -1; } } + + if (register_android_graphics_classes(env) < 0) { + return -1; + } + return 0; } int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { - string classNameString = string(className); - if (find(classesToDelegate.begin(), classesToDelegate.end(), classNameString) - != classesToDelegate.end()) { - // Register native methods to the delegate class <classNameString>_NativeDelegate - // by adding _Original to the name of each method. - replace(classNameString.begin(), classNameString.end(), '$', '_'); - string delegateClassName = classNameString + "_NativeDelegate"; - jclass clazz = env->FindClass(delegateClassName.c_str()); - JNINativeMethod gTypefaceDelegateMethods[numMethods]; - for (int i = 0; i < numMethods; i++) { - JNINativeMethod gTypefaceMethod = gMethods[i]; - string newName = string(gTypefaceMethod.name) + "_Original"; - gTypefaceDelegateMethods[i].name = strdup(newName.c_str()); - gTypefaceDelegateMethods[i].signature = gTypefaceMethod.signature; - gTypefaceDelegateMethods[i].fnPtr = gTypefaceMethod.fnPtr; - } - int result = env->RegisterNatives(clazz, gTypefaceDelegateMethods, numMethods); - for (int i = 0; i < numMethods; i++) { - free((char*)gTypefaceDelegateMethods[i].name); - } - return result; - } - - jclass clazz = env->FindClass(className); - return env->RegisterNatives(clazz, gMethods, numMethods); + return jniRegisterNativeMethods(env, className, gMethods, numMethods); } JNIEnv* AndroidRuntime::getJNIEnv() { @@ -164,6 +200,125 @@ static vector<string> parseCsv(JNIEnv* env, jstring csvJString) { return result; } +void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file, + unsigned int line, const char* message) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jint logPrio = severity; + jstring tagString = env->NewStringUTF(tag); + jstring messageString = env->NewStringUTF(message); + + jobject bridgeLog = env->CallStaticObjectMethod(bridge, getLogId); + + env->CallVoidMethod(bridgeLog, logMethodId, logPrio, tagString, messageString); + + env->DeleteLocalRef(tagString); + env->DeleteLocalRef(messageString); + env->DeleteLocalRef(bridgeLog); +} + +void LayoutlibAborter(const char* abort_message) { + // Layoutlib should not call abort() as it would terminate Studio. + // Throw an exception back to Java instead. + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jniThrowRuntimeException(env, "The Android framework has encountered a fatal error"); +} + +// This method has been copied/adapted from system/core/init/property_service.cpp +// If the ro.product.cpu.abilist* properties have not been explicitly +// set, derive them from ro.system.product.cpu.abilist* properties. +static void property_initialize_ro_cpu_abilist() { + const std::string EMPTY = ""; + const char* kAbilistProp = "ro.product.cpu.abilist"; + const char* kAbilist32Prop = "ro.product.cpu.abilist32"; + const char* kAbilist64Prop = "ro.product.cpu.abilist64"; + + // If the properties are defined explicitly, just use them. + if (base::GetProperty(kAbilistProp, EMPTY) != EMPTY) { + return; + } + + std::string abilist32_prop_val; + std::string abilist64_prop_val; + const auto abilist32_prop = "ro.system.product.cpu.abilist32"; + const auto abilist64_prop = "ro.system.product.cpu.abilist64"; + abilist32_prop_val = base::GetProperty(abilist32_prop, EMPTY); + abilist64_prop_val = base::GetProperty(abilist64_prop, EMPTY); + + // Merge ABI lists for ro.product.cpu.abilist + auto abilist_prop_val = abilist64_prop_val; + if (abilist32_prop_val != EMPTY) { + if (abilist_prop_val != EMPTY) { + abilist_prop_val += ","; + } + abilist_prop_val += abilist32_prop_val; + } + + // Set these properties + const std::pair<const char*, const std::string&> set_prop_list[] = { + {kAbilistProp, abilist_prop_val}, + {kAbilist32Prop, abilist32_prop_val}, + {kAbilist64Prop, abilist64_prop_val}, + }; + for (const auto& [prop, prop_val] : set_prop_list) { + base::SetProperty(prop, prop_val); + } +} + +static void* mmapFile(const char* dataFilePath) { +#ifdef _WIN32 + // Windows needs file path in wide chars to handle unicode file paths + int size = MultiByteToWideChar(CP_UTF8, 0, dataFilePath, -1, NULL, 0); + std::vector<wchar_t> wideDataFilePath(size); + MultiByteToWideChar(CP_UTF8, 0, dataFilePath, -1, wideDataFilePath.data(), size); + HANDLE file = + CreateFileW(wideDataFilePath.data(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, nullptr); + if ((HANDLE)INVALID_HANDLE_VALUE == file) { + return nullptr; + } + + struct CloseHandleWrapper { + void operator()(HANDLE h) { CloseHandle(h); } + }; + std::unique_ptr<void, CloseHandleWrapper> mmapHandle( + CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr)); + if (!mmapHandle) { + return nullptr; + } + return MapViewOfFile(mmapHandle.get(), FILE_MAP_READ, 0, 0, 0); +#else + int fd = open(dataFilePath, O_RDONLY); + if (fd == -1) { + return nullptr; + } + + struct stat sb; + if (fstat(fd, &sb) == -1) { + close(fd); + return nullptr; + } + + void* addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) { + close(fd); + return nullptr; + } + + close(fd); + return addr; +#endif +} + +static bool init_icu(const char* dataPath) { + void* addr = mmapFile(dataPath); + UErrorCode err = U_ZERO_ERROR; + udata_setCommonData(addr, &err); + if (err != U_ZERO_ERROR) { + return false; + } + return true; +} + } // namespace android using namespace android; @@ -175,37 +330,82 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { return JNI_ERR; } + init_android_graphics(); + // Configuration is stored as java System properties. // Get a reference to System.getProperty jclass system = FindClassOrDie(env, "java/lang/System"); jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); - // Get the names of classes that have to delegate their native methods - auto delegateNativesToNativesString = - (jstring) env->CallStaticObjectMethod(system, - getPropertyMethod, env->NewStringUTF("delegate_natives_to_natives"), - env->NewStringUTF("")); - classesToDelegate = parseCsv(env, delegateNativesToNativesString); - // Get the names of classes that need to register their native methods auto nativesClassesJString = - (jstring) env->CallStaticObjectMethod(system, - getPropertyMethod, env->NewStringUTF("native_classes"), - env->NewStringUTF("")); + (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, + env->NewStringUTF("core_native_classes"), + env->NewStringUTF("")); vector<string> classesToRegister = parseCsv(env, nativesClassesJString); + jstring registerProperty = + (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, + env->NewStringUTF( + "register_properties_during_load"), + env->NewStringUTF("")); + const char* registerPropertyString = env->GetStringUTFChars(registerProperty, 0); + if (strcmp(registerPropertyString, "true") == 0) { + // Set the system properties first as they could be used in the static initialization of + // other classes + if (register_android_os_SystemProperties(env) < 0) { + return JNI_ERR; + } + classesToRegister.erase(find(classesToRegister.begin(), classesToRegister.end(), + "android.os.SystemProperties")); + bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge"); + bridge = MakeGlobalRefOrDie(env, bridge); + jmethodID setSystemPropertiesMethod = + GetStaticMethodIDOrDie(env, bridge, "setSystemProperties", "()V"); + env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod); + property_initialize_ro_cpu_abilist(); + } + env->ReleaseStringUTFChars(registerProperty, registerPropertyString); + if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) { return JNI_ERR; } // Set the location of ICU data - auto stringPath = (jstring) env->CallStaticObjectMethod(system, - getPropertyMethod, env->NewStringUTF("icu.dir"), - env->NewStringUTF("")); + auto stringPath = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, + env->NewStringUTF("icu.data.path"), + env->NewStringUTF("")); const char* path = env->GetStringUTFChars(stringPath, 0); - u_setDataDirectory(path); + bool icuInitialized = init_icu(path); env->ReleaseStringUTFChars(stringPath, path); + if (!icuInitialized) { + return JNI_ERR; + } + + jstring useJniProperty = + (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, + env->NewStringUTF("use_bridge_for_logging"), + env->NewStringUTF("")); + const char* useJniString = env->GetStringUTFChars(useJniProperty, 0); + if (strcmp(useJniString, "true") == 0) { + layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog"); + layoutLog = MakeGlobalRefOrDie(env, layoutLog); + logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework", + "(ILjava/lang/String;Ljava/lang/String;)V"); + if (bridge == nullptr) { + bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge"); + bridge = MakeGlobalRefOrDie(env, bridge); + } + getLogId = GetStaticMethodIDOrDie(env, bridge, "getLog", + "()Lcom/android/ide/common/rendering/api/ILayoutLog;"); + android::base::SetLogger(LayoutlibLogger); + android::base::SetAborter(LayoutlibAborter); + } else { + // initialize logging, so ANDROD_LOG_TAGS env variable is respected + android::base::InitLogging(nullptr, android::base::StderrLogger); + } + env->ReleaseStringUTFChars(useJniProperty, useJniString); // Use English locale for number format to ensure correct parsing of floats when using strtof setlocale(LC_NUMERIC, "en_US.UTF-8"); @@ -213,3 +413,9 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { return JNI_VERSION_1_6; } +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) { + JNIEnv* env = nullptr; + vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); + env->DeleteGlobalRef(bridge); + env->DeleteGlobalRef(layoutLog); +} diff --git a/core/res/res/anim/popup_enter_material.xml b/core/res/res/anim/popup_enter_material.xml index ef5b7c05191b..9f771b9bc0ee 100644 --- a/core/res/res/anim/popup_enter_material.xml +++ b/core/res/res/anim/popup_enter_material.xml @@ -22,7 +22,7 @@ android:interpolator="@interpolator/standard" android:duration="@android:integer/config_activityDefaultDur" /> <translate - android:fromYDelta="20dp" + android:fromYDelta="@android:dimen/popup_enter_animation_from_y_delta" android:toYDelta="0" android:interpolator="@interpolator/standard" android:duration="@android:integer/config_activityDefaultDur" /> diff --git a/core/res/res/anim/popup_exit_material.xml b/core/res/res/anim/popup_exit_material.xml index 1efa7021791e..2b79ddfa38d2 100644 --- a/core/res/res/anim/popup_exit_material.xml +++ b/core/res/res/anim/popup_exit_material.xml @@ -23,7 +23,7 @@ android:duration="@android:integer/config_activityShortDur" /> <translate android:fromYDelta="0" - android:toYDelta="-10dp" + android:toYDelta="@android:dimen/popup_exit_animation_to_y_delta" android:interpolator="@interpolator/standard_accelerate" android:duration="@android:integer/config_activityShortDur" /> </set> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 3a2401a97009..7150fca5ab99 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3364,7 +3364,7 @@ area for the user and that ideally it should not be covered. Setting this is only appropriate for UI where the user would likely take action to uncover it. <p>The system will try to respect this, but when not possible will ignore it. - See {@link android.view.View#setPreferKeepClear}. --> + <p>This is equivalent to {@link android.view.View#setPreferKeepClear}.--> <attr name="preferKeepClear" format="boolean" /> <!-- <p>Whether or not the auto handwriting initiation is enabled in this View. diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 4874e6529620..744c3dab9510 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -991,4 +991,11 @@ <dimen name="secondary_rounded_corner_radius_adjustment">0px</dimen> <dimen name="secondary_rounded_corner_radius_top_adjustment">0px</dimen> <dimen name="secondary_rounded_corner_radius_bottom_adjustment">0px</dimen> + + <!-- Default size for user icons (a.k.a. avatar images) --> + <dimen name="user_icon_size">190dp</dimen> + + <!-- Dimensions for the translations of the default dialog animation. --> + <dimen name="popup_enter_animation_from_y_delta">20dp</dimen> + <dimen name="popup_exit_animation_to_y_delta">-10dp</dimen> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e27aa8392caa..e7eeecc2b516 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -545,6 +545,7 @@ <java-symbol type="dimen" name="immersive_mode_cling_width" /> <java-symbol type="dimen" name="accessibility_magnification_indicator_width" /> <java-symbol type="dimen" name="circular_display_mask_thickness" /> + <java-symbol type="dimen" name="user_icon_size" /> <java-symbol type="string" name="add_account_button_label" /> <java-symbol type="string" name="addToDictionary" /> @@ -2357,6 +2358,10 @@ <java-symbol type="string" name="nas_upgrade_notification_learn_more_action" /> <java-symbol type="string" name="nas_upgrade_notification_learn_more_content" /> <java-symbol type="bool" name="config_settingsHelpLinksEnabled" /> + <java-symbol type="integer" name="config_activityDefaultDur" /> + <java-symbol type="integer" name="config_activityShortDur" /> + <java-symbol type="dimen" name="popup_enter_animation_from_y_delta" /> + <java-symbol type="dimen" name="popup_exit_animation_to_y_delta" /> <!-- ImfTest --> <java-symbol type="layout" name="auto_complete_list" /> diff --git a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java new file mode 100644 index 000000000000..c6f592447c22 --- /dev/null +++ b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 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.widget; + +import static com.android.internal.widget.floatingtoolbar.FloatingToolbar.FLOATING_TOOLBAR_TAG; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.content.res.Resources; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.Until; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.R; + +final class FloatingToolbarUtils { + + private final UiDevice mDevice; + + FloatingToolbarUtils() { + mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + } + + void waitForFloatingToolbarPopup() { + mDevice.wait(Until.findObject(By.desc(FLOATING_TOOLBAR_TAG)), 500); + } + + void assertFloatingToolbarIsDisplayed() { + waitForFloatingToolbarPopup(); + assertThat(mDevice.hasObject(By.desc(FLOATING_TOOLBAR_TAG))).isTrue(); + } + + void assertFloatingToolbarContainsItem(String itemLabel) { + waitForFloatingToolbarPopup(); + assertWithMessage("Expected to find item labelled [" + itemLabel + "]") + .that(mDevice.hasObject( + By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel)))) + .isTrue(); + } + + void assertFloatingToolbarDoesNotContainItem(String itemLabel) { + waitForFloatingToolbarPopup(); + assertWithMessage("Expected to not find item labelled [" + itemLabel + "]") + .that(mDevice.hasObject( + By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel)))) + .isFalse(); + } + + void assertFloatingToolbarContainsItemAtIndex(String itemLabel, int index) { + waitForFloatingToolbarPopup(); + assertWithMessage("Expected to find item labelled [" + itemLabel + "] at index " + index) + .that(mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + .findObjects(By.clickable(true)) + .get(index) + .getChildren() + .get(1) + .getText()) + .isEqualTo(itemLabel); + } + + void clickFloatingToolbarItem(String label) { + waitForFloatingToolbarPopup(); + mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + .findObject(By.text(label)) + .click(); + } + + void clickFloatingToolbarOverflowItem(String label) { + // TODO: There might be a benefit to combining this with "clickFloatingToolbarItem" method. + waitForFloatingToolbarPopup(); + mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + .findObject(By.desc(str(R.string.floating_toolbar_open_overflow_description))) + .click(); + mDevice.wait( + Until.findObject(By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(label))), + 1000); + mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + .findObject(By.text(label)) + .click(); + } + + private static String str(int id) { + return Resources.getSystem().getString(id); + } +} diff --git a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java index 28f9ccc10135..90844ea4c76f 100644 --- a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java +++ b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java @@ -17,9 +17,6 @@ package android.widget; import static android.widget.espresso.DragHandleUtils.onHandleView; -import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem; -import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem; -import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup; import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupContainsItem; import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsDisplayed; import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsNotDisplayed; @@ -72,6 +69,7 @@ public class SuggestionsPopupWindowTest { @Rule public final ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(TextViewActivity.class); + private final FloatingToolbarUtils mToolbar = new FloatingToolbarUtils(); private TextViewActivity getActivity() { return mActivityRule.getActivity(); @@ -118,22 +116,19 @@ public class SuggestionsPopupWindowTest { setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e'))); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarContainsItem( - getActivity().getString(com.android.internal.R.string.replace)); - sleepForFloatingToolbarPopup(); - clickFloatingToolbarItem( + mToolbar.clickFloatingToolbarOverflowItem( getActivity().getString(com.android.internal.R.string.replace)); assertSuggestionsPopupIsDisplayed(); } @Test - public void testInsertionActionMode() { + public void testInsertionActionMode() throws Throwable { final String text = "abc def ghi"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(replaceText(text)); + Thread.sleep(500); final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION); @@ -141,10 +136,7 @@ public class SuggestionsPopupWindowTest { onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e'))); onHandleView(com.android.internal.R.id.insertion_handle).perform(click()); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarContainsItem( - getActivity().getString(com.android.internal.R.string.replace)); - clickFloatingToolbarItem( + mToolbar.clickFloatingToolbarItem( getActivity().getString(com.android.internal.R.string.replace)); assertSuggestionsPopupIsDisplayed(); diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index 152992cc57a4..659cd98fb8b7 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -16,15 +16,10 @@ package android.widget; +import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates; import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles; import static android.widget.espresso.DragHandleUtils.onHandleView; -import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem; -import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem; -import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed; -import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex; -import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem; -import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup; import static android.widget.espresso.TextViewActions.Handle; import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex; @@ -64,10 +59,17 @@ import static org.mockito.Mockito.when; import android.app.Activity; import android.app.Instrumentation; +import android.app.PendingIntent; +import android.app.RemoteAction; import android.content.ClipData; import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Icon; import android.os.Bundle; +import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.Until; import android.text.InputType; import android.text.Selection; import android.text.Spannable; @@ -79,6 +81,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.accessibility.AccessibilityNodeInfo; import android.view.textclassifier.SelectionEvent; +import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; @@ -102,6 +105,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Tests the TextView widget from an Activity @@ -116,11 +120,16 @@ public class TextViewActivityTest { private Activity mActivity; private Instrumentation mInstrumentation; + private UiDevice mDevice; + private FloatingToolbarUtils mToolbar; @Before - public void setUp() { + public void setUp() throws Exception { mActivity = mActivityRule.getActivity(); mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mDevice = UiDevice.getInstance(mInstrumentation); + mDevice.wakeUp(); + mToolbar = new FloatingToolbarUtils(); TextClassificationManager tcm = mActivity.getSystemService( TextClassificationManager.class); tcm.setTextClassifier(TextClassifier.NO_OP); @@ -132,14 +141,14 @@ public class TextViewActivityTest { final String helloWorld = "Hello world!"; // We use replaceText instead of typeTextIntoFocusedView to input text to avoid // unintentional interactions with software keyboard. - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); onView(withId(R.id.textview)).check(matches(withText(helloWorld))); } @Test public void testPositionCursorAtTextAtIndex() { final String helloWorld = "Hello world!"; - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("world"))); // Delete text at specified index and see if we got the right one. @@ -152,7 +161,7 @@ public class TextViewActivityTest { // Arabic text. The expected cursorable boundary is // | \u0623 \u064F | \u067A | \u0633 \u0652 | final String text = "\u0623\u064F\u067A\u0633\u0652"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0)); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); @@ -172,7 +181,7 @@ public class TextViewActivityTest { public void testPositionCursorAtTextAtIndex_devanagari() { // Devanagari text. The expected cursorable boundary is | \u0915 \u093E | final String text = "\u0915\u093E"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0)); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); @@ -186,7 +195,7 @@ public class TextViewActivityTest { public void testLongPressToSelect() { final String helloWorld = "Hello Kirk!"; onView(withId(R.id.textview)).perform(click()); - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); onView(withId(R.id.textview)).perform( longPressOnTextAtIndex(helloWorld.indexOf("Kirk"))); @@ -196,7 +205,7 @@ public class TextViewActivityTest { @Test public void testLongPressEmptySpace() { final String helloWorld = "Hello big round sun!"; - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); // Move cursor somewhere else onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("big"))); // Long-press at end of line. @@ -210,7 +219,7 @@ public class TextViewActivityTest { @Test public void testLongPressAndDragToSelect() { final String helloWorld = "Hello little handsome boy!"; - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); onView(withId(R.id.textview)).perform( longPressAndDragOnText(helloWorld.indexOf("little"), helloWorld.indexOf(" boy!"))); @@ -220,7 +229,7 @@ public class TextViewActivityTest { @Test public void testLongPressAndDragToSelect_emoji() { final String text = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 6)); onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE02")); @@ -234,7 +243,7 @@ public class TextViewActivityTest { @Test public void testDragAndDrop() { final String text = "abc def ghi."; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("e"))); onView(withId(R.id.textview)).perform( @@ -254,7 +263,7 @@ public class TextViewActivityTest { @Test public void testDoubleTapToSelect() { final String helloWorld = "Hello SuetYi!"; - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); onView(withId(R.id.textview)).perform( doubleClickOnTextAtIndex(helloWorld.indexOf("SuetYi"))); @@ -265,7 +274,7 @@ public class TextViewActivityTest { @Test public void testDoubleTapAndDragToSelect() { final String helloWorld = "Hello young beautiful person!"; - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); onView(withId(R.id.textview)).perform(doubleTapAndDragOnText(helloWorld.indexOf("young"), helloWorld.indexOf(" person!"))); @@ -275,7 +284,7 @@ public class TextViewActivityTest { @Test public void testDoubleTapAndDragToSelect_multiLine() { final String helloWorld = "abcd\n" + "efg\n" + "hijklm\n" + "nop"; - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); onView(withId(R.id.textview)).perform( doubleTapAndDragOnText(helloWorld.indexOf("m"), helloWorld.indexOf("a"))); onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijklm")); @@ -284,7 +293,7 @@ public class TextViewActivityTest { @Test public void testSelectBackwordsByTouch() { final String helloWorld = "Hello king of the Jungle!"; - onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + setText(helloWorld); onView(withId(R.id.textview)).perform( doubleTapAndDragOnText(helloWorld.indexOf(" Jungle!"), helloWorld.indexOf("king"))); @@ -294,12 +303,11 @@ public class TextViewActivityTest { @Test public void testToolbarAppearsAfterSelection() { final String text = "Toolbar appears after selection."; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform( longPressOnTextAtIndex(text.indexOf("appears"))); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); } @Test @@ -317,13 +325,12 @@ public class TextViewActivityTest { }); mInstrumentation.waitForIdleSync(); - onView(withId(R.id.textview)).perform(replaceText("test")); + setText("test"); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1)); - clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.cut)); + mToolbar.clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.cut)); onView(withId(R.id.textview)).perform(longClick()); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); } @Test @@ -331,8 +338,7 @@ public class TextViewActivityTest { TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview); int position = (textLink.getStart() + textLink.getEnd()) / 2; onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); } @Test @@ -342,23 +348,20 @@ public class TextViewActivityTest { final int position = (textLink.getStart() + textLink.getEnd()) / 2; onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); assertTrue(textView.hasSelection()); // toggle onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); - sleepForFloatingToolbarPopup(); + mToolbar.waitForFloatingToolbarPopup(); assertFalse(textView.hasSelection()); onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); assertTrue(textView.hasSelection()); // click outside onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(0)); - sleepForFloatingToolbarPopup(); assertFalse(textView.hasSelection()); } @@ -372,8 +375,7 @@ public class TextViewActivityTest { }); mInstrumentation.waitForIdleSync(); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); } @Test @@ -385,7 +387,7 @@ public class TextViewActivityTest { final TextView textView = mActivity.findViewById(R.id.textview); textView.setText(text); textView.setCustomSelectionActionModeCallback( - new ActionMode.Callback() { + new ActionModeCallbackAdapter() { @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { menu.clear(); @@ -398,29 +400,19 @@ public class TextViewActivityTest { clickedItem[0] = item; return true; } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) {} }); }); mInstrumentation.waitForIdleSync(); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f"))); - sleepForFloatingToolbarPopup(); // Change the selection so that the menu items are refreshed. final TextView textView = mActivity.findViewById(R.id.textview); onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, 0)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); - clickFloatingToolbarItem("Item"); + mToolbar.clickFloatingToolbarItem("Item"); mInstrumentation.waitForIdleSync(); assertEquals(latestItem[0], clickedItem[0]); @@ -469,13 +461,11 @@ public class TextViewActivityTest { mActivityRule.runOnUiThread(() -> textView.setFocusableInTouchMode(true)); onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); assertTrue(textView.hasSelection()); mActivityRule.runOnUiThread(() -> textView.clearFocus()); mInstrumentation.waitForIdleSync(); - sleepForFloatingToolbarPopup(); assertFalse(textView.hasSelection()); } @@ -488,14 +478,12 @@ public class TextViewActivityTest { onView(withId(R.id.nonselectable_textview)) .perform(clickOnTextAtIndex(nonselectablePosition)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); assertTrue(nonselectableTextView.hasSelection()); - UiDevice device = UiDevice.getInstance(mInstrumentation); - device.openNotification(); + mDevice.openNotification(); Thread.sleep(2000); - device.pressBack(); + mDevice.pressBack(); Thread.sleep(2000); assertFalse(nonselectableTextView.hasSelection()); @@ -528,62 +516,58 @@ public class TextViewActivityTest { } @Test - public void testToolbarAndInsertionHandle() { + public void testToolbarAndInsertionHandle() throws Throwable { final String text = "text"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); + Thread.sleep(500); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle).perform(click()); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); - assertFloatingToolbarContainsItem( + mToolbar.assertFloatingToolbarContainsItem( mActivity.getString(com.android.internal.R.string.selectAll)); - assertFloatingToolbarDoesNotContainItem( + mToolbar.assertFloatingToolbarDoesNotContainItem( mActivity.getString(com.android.internal.R.string.copy)); - assertFloatingToolbarDoesNotContainItem( + mToolbar.assertFloatingToolbarDoesNotContainItem( mActivity.getString(com.android.internal.R.string.cut)); } @Test public void testToolbarAndSelectionHandle() { final String text = "abcd efg hijk"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f"))); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); - assertFloatingToolbarContainsItem( + mToolbar.assertFloatingToolbarContainsItem( mActivity.getString(com.android.internal.R.string.selectAll)); - assertFloatingToolbarContainsItem( + mToolbar.assertFloatingToolbarContainsItem( mActivity.getString(com.android.internal.R.string.copy)); - assertFloatingToolbarContainsItem( + mToolbar.assertFloatingToolbarContainsItem( mActivity.getString(com.android.internal.R.string.cut)); final TextView textView = mActivity.findViewById(R.id.textview); onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a'))); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); onHandleView(com.android.internal.R.id.selection_end_handle) .perform(dragHandle(textView, Handle.SELECTION_END, text.length())); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); - assertFloatingToolbarDoesNotContainItem( + mToolbar.assertFloatingToolbarDoesNotContainItem( mActivity.getString(com.android.internal.R.string.selectAll)); - assertFloatingToolbarContainsItem( + mToolbar.assertFloatingToolbarContainsItem( mActivity.getString(com.android.internal.R.string.copy)); - assertFloatingToolbarContainsItem( + mToolbar.assertFloatingToolbarContainsItem( mActivity.getString(com.android.internal.R.string.cut)); } @Test public void testInsertionHandle() { final String text = "abcd efg hijk "; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); @@ -602,7 +586,7 @@ public class TextViewActivityTest { @Test public void testInsertionHandle_multiLine() { final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); @@ -640,7 +624,7 @@ public class TextViewActivityTest { final TextView textView = mActivity.findViewById(R.id.textview); final String text = "hello the world"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); @@ -654,7 +638,7 @@ public class TextViewActivityTest { enableFlagsForInsertionHandleGestures(); final TextView textView = mActivity.findViewById(R.id.textview); final String text = "hello the world"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); @@ -670,7 +654,7 @@ public class TextViewActivityTest { final TextView textView = mActivity.findViewById(R.id.textview); final String text = "hello the world"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); @@ -685,7 +669,7 @@ public class TextViewActivityTest { final TextView textView = mActivity.findViewById(R.id.textview); final String text = "hello the world"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); @@ -698,7 +682,7 @@ public class TextViewActivityTest { @Test public void testSelectionHandles() { final String text = "abcd efg hijk lmn"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f'))); @@ -720,7 +704,7 @@ public class TextViewActivityTest { @Test public void testSelectionHandles_bidi() { final String text = "abc \u0621\u0622\u0623 def"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622'))); @@ -762,7 +746,7 @@ public class TextViewActivityTest { @Test public void testSelectionHandles_multiLine() { final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); final TextView textView = mActivity.findViewById(R.id.textview); @@ -790,7 +774,7 @@ public class TextViewActivityTest { final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n" + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n" + "\u0639\u063A\u063B"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0634'))); final TextView textView = mActivity.findViewById(R.id.textview); @@ -817,7 +801,7 @@ public class TextViewActivityTest { @Test public void testSelectionHandles_doesNotPassAnotherHandle() { final String text = "abcd efg hijk lmn"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f'))); final TextView textView = mActivity.findViewById(R.id.textview); @@ -834,7 +818,7 @@ public class TextViewActivityTest { @Test public void testSelectionHandles_doesNotPassAnotherHandle_multiLine() { final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); final TextView textView = mActivity.findViewById(R.id.textview); @@ -851,7 +835,7 @@ public class TextViewActivityTest { @Test public void testSelectionHandles_snapToWordBoundary() { final String text = "abcd efg hijk lmn opqr"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i'))); final TextView textView = mActivity.findViewById(R.id.textview); @@ -904,7 +888,7 @@ public class TextViewActivityTest { @Test public void testSelectionHandles_snapToWordBoundary_multiLine() { final String text = "abcd efg\n" + "hijk lmn\n" + "opqr stu"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('m'))); final TextView textView = mActivity.findViewById(R.id.textview); @@ -939,7 +923,7 @@ public class TextViewActivityTest { @Test public void testSelectionHandles_visibleEvenWithEmptyMenu() { ((TextView) mActivity.findViewById(R.id.textview)).setCustomSelectionActionModeCallback( - new ActionMode.Callback() { + new ActionModeCallbackAdapter() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { menu.clear(); @@ -951,17 +935,9 @@ public class TextViewActivityTest { menu.clear(); return true; } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return false; - } - - @Override - public void onDestroyActionMode(ActionMode mode) {} }); final String text = "abcd efg hijk lmn"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f'))); @@ -982,7 +958,7 @@ public class TextViewActivityTest { textView.setCustomSelectionActionModeCallback(amCallback); final String text = "abc def"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); mActivityRule.runOnUiThread( () -> Selection.setSelection((Spannable) textView.getText(), 0, 3)); mInstrumentation.waitForIdleSync(); @@ -991,15 +967,13 @@ public class TextViewActivityTest { // Make sure that "Select All" is included in the selection action mode when the entire text // is not selected. onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e'))); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); // Changing the selection range by API should not interrupt the selection action mode. mActivityRule.runOnUiThread( () -> Selection.setSelection((Spannable) textView.getText(), 0, 3)); mInstrumentation.waitForIdleSync(); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); - assertFloatingToolbarContainsItem( + mToolbar.assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarContainsItem( mActivity.getString(com.android.internal.R.string.selectAll)); // Make sure that "Select All" is no longer included when the entire text is selected by // API. @@ -1007,9 +981,8 @@ public class TextViewActivityTest { () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length())); mInstrumentation.waitForIdleSync(); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); - assertFloatingToolbarDoesNotContainItem( + mToolbar.assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarDoesNotContainItem( mActivity.getString(com.android.internal.R.string.selectAll)); // Make sure that shrinking the selection range to cursor (an empty range) by API // terminates selection action mode and does not trigger the insertion action mode. @@ -1020,17 +993,15 @@ public class TextViewActivityTest { // Make sure that user click can trigger the insertion action mode. onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); onHandleView(com.android.internal.R.id.insertion_handle).perform(click()); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarIsDisplayed(); // Make sure that an existing insertion action mode keeps alive after the insertion point is // moved by API. mActivityRule.runOnUiThread( () -> Selection.setSelection((Spannable) textView.getText(), 0)); mInstrumentation.waitForIdleSync(); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); - assertFloatingToolbarDoesNotContainItem( + mToolbar.assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarDoesNotContainItem( mActivity.getString(com.android.internal.R.string.copy)); // Make sure that selection action mode is started after selection is created by API when // insertion action mode is active. @@ -1038,16 +1009,15 @@ public class TextViewActivityTest { () -> Selection.setSelection((Spannable) textView.getText(), 1, text.length())); mInstrumentation.waitForIdleSync(); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); - assertFloatingToolbarContainsItem( + mToolbar.assertFloatingToolbarIsDisplayed(); + mToolbar.assertFloatingToolbarContainsItem( mActivity.getString(com.android.internal.R.string.copy)); } @Test public void testTransientState() throws Throwable { final String text = "abc def"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); final TextView textView = mActivity.findViewById(R.id.textview); assertFalse(textView.hasTransientState()); @@ -1068,58 +1038,43 @@ public class TextViewActivityTest { @Test public void testResetMenuItemTitle() throws Throwable { - mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null); + mActivity.getSystemService(TextClassificationManager.class) + .setTextClassifier(TextClassifier.NO_OP); final TextView textView = mActivity.findViewById(R.id.textview); final int itemId = 1; - final String title1 = " AFIGBO"; - final int index = title1.indexOf('I'); - final String title2 = title1.substring(index); + final String title1 = "@AFIGBO"; + final int index = 3; + final String title2 = "IGBO"; final String[] title = new String[]{title1}; mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback( - new ActionMode.Callback() { - @Override - public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { - return true; - } - + new ActionModeCallbackAdapter() { @Override public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { - menu.removeItem(itemId); + menu.clear(); menu.add(Menu.NONE /* group */, itemId, 0 /* order */, title[0]); return true; } - - @Override - public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { - return false; - } - - @Override - public void onDestroyActionMode(ActionMode actionMode) { - } })); mInstrumentation.waitForIdleSync(); - onView(withId(R.id.textview)).perform(replaceText(title1)); + setText(title1); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(index)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarContainsItem(title1); + mToolbar.assertFloatingToolbarContainsItem(title1); // Change the menu item title. title[0] = title2; // Change the selection to invalidate the action mode without restarting it. onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, index)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarContainsItem(title2); + mToolbar.assertFloatingToolbarContainsItem(title2); } @Test public void testAssistItemIsAtIndexZero() throws Throwable { - useSystemDefaultTextClassifier(); + final SingleActionTextClassifier tc = useSingleActionTextClassifier(); final TextView textView = mActivity.findViewById(R.id.textview); mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback( - new ActionMode.Callback() { + new ActionModeCallbackAdapter() { @Override public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { // Create another item at order position 0 to confirm that it will never be @@ -1127,33 +1082,19 @@ public class TextViewActivityTest { menu.add(Menu.NONE, 0 /* id */, 0 /* order */, "Test"); return true; } - - @Override - public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { - return false; - } - - @Override - public void onDestroyActionMode(ActionMode actionMode) { - } })); mInstrumentation.waitForIdleSync(); final String text = "droid@android.com"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@'))); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarItemIndex(android.R.id.textAssist, 0); + mToolbar.assertFloatingToolbarContainsItemAtIndex(tc.getActionLabel(), 0); } @Test public void testNoAssistItemForPasswordField() throws Throwable { - useSystemDefaultTextClassifier(); + final SingleActionTextClassifier tc = useSingleActionTextClassifier(); + final TextView textView = mActivity.findViewById(R.id.textview); mActivityRule.runOnUiThread(() -> { textView.setInputType( @@ -1162,23 +1103,22 @@ public class TextViewActivityTest { mInstrumentation.waitForIdleSync(); final String password = "afigbo@android.com"; - onView(withId(R.id.textview)).perform(replaceText(password)); + setText(password); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(password.indexOf('@'))); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist); + mToolbar.assertFloatingToolbarDoesNotContainItem(tc.getActionLabel()); } @Test public void testNoAssistItemForTextFieldWithUnsupportedCharacters() throws Throwable { - useSystemDefaultTextClassifier(); + // NOTE: This test addresses a security bug. + final SingleActionTextClassifier tc = useSingleActionTextClassifier(); final String text = "\u202Emoc.diordna.com"; final TextView textView = mActivity.findViewById(R.id.textview); mActivityRule.runOnUiThread(() -> textView.setText(text)); mInstrumentation.waitForIdleSync(); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('.'))); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist); + mToolbar.assertFloatingToolbarDoesNotContainItem(tc.getActionLabel()); } @Test @@ -1195,10 +1135,9 @@ public class TextViewActivityTest { mInstrumentation.waitForIdleSync(); final String text = "andyroid@android.com"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@'))); - sleepForFloatingToolbarPopup(); - clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy)); + mToolbar.clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy)); mInstrumentation.waitForIdleSync(); final SelectionEvent lastEvent = selectionEvents.get(selectionEvents.size() - 1); @@ -1214,9 +1153,8 @@ public class TextViewActivityTest { final String text = "My number is 987654321"; - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('9'))); - sleepForFloatingToolbarPopup(); onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0)); mInstrumentation.waitForIdleSync(); @@ -1247,9 +1185,8 @@ public class TextViewActivityTest { final String text = "My number is 987654321"; // Long press to trigger selection - onView(withId(R.id.textview)).perform(replaceText(text)); + setText(text); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('9'))); - sleepForFloatingToolbarPopup(); // Type over the selection onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_A)); @@ -1291,16 +1228,14 @@ public class TextViewActivityTest { }); // Long press to trigger selection - onView(withId(R.id.textview)).perform(replaceText("android.com")); + setText("android.com"); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(0)); - sleepForFloatingToolbarPopup(); // Click "Copy" to dismiss the selection. - clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy)); + mToolbar.clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy)); // Long press to trigger another selection - onView(withId(R.id.textview)).perform(replaceText("android@android.com")); + setText("android@android.com"); onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(0)); - sleepForFloatingToolbarPopup(); // suggestSelection should be called in two different TextClassifier sessions. assertEquals(2, testableTextClassifiers.size()); @@ -1312,10 +1247,9 @@ public class TextViewActivityTest { public void testPastePlainText_menuAction() { initializeClipboardWithText(TextStyle.STYLED); - onView(withId(R.id.textview)).perform(replaceText("")); + setText(""); onView(withId(R.id.textview)).perform(longClick()); - sleepForFloatingToolbarPopup(); - clickFloatingToolbarItem( + mToolbar.clickFloatingToolbarItem( mActivity.getString(com.android.internal.R.string.paste_as_plain_text)); mInstrumentation.waitForIdleSync(); @@ -1327,18 +1261,33 @@ public class TextViewActivityTest { public void testPastePlainText_noMenuItemForPlainText() { initializeClipboardWithText(TextStyle.PLAIN); - onView(withId(R.id.textview)).perform(replaceText("")); + setText(""); onView(withId(R.id.textview)).perform(longClick()); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarDoesNotContainItem( + mToolbar.assertFloatingToolbarDoesNotContainItem( mActivity.getString(com.android.internal.R.string.paste_as_plain_text)); } + private void setText(String text) { + onView(withId(R.id.textview)).perform(replaceText(text)); + mDevice.wait(Until.findObject(By.text(text)), 1000); + mInstrumentation.waitForIdleSync(); + } + private void useSystemDefaultTextClassifier() { mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null); } + private SingleActionTextClassifier useSingleActionTextClassifier() { + useSystemDefaultTextClassifier(); + final TextClassificationManager tcm = + mActivity.getSystemService(TextClassificationManager.class); + final SingleActionTextClassifier oneActionTC = + new SingleActionTextClassifier(mActivity, tcm.getTextClassifier()); + tcm.setTextClassifier(oneActionTC); + return oneActionTC; + } + private void initializeClipboardWithText(TextStyle textStyle) { final ClipData clip; switch (textStyle) { @@ -1360,7 +1309,7 @@ public class TextViewActivityTest { PLAIN, STYLED } - private final class TestableTextClassifier implements TextClassifier { + private static final class TestableTextClassifier implements TextClassifier { final List<SelectionEvent> mSelectionEvents = new ArrayList<>(); final List<TextSelection.Request> mTextSelectionRequests = new ArrayList<>(); @@ -1385,4 +1334,54 @@ public class TextViewActivityTest { return mTextSelectionRequests; } } + + private static final class SingleActionTextClassifier implements TextClassifier { + + private final RemoteAction mAction; + private final TextClassifier mOriginal; + private final TextClassification mClassificationResult; + + SingleActionTextClassifier(Context context, TextClassifier original) { + mAction = new RemoteAction( + Icon.createWithResource(context, android.R.drawable.btn_star), + "assist", + "assist", + PendingIntent.getActivity(context, 0, new Intent(), FLAG_IMMUTABLE)); + mClassificationResult = new TextClassification.Builder().addAction(mAction).build(); + mOriginal = Objects.requireNonNull(original); + } + + public String getActionLabel() { + return mAction.getTitle().toString(); + } + + @Override + public TextSelection suggestSelection(TextSelection.Request request) { + final TextSelection sel = mOriginal.suggestSelection(request); + return new TextSelection.Builder( + sel.getSelectionStartIndex(), sel.getSelectionEndIndex()) + .setTextClassification(mClassificationResult) + .build(); + } + } + + private static class ActionModeCallbackAdapter implements ActionMode.Callback { + @Override + public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { + return true; + } + + @Override + public void onDestroyActionMode(ActionMode actionMode) {} + } } diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java deleted file mode 100644 index 4f95cb88e217..000000000000 --- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package android.widget.espresso; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup; -import static androidx.test.espresso.matcher.RootMatchers.withDecorView; -import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isRoot; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withTagValue; -import static androidx.test.espresso.matcher.ViewMatchers.withText; - -import static com.android.internal.widget.floatingtoolbar.LocalFloatingToolbarPopup.MenuItemRepr; - -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.is; - -import android.view.View; -import android.view.ViewGroup; - -import androidx.test.espresso.NoMatchingRootException; -import androidx.test.espresso.NoMatchingViewException; -import androidx.test.espresso.UiController; -import androidx.test.espresso.ViewAction; -import androidx.test.espresso.ViewInteraction; - -import com.android.internal.widget.floatingtoolbar.FloatingToolbar; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -/** - * Espresso utility methods for the floating toolbar. - */ -public class FloatingToolbarEspressoUtils { - private final static Object TAG = FloatingToolbar.FLOATING_TOOLBAR_TAG; - - private FloatingToolbarEspressoUtils() {} - - private static ViewInteraction onFloatingToolBar() { - return onView(withTagValue(is(TAG))) - .inRoot(allOf( - isPlatformPopup(), - withDecorView(hasDescendant(withTagValue(is(TAG)))))); - } - - /** - * Creates a {@link ViewInteraction} for the floating bar menu item with the given matcher. - * - * @param matcher The matcher for the menu item. - */ - public static ViewInteraction onFloatingToolBarItem(Matcher<View> matcher) { - return onView(matcher) - .inRoot(withDecorView(hasDescendant(withTagValue(is(TAG))))); - } - - /** - * Asserts that the floating toolbar is displayed on screen. - * - * @throws AssertionError if the assertion fails - */ - public static void assertFloatingToolbarIsDisplayed() { - onFloatingToolBar().check(matches(isDisplayed())); - } - - /** - * Asserts that the floating toolbar is not displayed on screen. - * - * @throws AssertionError if the assertion fails - * @deprecated Negative assertions are taking too long to timeout in Espresso. - */ - @Deprecated - public static void assertFloatingToolbarIsNotDisplayed() { - try { - onFloatingToolBar().check(matches(isDisplayed())); - } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) { - return; - } - throw new AssertionError("Floating toolbar is displayed"); - } - - private static void toggleOverflow() { - final int id = com.android.internal.R.id.overflow; - onView(allOf(withId(id), isDisplayed())) - .inRoot(withDecorView(hasDescendant(withId(id)))) - .perform(click()); - onView(isRoot()).perform(SLEEP); - } - - public static void sleepForFloatingToolbarPopup() { - onView(isRoot()).perform(SLEEP); - } - - /** - * Asserts that the floating toolbar contains the specified item. - * - * @param itemLabel label of the item. - * @throws AssertionError if the assertion fails - */ - public static void assertFloatingToolbarContainsItem(String itemLabel) { - try{ - onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel)))); - } catch (AssertionError e) { - try{ - toggleOverflow(); - } catch (NoMatchingViewException | NoMatchingRootException e2) { - // No overflow items. - throw e; - } - try{ - onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel)))); - } finally { - toggleOverflow(); - } - } - } - - /** - * Asserts that the floating toolbar contains a specified item at a specified index. - * - * @param menuItemId id of the menu item - * @param index expected index of the menu item in the floating toolbar - * @throws AssertionError if the assertion fails - */ - public static void assertFloatingToolbarItemIndex(final int menuItemId, final int index) { - onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() { - private List<Integer> menuItemIds = new ArrayList<>(); - - @Override - public boolean matchesSafely(View view) { - collectMenuItemIds(view); - return menuItemIds.size() > index && menuItemIds.get(index) == menuItemId; - } - - @Override - public void describeTo(Description description) {} - - private void collectMenuItemIds(View view) { - if (view.getTag() instanceof MenuItemRepr) { - menuItemIds.add(((MenuItemRepr) view.getTag()).itemId); - } else if (view instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) view; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - collectMenuItemIds(viewGroup.getChildAt(i)); - } - } - } - })); - } - - /** - * Asserts that the floating toolbar doesn't contain the specified item. - * - * @param itemLabel label of the item. - * @throws AssertionError if the assertion fails - */ - public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) { - final Predicate<View> hasMenuItemLabel = view -> - view.getTag() instanceof MenuItemRepr - && itemLabel.equals(((MenuItemRepr) view.getTag()).title); - assertFloatingToolbarMenuItem(hasMenuItemLabel, false); - } - - /** - * Asserts that the floating toolbar does not contain a menu item with the specified id. - * - * @param menuItemId id of the menu item - * @throws AssertionError if the assertion fails - */ - public static void assertFloatingToolbarDoesNotContainItem(final int menuItemId) { - final Predicate<View> hasMenuItemId = view -> - view.getTag() instanceof MenuItemRepr - && ((MenuItemRepr) view.getTag()).itemId == menuItemId; - assertFloatingToolbarMenuItem(hasMenuItemId, false); - } - - private static void assertFloatingToolbarMenuItem( - final Predicate<View> predicate, final boolean positiveAssertion) { - onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() { - @Override - public boolean matchesSafely(View view) { - return positiveAssertion == containsItem(view); - } - - @Override - public void describeTo(Description description) {} - - private boolean containsItem(View view) { - if (predicate.test(view)) { - return true; - } else if (view instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) view; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - if (containsItem(viewGroup.getChildAt(i))) { - return true; - } - } - } - return false; - } - })); - } - - /** - * Click specified item on the floating tool bar. - * - * @param itemLabel label of the item. - */ - public static void clickFloatingToolbarItem(String itemLabel) { - try{ - onFloatingToolBarItem(withText(itemLabel)).check(matches(isDisplayed())); - } catch (AssertionError e) { - // Try to find the item in the overflow menu. - toggleOverflow(); - } - onFloatingToolBarItem(withText(itemLabel)).perform(click()); - } - - /** - * ViewAction to sleep to wait floating toolbar's animation. - */ - private static final ViewAction SLEEP = new ViewAction() { - private static final long SLEEP_DURATION = 400; - - @Override - public Matcher<View> getConstraints() { - return isDisplayed(); - } - - @Override - public String getDescription() { - return "Sleep " + SLEEP_DURATION + " ms."; - } - - @Override - public void perform(UiController uiController, View view) { - uiController.loopMainThreadForAtLeast(SLEEP_DURATION); - } - }; -} diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java index 52cb9f318dd0..a52d2e88145f 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -86,11 +86,13 @@ public class WindowContextControllerTest { mController.attachToDisplayArea(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, null /* options */); - assertThat(mController.mAttachedToDisplayArea).isTrue(); + assertThat(mController.mAttachedToDisplayArea).isEqualTo( + WindowContextController.AttachStatus.STATUS_ATTACHED); mController.detachIfNeeded(); - assertThat(mController.mAttachedToDisplayArea).isFalse(); + assertThat(mController.mAttachedToDisplayArea).isEqualTo( + WindowContextController.AttachStatus.STATUS_DETACHED); } @Test(expected = IllegalStateException.class) diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index cd42a34c7557..23ec3ead959f 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -2020,6 +2020,7 @@ public class ChooserActivityTest { .check(matches(isDisplayed())); } + @Ignore // b/220067877 @Test public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() { // enable the work tab feature flag @@ -2304,6 +2305,7 @@ public class ChooserActivityTest { assertThat(logger.numCalls(), is(5)); } + @Ignore // b/220067877 @Test public void testCopyTextToClipboardLogging() throws Exception { Intent sendIntent = createSendTextIntent(); diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java index a2bc77a71c90..3a272256e60e 100644 --- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java +++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java @@ -29,6 +29,7 @@ import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.function.BiFunction; /** * Unit test for {@link AndroidFuture}. @@ -154,4 +155,35 @@ public class AndroidFutureTest { expectThrows(ExecutionException.class, future1::get); assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class); } + + @Test + public void testThenCombine() throws Exception { + String nearFutureString = "near future comes"; + AndroidFuture<String> nearFuture = AndroidFuture.supply(() -> nearFutureString); + String farFutureString = " before far future."; + AndroidFuture<String> farFuture = AndroidFuture.supply(() -> farFutureString); + AndroidFuture<String> combinedFuture = + nearFuture.thenCombine(farFuture, ((s1, s2) -> s1 + s2)); + + assertThat(combinedFuture.get()).isEqualTo(nearFutureString + farFutureString); + } + + @Test + public void testThenCombine_functionThrowingException() throws Exception { + String nearFutureString = "near future comes"; + AndroidFuture<String> nearFuture = AndroidFuture.supply(() -> nearFutureString); + String farFutureString = " before far future."; + AndroidFuture<String> farFuture = AndroidFuture.supply(() -> farFutureString); + UnsupportedOperationException exception = new UnsupportedOperationException( + "Unsupported operation exception thrown!"); + BiFunction<String, String, String> throwingFunction = (s1, s2) -> { + throw exception; + }; + AndroidFuture<String> combinedFuture = nearFuture.thenCombine(farFuture, throwingFunction); + + ExecutionException thrown = expectThrows(ExecutionException.class, + () -> combinedFuture.get()); + + assertThat(thrown.getCause()).isSameInstanceAs(exception); + } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index ac5daf09c6f9..21b2cb007cf3 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -481,6 +481,9 @@ applications that come with the platform <permission name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" /> <permission name="android.permission.NEARBY_WIFI_DEVICES" /> <permission name="android.permission.OVERRIDE_WIFI_CONFIG" /> + <!-- Permission needed for CTS test - ConcurrencyTest#testP2pExternalApprover + P2P external approver API sets require MANAGE_WIFI_AUTO_JOIN permission. --> + <permission name="android.permission.MANAGE_WIFI_AUTO_JOIN" /> <!-- Permission required for CTS test CarrierMessagingServiceWrapperTest --> <permission name="android.permission.BIND_CARRIER_SERVICES"/> <!-- Permission required for CTS test - MusicRecognitionManagerTest --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index e50ad385faf9..9b414681c0fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -52,9 +52,6 @@ import com.android.wm.shell.common.annotations.ShellMainThread; */ public class BackAnimationController implements RemoteCallable<BackAnimationController> { - private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; - public static final boolean IS_ENABLED = SystemProperties - .getInt(BACK_PREDICTABILITY_PROP, 1) > 0; private static final String BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP = "persist.debug.back_predictability_progress_threshold"; private static final int PROGRESS_THRESHOLD = SystemProperties diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 6ffcf101693e..241f1a7d4d1c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -423,7 +423,6 @@ public class BubbleController { WindowContainerTransaction t) { // This is triggered right before the rotation is applied if (fromRotation != toRotation) { - mBubblePositioner.setRotation(toRotation); if (mStackView != null) { // Layout listener set on stackView will update the positioner // once the rotation is applied diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 127d5a8a9966..75b19fb03e73 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -20,6 +20,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PointF; @@ -112,10 +113,6 @@ public class BubblePositioner { update(); } - public void setRotation(int rotation) { - mRotation = rotation; - } - /** * Available space and inset information. Call this when config changes * occur or when added to a window. @@ -273,7 +270,8 @@ public class BubblePositioner { /** @return whether the device is in landscape orientation. */ public boolean isLandscape() { - return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270; + return mContext.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; } /** @return whether the screen is considered large. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 9219baa06157..c6a68dc98da6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1237,7 +1237,7 @@ public class BubbleStackView extends FrameLayout b.getExpandedView().updateFontSize(); } } - if (mBubbleOverflow != null) { + if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) { mBubbleOverflow.getExpandedView().updateFontSize(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 656dae3af5ac..79a24b775d19 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -40,6 +40,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; import java.lang.ref.WeakReference; @@ -105,9 +106,8 @@ public class CompatUIController implements OnDisplaysChangedListener, private CompatUICallback mCallback; - // Only show once automatically in the process life. - private boolean mHasShownSizeCompatHint; - private boolean mHasShownCameraCompatHint; + // Only show each hint once automatically in the process life. + private final CompatUIHintsState mCompatUIHintsState; // Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't // be shown. @@ -127,6 +127,7 @@ public class CompatUIController implements OnDisplaysChangedListener, mMainExecutor = mainExecutor; mDisplayController.addDisplayWindowListener(this); mImeController.addPositionProcessor(this); + mCompatUIHintsState = new CompatUIHintsState(); } /** Returns implementation of {@link CompatUI}. */ @@ -259,19 +260,9 @@ public class CompatUIController implements OnDisplaysChangedListener, @VisibleForTesting CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { - final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context, + return new CompatUIWindowManager(context, taskInfo, mSyncQueue, mCallback, taskListener, - mDisplayController.getDisplayLayout(taskInfo.displayId), mHasShownSizeCompatHint, - mHasShownCameraCompatHint); - // TODO(b/218304113): updates values only if hints are actually shown to the user. - // Only show hints for the first time. - if (taskInfo.topActivityInSizeCompat) { - mHasShownSizeCompatHint = true; - } - if (taskInfo.hasCameraCompatControl()) { - mHasShownCameraCompatHint = true; - } - return compatUIWindowManager; + mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState); } private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 7c6780a632c0..bce3ec4128e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -59,9 +59,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN; @VisibleForTesting - boolean mShouldShowSizeCompatHint; - @VisibleForTesting - boolean mShouldShowCameraCompatHint; + CompatUIHintsState mCompatUIHintsState; @Nullable @VisibleForTesting @@ -70,13 +68,12 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { CompatUIWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, CompatUICallback callback, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, - boolean hasShownSizeCompatHint, boolean hasShownCameraCompatHint) { + CompatUIHintsState compatUIHintsState) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mCallback = callback; mHasSizeCompat = taskInfo.topActivityInSizeCompat; mCameraCompatControlState = taskInfo.cameraCompatControlState; - mShouldShowSizeCompatHint = !hasShownSizeCompatHint; - mShouldShowCameraCompatHint = !hasShownCameraCompatHint; + mCompatUIHintsState = compatUIHintsState; } @Override @@ -212,18 +209,18 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { } // Size Compat mode restart button. mLayout.setRestartButtonVisibility(mHasSizeCompat); - if (mHasSizeCompat && mShouldShowSizeCompatHint) { + // Only show by default for the first time. + if (mHasSizeCompat && !mCompatUIHintsState.mHasShownSizeCompatHint) { mLayout.setSizeCompatHintVisibility(/* show= */ true); - // Only show by default for the first time. - mShouldShowSizeCompatHint = false; + mCompatUIHintsState.mHasShownSizeCompatHint = true; } // Camera control for stretched issues. mLayout.setCameraControlVisibility(shouldShowCameraControl()); - if (shouldShowCameraControl() && mShouldShowCameraCompatHint) { + // Only show by default for the first time. + if (shouldShowCameraControl() && !mCompatUIHintsState.mHasShownCameraCompatHint) { mLayout.setCameraCompatHintVisibility(/* show= */ true); - // Only show by default for the first time. - mShouldShowCameraCompatHint = false; + mCompatUIHintsState.mHasShownCameraCompatHint = true; } if (shouldShowCameraControl()) { mLayout.updateCameraTreatmentButton(mCameraCompatControlState); @@ -234,4 +231,15 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED; } + + /** + * A class holding the state of the compat UI hints, which is shared between all compat UI + * window managers. + */ + static class CompatUIHintsState { + @VisibleForTesting + boolean mHasShownSizeCompatHint; + @VisibleForTesting + boolean mHasShownCameraCompatHint; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 2e54c792ed57..c94f3d197dea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -168,8 +168,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static DragAndDrop provideDragAndDrop(DragAndDropController dragAndDropController) { - return dragAndDropController.asDragAndDrop(); + static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) { + return Optional.of(dragAndDropController.asDragAndDrop()); } @WMSingleton @@ -184,8 +184,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static CompatUI provideCompatUI(CompatUIController compatUIController) { - return compatUIController.asCompatUI(); + static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) { + return Optional.of(compatUIController.asCompatUI()); } @WMSingleton @@ -699,10 +699,7 @@ public abstract class WMShellBaseModule { Context context, @ShellMainThread ShellExecutor shellExecutor ) { - if (BackAnimationController.IS_ENABLED) { - return Optional.of( - new BackAnimationController(shellExecutor, context)); - } - return Optional.empty(); + return Optional.of( + new BackAnimationController(shellExecutor, context)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java index 5c205f97beb7..8f9636c0bb30 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java @@ -27,7 +27,10 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Trace; +import androidx.annotation.Nullable; + import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.wm.shell.R; import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ChoreographerSfVsync; @@ -35,7 +38,6 @@ import com.android.wm.shell.common.annotations.ExternalMainThread; import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; -import com.android.wm.shell.R; import dagger.Module; import dagger.Provides; @@ -53,7 +55,7 @@ public abstract class WMShellConcurrencyModule { /** * Returns whether to enable a separate shell thread for the shell features. */ - private static boolean enableShellMainThread(Context context) { + public static boolean enableShellMainThread(Context context) { return context.getResources().getBoolean(R.bool.config_enableShellMainThread); } @@ -85,23 +87,41 @@ public abstract class WMShellConcurrencyModule { } /** + * Creates a shell main thread to be injected into the shell components. This does not provide + * the {@param HandleThread}, but is used to create the thread prior to initializing the + * WM component, and is explicitly bound. + * + * See {@link com.android.systemui.SystemUIFactory#init(Context, boolean)}. + */ + public static HandlerThread createShellMainThread() { + HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY); + return mainThread; + } + + /** * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe * multiple types of messages, etc.) + * + * @param mainThread If non-null, this thread is expected to be started already */ @WMSingleton @Provides @ShellMainThread public static Handler provideShellMainHandler(Context context, + @Nullable @ShellMainThread HandlerThread mainThread, @ExternalMainThread Handler sysuiMainHandler) { if (enableShellMainThread(context)) { - HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY); - mainThread.start(); - if (Build.IS_DEBUGGABLE) { - mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); - mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, - MSGQ_SLOW_DELIVERY_THRESHOLD_MS); - } - return Handler.createAsync(mainThread.getLooper()); + if (mainThread == null) { + // If this thread wasn't pre-emptively started, then create and start it + mainThread = createShellMainThread(); + mainThread.start(); + } + if (Build.IS_DEBUGGABLE) { + mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); + mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, + MSGQ_SLOW_DELIVERY_THRESHOLD_MS); + } + return Handler.createAsync(mainThread.getLooper()); } return sysuiMainHandler; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 67b39839826c..1eb9501ac076 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -310,6 +310,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return mPipTransitionState.isInPip(); } + private boolean isLaunchIntoPipTask() { + return mPictureInPictureParams != null && mPictureInPictureParams.isLaunchIntoPip(); + } + /** * Returns whether the entry animation is waiting to be started. */ @@ -397,6 +401,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (isLaunchIntoPipTask()) { + exitLaunchIntoPipTask(wct); + return; + } if (ENABLE_SHELL_TRANSITIONS) { if (requestEnterSplit && mSplitScreenOptional.isPresent()) { @@ -468,6 +476,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }); } + private void exitLaunchIntoPipTask(WindowContainerTransaction wct) { + wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */); + mTaskOrganizer.applyTransaction(wct); + + // Remove the PiP with fade-out animation right after the host Task is brought to front. + removePip(); + } + private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) { // Reset the final windowing mode. wct.setWindowingMode(mToken, getOutPipWindowingMode()); @@ -563,6 +579,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Log.d(TAG, "Alpha animation is expired. Use bounds animation."); mOneShotAnimationType = ANIM_TYPE_BOUNDS; } + + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + // For Shell transition, we will animate the window in PipTransition#startAnimation + // instead of #onTaskAppeared. + return; + } + if (mWaitForFixedRotation) { onTaskAppearedWithFixedRotation(); return; @@ -572,15 +595,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Objects.requireNonNull(destinationBounds, "Missing destination bounds"); final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { - mPipMenuController.attach(mLeash); - } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { - mOneShotAnimationType = ANIM_TYPE_BOUNDS; - } - return; - } - if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { mPipMenuController.attach(mLeash); final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( @@ -729,7 +743,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}. + * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int, boolean)}. * Meanwhile this callback is invoked whenever the task is removed. For instance: * - as a result of removeRootTasksInWindowingModes from WM * - activity itself is died @@ -813,6 +827,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mNextRotation = newRotation; mWaitForFixedRotation = true; + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + // The fixed rotation will also be included in the transition info. However, if it is + // not a PIP transition (such as open another app to different orientation), + // PIP transition handler may not be aware of the fixed rotation start. + // Notify the PIP transition handler so that it can fade out the PIP window early for + // fixed transition of other windows. + mPipTransitionController.onFixedRotationStarted(); + return; + } + if (mPipTransitionState.isInPip()) { // Fade out the existing PiP to avoid jump cut during seamless rotation. fadeExistingPip(false /* show */); @@ -824,6 +848,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (!mWaitForFixedRotation) { return; } + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + clearWaitForFixedRotation(); + return; + } if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) { if (mPipTransitionState.getInSwipePipToHomeTransition()) { onEndOfSwipePipToHomeTransition(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 3e5d5f645725..60aac6806623 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.util.RotationUtils.deltaRotation; @@ -32,9 +33,11 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; +import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; @@ -47,6 +50,7 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; +import android.util.Log; import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -86,6 +90,16 @@ public class PipTransition extends PipTransitionController { /** The Task window that is currently in PIP windowing mode. */ @Nullable private WindowContainerToken mCurrentPipTaskToken; + /** Whether display is in fixed rotation. */ + private boolean mInFixedRotation; + /** + * The rotation that the display will apply after expanding PiP to fullscreen. This is only + * meaningful if {@link #mInFixedRotation} is true. + */ + @Surface.Rotation + private int mFixedRotation; + /** Whether the PIP window has fade out for fixed rotation. */ + private boolean mHasFadeOut; public PipTransition(Context context, PipBoundsState pipBoundsState, @@ -136,35 +150,41 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + final TransitionInfo.Change currentPipChange = findCurrentPipChange(info); + final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + mInFixedRotation = fixedRotationChange != null; + mFixedRotation = mInFixedRotation + ? fixedRotationChange.getEndFixedRotation() + : ROTATION_UNDEFINED; + // Exiting PIP. final int type = info.getType(); if (transition.equals(mExitTransition)) { mExitDestinationBounds.setEmpty(); mExitTransition = null; - + mHasFadeOut = false; if (mFinishCallback != null) { mFinishCallback.onTransitionFinished(null, null); mFinishCallback = null; throw new RuntimeException("Previous callback not called, aborting exit PIP."); } - final TransitionInfo.Change exitPipChange = findCurrentPipChange(info); - if (exitPipChange == null) { + if (currentPipChange == null) { throw new RuntimeException("Cannot find the pip window for exit-pip transition."); } switch (type) { case TRANSIT_EXIT_PIP: startExitAnimation(info, startTransaction, finishTransaction, finishCallback, - exitPipChange); + currentPipChange); break; case TRANSIT_EXIT_PIP_TO_SPLIT: startExitToSplitAnimation(info, startTransaction, finishTransaction, - finishCallback, exitPipChange); + finishCallback, currentPipChange); break; case TRANSIT_REMOVE_PIP: removePipImmediately(info, startTransaction, finishTransaction, finishCallback, - exitPipChange); + currentPipChange); break; default: throw new IllegalStateException("mExitTransition with unexpected transit type=" @@ -177,7 +197,6 @@ public class PipTransition extends PipTransitionController { // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can // happen when a new activity requests enter PIP). In this case, we just show this Task in // its end state, and play other animation as normal. - final TransitionInfo.Change currentPipChange = findCurrentPipChange(info); if (currentPipChange != null && currentPipChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) { resetPrevPip(currentPipChange, startTransaction); @@ -193,6 +212,12 @@ public class PipTransition extends PipTransitionController { if (currentPipChange != null) { updatePipForUnhandledTransition(currentPipChange, startTransaction, finishTransaction); } + + // Fade in the fadeout PIP when the fixed rotation is finished. + if (mPipTransitionState.isInPip() && !mInFixedRotation && mHasFadeOut) { + fadeExistingPip(true /* show */); + } + return false; } @@ -242,9 +267,8 @@ public class PipTransition extends PipTransitionController { public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, @Nullable SurfaceControl.Transaction tx) { - if (isInPipDirection(direction)) { - mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP); + mPipTransitionState.setTransitionState(ENTERED_PIP); } // If there is an expected exit transition, then the exit will be "merged" into this // transition so don't fire the finish-callback in that case. @@ -268,6 +292,16 @@ public class PipTransition extends PipTransitionController { mFinishCallback = null; } + @Override + public void onFixedRotationStarted() { + // The transition with this fixed rotation may be handled by other handler before reaching + // PipTransition, so we cannot do this in #startAnimation. + if (mPipTransitionState.getTransitionState() == ENTERED_PIP && !mHasFadeOut) { + // Fade out the existing PiP to avoid jump cut during seamless rotation. + fadeExistingPip(false /* show */); + } + } + @Nullable private TransitionInfo.Change findCurrentPipChange(@NonNull TransitionInfo info) { if (mCurrentPipTaskToken == null) { @@ -282,6 +316,17 @@ public class PipTransition extends PipTransitionController { return null; } + @Nullable + private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getEndFixedRotation() != ROTATION_UNDEFINED) { + return change; + } + } + return null; + } + private void startExitAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @@ -453,6 +498,7 @@ public class PipTransition extends PipTransitionController { } // Keep track of the PIP task. mCurrentPipTaskToken = enterPip.getContainer(); + mHasFadeOut = false; if (mFinishCallback != null) { mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); @@ -465,12 +511,25 @@ public class PipTransition extends PipTransitionController { startTransaction.show(wallpaper.getLeash()); startTransaction.setAlpha(wallpaper.getLeash(), 1.f); } + // Make sure other open changes are visible as entering PIP. Some may be hidden in + // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change == enterPip || change == wallpaper) { + continue; + } + if (isOpeningType(change.getMode())) { + final SurfaceControl leash = change.getLeash(); + startTransaction.show(leash).setAlpha(leash, 1.f); + } + } mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); mFinishCallback = finishCallback; + final int endRotation = mInFixedRotation ? mFixedRotation : enterPip.getEndRotation(); return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(), startTransaction, finishTransaction, enterPip.getStartRotation(), - enterPip.getEndRotation()); + endRotation); } private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash, @@ -481,25 +540,36 @@ public class PipTransition extends PipTransitionController { taskInfo.topActivityInfo); final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds(); + int rotationDelta = deltaRotation(startRotation, endRotation); + Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( + taskInfo.pictureInPictureParams, currentBounds); + if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { + // Need to get the bounds of new rotation in old rotation for fixed rotation, + sourceHintRect = computeRotatedBounds(rotationDelta, startRotation, endRotation, + taskInfo, TRANSITION_DIRECTION_TO_PIP, destinationBounds, sourceHintRect); + } PipAnimationController.PipTransitionAnimator animator; // Set corner radius for entering pip. mSurfaceTransactionHelper .crop(finishTransaction, leash, destinationBounds) .round(finishTransaction, leash, true /* applyCornerRadius */); + mPipMenuController.attach(leash); + if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { mOneShotAnimationType = ANIM_TYPE_BOUNDS; - - // PiP menu is attached late in the process here to avoid any artifacts on the leash - // caused by addShellRoot when in gesture navigation mode. - mPipMenuController.attach(leash); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9]) .setPosition(leash, destinationBounds.left, destinationBounds.top) .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()); startTransaction.merge(tx); startTransaction.apply(); + if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { + // For fixed rotation, set the destination bounds to the new rotation coordinates + // at the end. + destinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); + } mPipBoundsState.setBounds(destinationBounds); onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */); sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); @@ -507,17 +577,14 @@ public class PipTransition extends PipTransitionController { return true; } - int rotationDelta = deltaRotation(endRotation, startRotation); if (rotationDelta != Surface.ROTATION_0) { Matrix tmpTransform = new Matrix(); - tmpTransform.postRotate(rotationDelta == Surface.ROTATION_90 - ? Surface.ROTATION_270 : Surface.ROTATION_90); + tmpTransform.postRotate(rotationDelta); startTransaction.setMatrix(leash, tmpTransform, new float[9]); } if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { - final Rect sourceHintRect = - PipBoundsAlgorithm.getValidSourceHintRect( - taskInfo.pictureInPictureParams, currentBounds); + // Reverse the rotation for Shell transition animation. + rotationDelta = deltaRotation(rotationDelta, 0); animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, rotationDelta); @@ -528,9 +595,6 @@ public class PipTransition extends PipTransitionController { } } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { startTransaction.setAlpha(leash, 0f); - // PiP menu is attached late in the process here to avoid any artifacts on the leash - // caused by addShellRoot when in gesture navigation mode. - mPipMenuController.attach(leash); animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); mOneShotAnimationType = ANIM_TYPE_BOUNDS; @@ -541,12 +605,47 @@ public class PipTransition extends PipTransitionController { startTransaction.apply(); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) - .setDuration(mEnterExitAnimationDuration) - .start(); + .setDuration(mEnterExitAnimationDuration); + if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { + // For fixed rotation, the animation destination bounds is in old rotation coordinates. + // Set the destination bounds to new coordinates after the animation is finished. + // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation. + animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); + } + animator.start(); return true; } + /** Computes destination bounds in old rotation and returns source hint rect if available. */ + @Nullable + private Rect computeRotatedBounds(int rotationDelta, int startRotation, int endRotation, + TaskInfo taskInfo, int direction, Rect outDestinationBounds, + @Nullable Rect sourceHintRect) { + if (direction == TRANSITION_DIRECTION_TO_PIP) { + mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation); + final Rect displayBounds = mPipBoundsState.getDisplayBounds(); + outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); + // Transform the destination bounds to current display coordinates. + rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation); + // When entering PiP (from button navigation mode), adjust the source rect hint by + // display cutout if applicable. + if (sourceHintRect != null && taskInfo.displayCutoutInsets != null) { + if (rotationDelta == Surface.ROTATION_270) { + sourceHintRect.offset(taskInfo.displayCutoutInsets.left, + taskInfo.displayCutoutInsets.top); + } + } + } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) { + final Rect rotatedDestinationBounds = new Rect(outDestinationBounds); + rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(), + rotationDelta); + return PipBoundsAlgorithm.getValidSourceHintRect(taskInfo.pictureInPictureParams, + rotatedDestinationBounds); + } + return sourceHintRect; + } + private void startExitToSplitAnimation(TransitionInfo info, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, @@ -595,6 +694,13 @@ public class PipTransition extends PipTransitionController { startTransaction.setCornerRadius(leash, 0); startTransaction.setPosition(leash, bounds.left, bounds.top); + if (mHasFadeOut && prevPipChange.getTaskInfo().isVisible()) { + if (mPipAnimationController.getCurrentAnimator() != null) { + mPipAnimationController.getCurrentAnimator().cancel(); + } + startTransaction.setAlpha(leash, 1); + } + mHasFadeOut = false; mCurrentPipTaskToken = null; mPipOrganizer.onExitPipFinished(prevPipChange.getTaskInfo()); } @@ -615,6 +721,25 @@ public class PipTransition extends PipTransitionController { .round(finishTransaction, leash, isInPip); } + /** Hides and shows the existing PIP during fixed rotation transition of other activities. */ + private void fadeExistingPip(boolean show) { + final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); + final TaskInfo taskInfo = mPipOrganizer.getTaskInfo(); + if (leash == null || !leash.isValid() || taskInfo == null) { + Log.w(TAG, "Invalid leash on fadeExistingPip: " + leash); + return; + } + final float alphaStart = show ? 0 : 1; + final float alphaEnd = show ? 1 : 0; + mPipAnimationController + .getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), alphaStart, alphaEnd) + .setTransitionDirection(TRANSITION_DIRECTION_SAME) + .setPipAnimationCallback(mPipAnimationCallback) + .setDuration(mEnterExitAnimationDuration) + .start(); + mHasFadeOut = !show; + } + private void finishResizeForMenu(Rect destinationBounds) { mPipMenuController.movePipMenu(null, null, destinationBounds); mPipMenuController.updateMenuBounds(destinationBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 3403fb5aa940..02e713d2eaa3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -123,6 +123,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH public void forceFinishTransition() { } + /** Called when the fixed rotation started. */ + public void onFixedRotationStarted() { + } + public PipTransitionController(PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, Transitions transitions, diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml index 556742e2ac5f..574a9f4da627 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml @@ -26,16 +26,8 @@ <option name="shell-timeout" value="6600s" /> <option name="test-timeout" value="6000s" /> <option name="hidden-api-checks" value="false" /> - <option name="device-listeners" - value="com.android.server.wm.flicker.TraceFileReadyListener" /> </test> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="pull-pattern-keys" value="(\w)+\.winscope" /> - <option name="pull-pattern-keys" value="(\w)+\.mp4" /> - <option name="collect-on-run-ended-only" value="false" /> - <option name="clean-up" value="true" /> - </metrics_collector> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> <option name="directory-keys" value="/sdcard/flicker" /> <option name="collect-on-run-ended-only" value="true" /> <option name="clean-up" value="true" /> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt index 0a3321eb5a72..a57d3e63b559 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.bubble -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 19e020a52dee..5fc80db6c617 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt index 8729bb6776f0..87e927fd50ea 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -24,7 +24,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.traces.region.RegionSubject +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -58,6 +61,11 @@ import org.junit.runners.Parameterized open class MovePipDownShelfHeightChangeTest( testSpec: FlickerTestParameter ) : MovePipShelfHeightTransition(testSpec) { + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } + /** * Defines the transition used to run the test */ diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt index 6c80daadfdd9..0ff260b94dc8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -58,7 +58,7 @@ class MovePipDownShelfHeightChangeTest_ShellTransit( testSpec: FlickerTestParameter ) : MovePipDownShelfHeightChangeTest(testSpec) { @Before - fun before() { + override fun before() { Assume.assumeTrue(isShellTransitionsEnabled) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index 4bc8eb13e26f..388b5e0b5e47 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import android.view.Surface import com.android.server.wm.flicker.FlickerParametersRunnerFactory diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index 352805b6aaf6..7d3e718313e6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; import org.junit.Before; import org.junit.Test; @@ -77,8 +78,7 @@ public class CompatUILayoutTest extends ShellTestCase { mWindowManager = new CompatUIWindowManager(mContext, createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN), mSyncTransactionQueue, mCallback, mTaskListener, - new DisplayLayout(), /* hasShownSizeCompatHint= */ false, - /* hasShownCameraCompatHint= */ false); + new DisplayLayout(), new CompatUIHintsState()); mLayout = (CompatUILayout) LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index f9cfd1262364..e79b803b4304 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -51,6 +51,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; import org.junit.Before; import org.junit.Test; @@ -85,8 +86,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { mWindowManager = new CompatUIWindowManager(mContext, createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN), mSyncTransactionQueue, mCallback, mTaskListener, - new DisplayLayout(), /* hasShownSizeCompatHint= */ false, - /* hasShownCameraCompatHint= */ false); + new DisplayLayout(), new CompatUIHintsState()); spyOn(mWindowManager); doReturn(mLayout).when(mWindowManager).inflateLayout(); @@ -102,7 +102,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { verify(mWindowManager, never()).inflateLayout(); // Doesn't create hint popup. - mWindowManager.mShouldShowSizeCompatHint = false; + mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true; assertTrue(mWindowManager.createLayout(/* canShow= */ true)); verify(mWindowManager).inflateLayout(); @@ -113,14 +113,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase { clearInvocations(mWindowManager); clearInvocations(mLayout); mWindowManager.release(); - mWindowManager.mShouldShowSizeCompatHint = true; + mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = false; assertTrue(mWindowManager.createLayout(/* canShow= */ true)); verify(mWindowManager).inflateLayout(); assertNotNull(mLayout); verify(mLayout).setRestartButtonVisibility(/* show= */ true); verify(mLayout).setSizeCompatHintVisibility(/* show= */ true); - assertFalse(mWindowManager.mShouldShowSizeCompatHint); + assertTrue(mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint); // Returns false and doesn't create layout if has Size Compat is false. clearInvocations(mWindowManager); @@ -140,7 +140,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { verify(mWindowManager, never()).inflateLayout(); // Doesn't create hint popup. - mWindowManager.mShouldShowCameraCompatHint = false; + mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = true; assertTrue(mWindowManager.createLayout(/* canShow= */ true)); verify(mWindowManager).inflateLayout(); @@ -151,14 +151,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase { clearInvocations(mWindowManager); clearInvocations(mLayout); mWindowManager.release(); - mWindowManager.mShouldShowCameraCompatHint = true; + mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = false; assertTrue(mWindowManager.createLayout(/* canShow= */ true)); verify(mWindowManager).inflateLayout(); assertNotNull(mLayout); verify(mLayout).setCameraControlVisibility(/* show= */ true); verify(mLayout).setCameraCompatHintVisibility(/* show= */ true); - assertFalse(mWindowManager.mShouldShowCameraCompatHint); + assertTrue(mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint); // Returns false and doesn't create layout if Camera Compat state is hidden clearInvocations(mWindowManager); @@ -411,7 +411,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { public void testOnRestartButtonLongClicked_showHint() { // Not create hint popup. mWindowManager.mHasSizeCompat = true; - mWindowManager.mShouldShowSizeCompatHint = false; + mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true; mWindowManager.createLayout(/* canShow= */ true); verify(mWindowManager).inflateLayout(); @@ -423,10 +423,10 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test - public void testOnCamerControlLongClicked_showHint() { + public void testOnCameraControlLongClicked_showHint() { // Not create hint popup. mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; - mWindowManager.mShouldShowCameraCompatHint = false; + mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = true; mWindowManager.createLayout(/* canShow= */ true); verify(mWindowManager).inflateLayout(); diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java index 29888e147c26..f140c68b19db 100644 --- a/location/java/android/location/SatellitePvt.java +++ b/location/java/android/location/SatellitePvt.java @@ -144,8 +144,8 @@ public final class SatellitePvt implements Parcelable { private final ClockInfo mClockInfo; private final double mIonoDelayMeters; private final double mTropoDelayMeters; - private final int mTimeOfClock; - private final int mTimeOfEphemeris; + private final long mTimeOfClock; + private final long mTimeOfEphemeris; private final int mIssueOfDataClock; private final int mIssueOfDataEphemeris; @EphemerisSource @@ -457,8 +457,8 @@ public final class SatellitePvt implements Parcelable { @Nullable ClockInfo clockInfo, double ionoDelayMeters, double tropoDelayMeters, - int timeOfClock, - int timeOfEphemeris, + long timeOfClock, + long timeOfEphemeris, int issueOfDataClock, int issueOfDataEphemeris, @EphemerisSource int ephemerisSource) { @@ -547,26 +547,28 @@ public final class SatellitePvt implements Parcelable { /** * Time of Clock. * - * <p>This is defined in GPS ICD200 documentation (e.g., - * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>). + * <p>The value is in seconds since GPS epoch, regardless of the constellation. + * + * <p>The value is not encoded as in GPS ICD200 documentation. * * <p>This field is valid if {@link #hasTimeOfClock()} is true. */ - @IntRange(from = 0, to = 604784) - public int getTimeOfClock() { + @IntRange(from = 0) + public long getTimeOfClock() { return mTimeOfClock; } /** * Time of ephemeris. * - * <p>This is defined in GPS ICD200 documentation (e.g., - * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>). + * <p>The value is in seconds since GPS epoch, regardless of the constellation. + * + * <p>The value is not encoded as in GPS ICD200 documentation. * * <p>This field is valid if {@link #hasTimeOfEphemeris()} is true. */ - @IntRange(from = 0, to = 604784) - public int getTimeOfEphemeris() { + @IntRange(from = 0) + public long getTimeOfEphemeris() { return mTimeOfEphemeris; } @@ -630,8 +632,8 @@ public final class SatellitePvt implements Parcelable { android.location.SatellitePvt.ClockInfo.class); double ionoDelayMeters = in.readDouble(); double tropoDelayMeters = in.readDouble(); - int toc = in.readInt(); - int toe = in.readInt(); + long toc = in.readLong(); + long toe = in.readLong(); int iodc = in.readInt(); int iode = in.readInt(); int ephemerisSource = in.readInt(); @@ -669,8 +671,8 @@ public final class SatellitePvt implements Parcelable { parcel.writeParcelable(mClockInfo, flags); parcel.writeDouble(mIonoDelayMeters); parcel.writeDouble(mTropoDelayMeters); - parcel.writeInt(mTimeOfClock); - parcel.writeInt(mTimeOfEphemeris); + parcel.writeLong(mTimeOfClock); + parcel.writeLong(mTimeOfEphemeris); parcel.writeInt(mIssueOfDataClock); parcel.writeInt(mIssueOfDataEphemeris); parcel.writeInt(mEphemerisSource); @@ -707,8 +709,8 @@ public final class SatellitePvt implements Parcelable { @Nullable private ClockInfo mClockInfo; private double mIonoDelayMeters; private double mTropoDelayMeters; - private int mTimeOfClock; - private int mTimeOfEphemeris; + private long mTimeOfClock; + private long mTimeOfEphemeris; private int mIssueOfDataClock; private int mIssueOfDataEphemeris; @EphemerisSource @@ -721,8 +723,7 @@ public final class SatellitePvt implements Parcelable { * @return builder object */ @NonNull - public Builder setPositionEcef( - @NonNull PositionEcef positionEcef) { + public Builder setPositionEcef(@NonNull PositionEcef positionEcef) { mPositionEcef = positionEcef; updateFlags(); return this; @@ -735,8 +736,7 @@ public final class SatellitePvt implements Parcelable { * @return builder object */ @NonNull - public Builder setVelocityEcef( - @NonNull VelocityEcef velocityEcef) { + public Builder setVelocityEcef(@NonNull VelocityEcef velocityEcef) { mVelocityEcef = velocityEcef; updateFlags(); return this; @@ -749,8 +749,7 @@ public final class SatellitePvt implements Parcelable { * @return builder object */ @NonNull - public Builder setClockInfo( - @NonNull ClockInfo clockInfo) { + public Builder setClockInfo(@NonNull ClockInfo clockInfo) { mClockInfo = clockInfo; updateFlags(); return this; @@ -793,12 +792,16 @@ public final class SatellitePvt implements Parcelable { /** * Set time of clock in seconds. * + * <p>The value is in seconds since GPS epoch, regardless of the constellation. + * + * <p>The value is not encoded as in GPS ICD200 documentation. + * * @param timeOfClock time of clock (seconds) * @return builder object */ @NonNull - public Builder setTimeOfClock(@IntRange(from = 0, to = 604784) int timeOfClock) { - Preconditions.checkArgumentInRange(timeOfClock, 0, 604784, "timeOfClock"); + public Builder setTimeOfClock(@IntRange(from = 0) long timeOfClock) { + Preconditions.checkArgumentNonnegative(timeOfClock); mTimeOfClock = timeOfClock; mFlags = (byte) (mFlags | HAS_TIME_OF_CLOCK); return this; @@ -807,12 +810,16 @@ public final class SatellitePvt implements Parcelable { /** * Set time of ephemeris in seconds. * + * <p>The value is in seconds since GPS epoch, regardless of the constellation. + * + * <p>The value is not encoded as in GPS ICD200 documentation. + * * @param timeOfEphemeris time of ephemeris (seconds) * @return builder object */ @NonNull - public Builder setTimeOfEphemeris(@IntRange(from = 0, to = 604784) int timeOfEphemeris) { - Preconditions.checkArgumentInRange(timeOfEphemeris, 0, 604784, "timeOfEphemeris"); + public Builder setTimeOfEphemeris(@IntRange(from = 0) int timeOfEphemeris) { + Preconditions.checkArgumentNonnegative(timeOfEphemeris); mTimeOfEphemeris = timeOfEphemeris; mFlags = (byte) (mFlags | HAS_TIME_OF_EPHEMERIS); return this; diff --git a/media/java/Android.bp b/media/java/Android.bp index c7c1d54cd3c6..6878f9d61f6d 100644 --- a/media/java/Android.bp +++ b/media/java/Android.bp @@ -16,7 +16,9 @@ filegroup { exclude_srcs: [ ":framework-media-tv-tunerresourcemanager-sources-aidl", ], - visibility: ["//frameworks/base"], + visibility: [ + "//frameworks/base", + ], } filegroup { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 3887372d00b5..6e695e68fbf2 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -7294,8 +7294,13 @@ public class AudioManager { * Ultrasound playback and capture, false otherwise. */ @SystemApi - public static boolean isUltrasoundSupported() { - return AudioSystem.isUltrasoundSupported(); + @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) + public boolean isUltrasoundSupported() { + try { + return getService().isUltrasoundSupported(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 2c9f015cf4ce..d702eb9aef57 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -138,6 +138,8 @@ interface IAudioService { boolean isMicrophoneMuted(); + boolean isUltrasoundSupported(); + void setMicrophoneMute(boolean on, String callingPackage, int userId, in String attributionTag); oneway void setMicrophoneMuteFromSwitch(boolean on); diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index 5348d4e358d0..74c549943a74 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -112,20 +112,11 @@ public final class MidiManager { // Binder stub for receiving device notifications from MidiService private class DeviceListener extends IMidiDeviceListener.Stub { private final DeviceCallback mCallback; - private final Handler mHandler; private final Executor mExecutor; private final int mTransport; - DeviceListener(DeviceCallback callback, Handler handler, int transport) { - mCallback = callback; - mHandler = handler; - mExecutor = null; - mTransport = transport; - } - DeviceListener(DeviceCallback callback, Executor executor, int transport) { mCallback = callback; - mHandler = null; mExecutor = executor; mTransport = transport; } @@ -136,13 +127,6 @@ public final class MidiManager { if (mExecutor != null) { mExecutor.execute(() -> mCallback.onDeviceAdded(device)); - } else if (mHandler != null) { - final MidiDeviceInfo deviceF = device; - mHandler.post(new Runnable() { - @Override public void run() { - mCallback.onDeviceAdded(deviceF); - } - }); } else { mCallback.onDeviceAdded(device); } @@ -155,13 +139,6 @@ public final class MidiManager { if (mExecutor != null) { mExecutor.execute(() -> mCallback.onDeviceRemoved(device)); - } else if (mHandler != null) { - final MidiDeviceInfo deviceF = device; - mHandler.post(new Runnable() { - @Override public void run() { - mCallback.onDeviceRemoved(deviceF); - } - }); } else { mCallback.onDeviceRemoved(device); } @@ -173,13 +150,6 @@ public final class MidiManager { if (mExecutor != null) { mExecutor.execute(() -> mCallback.onDeviceStatusChanged(status)); - } else if (mHandler != null) { - final MidiDeviceStatus statusF = status; - mHandler.post(new Runnable() { - @Override public void run() { - mCallback.onDeviceStatusChanged(statusF); - } - }); } else { mCallback.onDeviceStatusChanged(status); } @@ -275,7 +245,11 @@ public final class MidiManager { */ @Deprecated public void registerDeviceCallback(DeviceCallback callback, Handler handler) { - DeviceListener deviceListener = new DeviceListener(callback, handler, + Executor executor = null; + if (handler != null) { + executor = handler::post; + } + DeviceListener deviceListener = new DeviceListener(callback, executor, TRANSPORT_MIDI_BYTE_STREAM); try { mService.registerListener(mToken, deviceListener); diff --git a/native/android/input.cpp b/native/android/input.cpp index c06c81ed03ec..a231d8f153e7 100644 --- a/native/android/input.cpp +++ b/native/android/input.cpp @@ -283,6 +283,21 @@ float AMotionEvent_getHistoricalAxisValue(const AInputEvent* motion_event, axis, pointer_index, history_index); } +int32_t AMotionEvent_getActionButton(const AInputEvent* motion_event) { + return static_cast<const MotionEvent*>(motion_event)->getActionButton(); +} + +int32_t AMotionEvent_getClassification(const AInputEvent* motion_event) { + switch (static_cast<const MotionEvent*>(motion_event)->getClassification()) { + case android::MotionClassification::NONE: + return AMOTION_EVENT_CLASSIFICATION_NONE; + case android::MotionClassification::AMBIGUOUS_GESTURE: + return AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE; + case android::MotionClassification::DEEP_PRESS: + return AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS; + } +} + const AInputEvent* AMotionEvent_fromJava(JNIEnv* env, jobject motionEvent) { MotionEvent* eventSrc = android::android_view_MotionEvent_getNativePtr(env, motionEvent); if (eventSrc == nullptr) { diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 3009a36bae2c..67a98a92dd98 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -114,8 +114,10 @@ LIBANDROID { ALooper_removeFd; ALooper_wake; AMotionEvent_getAction; + AMotionEvent_getActionButton; # introduced=Tiramisu AMotionEvent_getAxisValue; # introduced-arm=13 introduced-arm64=21 introduced-mips=13 introduced-mips64=21 introduced-x86=13 introduced-x86_64=21 AMotionEvent_getButtonState; # introduced-arm=14 introduced-arm64=21 introduced-mips=14 introduced-mips64=21 introduced-x86=14 introduced-x86_64=21 + AMotionEvent_getClassification; # introduced=Tiramisu AMotionEvent_getDownTime; AMotionEvent_getEdgeFlags; AMotionEvent_getEventTime; diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp index 0e6087376d11..6ded16371e8a 100644 --- a/packages/CompanionDeviceManager/Android.bp +++ b/packages/CompanionDeviceManager/Android.bp @@ -39,6 +39,7 @@ android_app { static_libs: [ "androidx.lifecycle_lifecycle-livedata", "androidx.lifecycle_lifecycle-extensions", + "androidx.recyclerview_recyclerview", "androidx.appcompat_appcompat", ], diff --git a/packages/SystemUI/res/layout/idle_host_view.xml b/packages/CompanionDeviceManager/res/color/selector.xml index f4078742c916..fda827d7f9c8 100644 --- a/packages/SystemUI/res/layout/idle_host_view.xml +++ b/packages/CompanionDeviceManager/res/color/selector.xml @@ -1,6 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2021 The Android Open Source Project + ~ Copyright (C) 2022 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 +14,7 @@ ~ limitations under the License. --> - -<com.android.systemui.idle.IdleHostView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/idle_host_view" - android:layout_width="match_parent" - android:layout_height="match_parent"/> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:color="@android:color/darker_gray"/> <!-- pressed --> + <item android:color="@android:color/white"/> +</selector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml index 313e164cdbef..70cbfdf5cefc 100644 --- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml @@ -49,8 +49,8 @@ android:layout_height="0dp" android:layout_weight="1"> - <ListView - android:id="@+id/device_list" + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/device_list" style="@android:style/Widget.Material.ListView" android:layout_width="match_parent" android:layout_height="200dp" /> diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml index d79aea6fae6b..153fc1f35abe 100644 --- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml +++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml @@ -19,7 +19,8 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" - android:padding="12dp"> + android:padding="12dp" + android:background="@color/selector"> <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. --> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 16e851be4362..b51d3103caec 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -46,10 +46,11 @@ import android.text.Spanned; import android.util.Log; import android.view.View; import android.widget.Button; -import android.widget.ListView; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import java.util.List; @@ -94,9 +95,9 @@ public class CompanionDeviceActivity extends AppCompatActivity { // regular. private Button mButtonAllow; - // The list is only shown for multiple-device regular association request, after at least one - // matching device is found. - private @Nullable ListView mListView; + // The recycler view is only shown for multiple-device regular association request, after + // at least one matching device is found. + private @Nullable RecyclerView mRecyclerView; private @Nullable DeviceListAdapter mAdapter; // The flag used to prevent double taps, that may lead to sending several requests for creating @@ -195,14 +196,15 @@ public class CompanionDeviceActivity extends AppCompatActivity { mTitle = findViewById(R.id.title); mSummary = findViewById(R.id.summary); - mListView = findViewById(R.id.device_list); - mListView.setOnItemClickListener((av, iv, position, id) -> onListItemClick(position)); + mRecyclerView = findViewById(R.id.device_list); + mAdapter = new DeviceListAdapter(this, this::onListItemClick); mButtonAllow = findViewById(R.id.btn_positive); mButtonAllow.setOnClickListener(this::onPositiveButtonClick); findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick); final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName()); + if (mRequest.isSelfManaged()) { initUiForSelfManagedAssociation(appLabel); } else if (mRequest.isSingleDevice()) { @@ -333,7 +335,7 @@ public class CompanionDeviceActivity extends AppCompatActivity { mTitle.setText(title); mSummary.setText(summary); - mListView.setVisibility(View.GONE); + mRecyclerView.setVisibility(View.GONE); } private void initUiForSingleDevice(CharSequence appLabel) { @@ -345,12 +347,12 @@ public class CompanionDeviceActivity extends AppCompatActivity { deviceFilterPairs -> updateSingleDeviceUi( deviceFilterPairs, deviceProfile, appLabel)); - mListView.setVisibility(View.GONE); + mRecyclerView.setVisibility(View.GONE); } private void updateSingleDeviceUi(List<DeviceFilterPair<?>> deviceFilterPairs, String deviceProfile, CharSequence appLabel) { - // Ignore "empty" scan repots. + // Ignore "empty" scan reports. if (deviceFilterPairs.isEmpty()) return; mSelectedDevice = requireNonNull(deviceFilterPairs.get(0)); @@ -393,10 +395,12 @@ public class CompanionDeviceActivity extends AppCompatActivity { mTitle.setText(title); mSummary.setText(summary); - mAdapter = new DeviceListAdapter(this); + mAdapter = new DeviceListAdapter(this, this::onListItemClick); // TODO: hide the list and show a spinner until a first device matching device is found. - mListView.setAdapter(mAdapter); + mRecyclerView.setAdapter(mAdapter); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + CompanionDeviceDiscoveryService.getScanResult().observe( /* lifecycleOwner */ this, /* observer */ mAdapter); @@ -414,6 +418,8 @@ public class CompanionDeviceActivity extends AppCompatActivity { if (DEBUG) Log.w(TAG, "Already selected."); return; } + // Notify the adapter to highlight the selected item. + mAdapter.setSelectedPosition(position); mSelectedDevice = requireNonNull(selectedDevice); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java index 198b7780e2b8..e5513b074865 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java @@ -15,42 +15,70 @@ */ package com.android.companiondevicemanager; - -import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.RecyclerView; import java.util.List; - /** * Adapter for the list of "found" devices. */ -class DeviceListAdapter extends BaseAdapter implements Observer<List<DeviceFilterPair<?>>> { +class DeviceListAdapter extends RecyclerView.Adapter<DeviceListAdapter.ViewHolder> implements + Observer<List<DeviceFilterPair<?>>> { + public int mSelectedPosition = RecyclerView.NO_POSITION; + private final Context mContext; // List if pairs (display name, address) private List<DeviceFilterPair<?>> mDevices; - DeviceListAdapter(Context context) { + private OnItemClickListener mListener; + + private static final int TYPE_WIFI = 0; + private static final int TYPE_BT = 1; + + DeviceListAdapter(Context context, OnItemClickListener listener) { mContext = context; + mListener = listener; + } + + public DeviceFilterPair<?> getItem(int position) { + return mDevices.get(position); } @Override - public int getCount() { - return mDevices != null ? mDevices.size() : 0; + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate( + R.layout.list_item_device, parent, false); + ViewHolder viewHolder = new ViewHolder(view); + if (viewType == TYPE_WIFI) { + viewHolder.mImageView.setImageDrawable(getIcon( + com.android.internal.R.drawable.ic_wifi_signal_3)); + } else { + viewHolder.mImageView.setImageDrawable(getIcon( + android.R.drawable.stat_sys_data_bluetooth)); + } + return viewHolder; } @Override - public DeviceFilterPair<?> getItem(int position) { - return mDevices.get(position); + public void onBindViewHolder(ViewHolder holder, int position) { + holder.itemView.setSelected(mSelectedPosition == position); + holder.mTextView.setText(mDevices.get(position).getDisplayName()); + holder.itemView.setOnClickListener(v -> mListener.onItemClick(position)); + } + + @Override + public int getItemViewType(int position) { + return isWifiDevice(position) ? TYPE_WIFI : TYPE_BT; } @Override @@ -59,29 +87,12 @@ class DeviceListAdapter extends BaseAdapter implements Observer<List<DeviceFilte } @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - final View view = convertView != null - ? convertView - : LayoutInflater.from(mContext).inflate(R.layout.list_item_device, parent, false); - - final DeviceFilterPair<?> item = getItem(position); - bindView(view, item); - - return view; + public int getItemCount() { + return mDevices != null ? mDevices.size() : 0; } - private void bindView(@NonNull View view, DeviceFilterPair<?> item) { - final TextView textView = view.findViewById(android.R.id.text1); - textView.setText(item.getDisplayName()); - - final ImageView iconView = view.findViewById(android.R.id.icon); - - // TODO(b/211417476): Set either Bluetooth or WiFi icon. - iconView.setVisibility(View.GONE); - // final int iconRes = isBt ? android.R.drawable.stat_sys_data_bluetooth - // : com.android.internal.R.drawable.ic_wifi_signal_3; - // final Drawable icon = getTintedIcon(mResources, iconRes); - // iconView.setImageDrawable(icon); + public void setSelectedPosition(int position) { + mSelectedPosition = position; } @Override @@ -89,4 +100,28 @@ class DeviceListAdapter extends BaseAdapter implements Observer<List<DeviceFilte mDevices = deviceFilterPairs; notifyDataSetChanged(); } + + static class ViewHolder extends RecyclerView.ViewHolder { + private TextView mTextView; + private ImageView mImageView; + ViewHolder(View itemView) { + super(itemView); + mTextView = itemView.findViewById(android.R.id.text1); + mImageView = itemView.findViewById(android.R.id.icon); + } + } + + private boolean isWifiDevice(int position) { + return mDevices.get(position).getDevice() instanceof android.net.wifi.ScanResult; + } + + private Drawable getIcon(int resId) { + Drawable icon = mContext.getResources().getDrawable(resId, null); + icon.setTint(Color.DKGRAY); + return icon; + } + + public interface OnItemClickListener { + void onItemClick(int position); + } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java index 1a955c4c57d7..72243f9e87d9 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java @@ -146,7 +146,7 @@ public class EthernetManager { * @param iface the name of the interface. * @param state the current state of the interface, or {@link #STATE_ABSENT} if the * interface was removed. - * @param role whether the interface is in the client mode or server mode. + * @param role whether the interface is in client mode or server mode. * @param configuration the current IP configuration of the interface. * @hide */ diff --git a/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp b/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp index f8a81682bdcf..39cbaf716fc0 100644 --- a/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp +++ b/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp @@ -102,15 +102,10 @@ static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) { } } -static int deleteTagData(JNIEnv* /* env */, jclass /* clazz */, jint uid) { - return qtaguid_deleteTagData(0, uid); -} - static const JNINativeMethod gMethods[] = { {"nativeGetTotalStat", "(I)J", (void*)getTotalStat}, {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat}, {"nativeGetUidStat", "(II)J", (void*)getUidStat}, - {"nativeDeleteTagData", "(I)I", (void*)deleteTagData}, }; int register_android_server_net_NetworkStatsService(JNIEnv* env) { diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index e8b3d4cd1310..ef9ebb50b642 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -51,6 +51,7 @@ import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID; import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG; import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT; import static android.os.Trace.TRACE_TAG_NETWORK; +import static android.system.OsConstants.ENOENT; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; @@ -122,7 +123,6 @@ import android.provider.Settings.Global; import android.service.NetworkInterfaceProto; import android.service.NetworkStatsServiceDumpProto; import android.system.ErrnoException; -import android.system.Os; import android.telephony.PhoneStateListener; import android.telephony.SubscriptionPlan; import android.text.TextUtils; @@ -221,6 +221,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // This is current path but may be changed soon. private static final String UID_COUNTERSET_MAP_PATH = "/sys/fs/bpf/map_netd_uid_counterset_map"; + private static final String COOKIE_TAG_MAP_PATH = + "/sys/fs/bpf/map_netd_cookie_tag_map"; + private static final String APP_UID_STATS_MAP_PATH = + "/sys/fs/bpf/map_netd_app_uid_stats_map"; + private static final String STATS_MAP_A_PATH = + "/sys/fs/bpf/map_netd_stats_map_A"; + private static final String STATS_MAP_B_PATH = + "/sys/fs/bpf/map_netd_stats_map_B"; private final Context mContext; private final NetworkStatsFactory mStatsFactory; @@ -348,6 +356,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ private SparseIntArray mActiveUidCounterSet = new SparseIntArray(); private final IBpfMap<U32, U8> mUidCounterSetMap; + private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap; + private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapA; + private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapB; + private final IBpfMap<UidStatsMapKey, StatsMapValue> mAppUidStatsMap; /** Data layer operation counters for splicing into other structures. */ private NetworkStats mUidOperations = new NetworkStats(0L, 10); @@ -481,6 +493,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler); mInterfaceMapUpdater.start(); mUidCounterSetMap = mDeps.getUidCounterSetMap(); + mCookieTagMap = mDeps.getCookieTagMap(); + mStatsMapA = mDeps.getStatsMapA(); + mStatsMapB = mDeps.getStatsMapB(); + mAppUidStatsMap = mDeps.getAppUidStatsMap(); } /** @@ -554,8 +570,48 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - public TagStatsDeleter getTagStatsDeleter() { - return NetworkStatsService::nativeDeleteTagData; + /** Gets the cookie tag map */ + public IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() { + try { + return new BpfMap<CookieTagMapKey, CookieTagMapValue>(COOKIE_TAG_MAP_PATH, + BpfMap.BPF_F_RDWR, CookieTagMapKey.class, CookieTagMapValue.class); + } catch (ErrnoException e) { + Log.wtf(TAG, "Cannot create cookie tag map: " + e); + return null; + } + } + + /** Gets stats map A */ + public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapA() { + try { + return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_A_PATH, + BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class); + } catch (ErrnoException e) { + Log.wtf(TAG, "Cannot create stats map A: " + e); + return null; + } + } + + /** Gets stats map B */ + public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapB() { + try { + return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_B_PATH, + BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class); + } catch (ErrnoException e) { + Log.wtf(TAG, "Cannot create stats map B: " + e); + return null; + } + } + + /** Gets the uid stats map */ + public IBpfMap<UidStatsMapKey, StatsMapValue> getAppUidStatsMap() { + try { + return new BpfMap<UidStatsMapKey, StatsMapValue>(APP_UID_STATS_MAP_PATH, + BpfMap.BPF_F_RDWR, UidStatsMapKey.class, StatsMapValue.class); + } catch (ErrnoException e) { + Log.wtf(TAG, "Cannot create app uid stats map: " + e); + return null; + } } } @@ -1802,6 +1858,63 @@ public class NetworkStatsService extends INetworkStatsService.Stub { currentTime); } + // deleteKernelTagData can ignore ENOENT; otherwise we should log an error + private void logErrorIfNotErrNoent(final ErrnoException e, final String msg) { + if (e.errno != ENOENT) Log.e(TAG, msg, e); + } + + private <K extends StatsMapKey, V extends StatsMapValue> void deleteStatsMapTagData( + IBpfMap<K, V> statsMap, int uid) { + try { + statsMap.forEach((key, value) -> { + if (key.uid == uid) { + try { + statsMap.deleteEntry(key); + } catch (ErrnoException e) { + logErrorIfNotErrNoent(e, "Failed to delete data(uid = " + key.uid + ")"); + } + } + }); + } catch (ErrnoException e) { + Log.e(TAG, "FAILED to delete tag data from stats map", e); + } + } + + /** + * Deletes uid tag data from CookieTagMap, StatsMapA, StatsMapB, and UidStatsMap + * @param uid + */ + private void deleteKernelTagData(int uid) { + try { + mCookieTagMap.forEach((key, value) -> { + if (value.uid == uid) { + try { + mCookieTagMap.deleteEntry(key); + } catch (ErrnoException e) { + logErrorIfNotErrNoent(e, "Failed to delete data(cookie = " + key + ")"); + } + } + }); + } catch (ErrnoException e) { + Log.e(TAG, "Failed to delete tag data from cookie tag map", e); + } + + deleteStatsMapTagData(mStatsMapA, uid); + deleteStatsMapTagData(mStatsMapB, uid); + + try { + mUidCounterSetMap.deleteEntry(new U32(uid)); + } catch (ErrnoException e) { + logErrorIfNotErrNoent(e, "Failed to delete tag data from uid counter set map"); + } + + try { + mAppUidStatsMap.deleteEntry(new UidStatsMapKey(uid)); + } catch (ErrnoException e) { + logErrorIfNotErrNoent(e, "Failed to delete tag data from app uid stats map"); + } + } + /** * Clean up {@link #mUidRecorder} after UID is removed. */ @@ -1817,10 +1930,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // Clear kernel stats associated with UID for (int uid : uids) { - final int ret = mDeps.getTagStatsDeleter().deleteTagData(uid); - if (ret < 0) { - Log.w(TAG, "problem clearing counters for uid " + uid + ": " + Os.strerror(-ret)); - } + deleteKernelTagData(uid); } } @@ -2402,12 +2512,4 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static native long nativeGetTotalStat(int type); private static native long nativeGetIfaceStat(String iface, int type); private static native long nativeGetUidStat(int uid, int type); - - // TODO: use BpfNetMaps to delete tag data and remove this. - @VisibleForTesting - interface TagStatsDeleter { - int deleteTagData(int uid); - } - - private static native int nativeDeleteTagData(int uid); } diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp index fc82b79399ef..2569198f9622 100644 --- a/packages/SettingsLib/ActivityEmbedding/Android.bp +++ b/packages/SettingsLib/ActivityEmbedding/Android.bp @@ -14,6 +14,8 @@ android_library { static_libs: [ "androidx.annotation_annotation", + "androidx.core_core", + "windowExtLib", "SettingsLibUtils", ], sdk_version: "system_current", diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java index 7f17d26b156a..44b3b4e8488a 100644 --- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java +++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java @@ -16,8 +16,14 @@ package com.android.settingslib.activityembedding; +import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.provider.Settings; +import android.text.TextUtils; + +import androidx.core.os.BuildCompat; +import androidx.window.embedding.SplitController; import com.android.settingslib.utils.BuildCompatUtils; @@ -44,6 +50,33 @@ public class ActivityEmbeddingUtils { return false; } + /** + * Whether current activity is embedded in the Settings app or not. + */ + public static boolean isActivityEmbedded(Activity activity) { + return SplitController.getInstance().isActivityEmbedded(activity); + } + + /** + * Whether current activity is suggested to show back button or not. + */ + public static boolean shouldHideBackButton(Activity activity, boolean isSecondaryLayerPage) { + if (!BuildCompat.isAtLeastT()) { + return false; + } + if (!isSecondaryLayerPage) { + return false; + } + final String shouldHideBackButton = Settings.Global.getString(activity.getContentResolver(), + "settings_hide_secondary_page_back_button_in_two_pane"); + + if (TextUtils.isEmpty(shouldHideBackButton) + || TextUtils.equals("true", shouldHideBackButton)) { + return isActivityEmbedded(activity); + } + return false; + } + private ActivityEmbeddingUtils() { } } diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 684f4dee9ac2..7a5ea472b532 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -48,7 +48,6 @@ android_library { "SettingsLibCollapsingToolbarBaseActivity", "SettingsLibTwoTargetPreference", "SettingsLibSettingsTransition", - "SettingsLibActivityEmbedding", "SettingsLibButtonPreference", "setupdesign", ], diff --git a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_dropdown_view.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v31/settings_spinner_dropdown_view.xml index a342c840cfbe..cea1133f7fee 100644 --- a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_dropdown_view.xml +++ b/packages/SettingsLib/SettingsSpinner/res/layout-v31/settings_spinner_dropdown_view.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2021 The Android Open Source Project + Copyright (C) 2022 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. @@ -18,8 +18,7 @@ <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" - style="@style/SettingsSpinnerTitleBar" + style="@style/SettingsSpinnerDropdown" android:gravity="center_vertical" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/settings_spinner_dropdown_background"/> + android:layout_height="wrap_content"/> diff --git a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml b/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml index 7d5b6db6f6d6..4c75344bbde3 100644 --- a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml +++ b/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml @@ -22,7 +22,7 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp"> - <com.android.settingslib.widget.settingsspinner.SettingsSpinner + <Spinner android:id="@+id/spinner" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SettingsLib/SettingsSpinner/res/values-night/colors.xml b/packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml index abcf822c6d78..8fda876355d6 100644 --- a/packages/SettingsLib/SettingsSpinner/res/values-night/colors.xml +++ b/packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 The Android Open Source Project +<!-- Copyright (C) 2022 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,5 +15,6 @@ --> <resources> - <color name="ripple_color">@*android:color/material_grey_900</color> + <color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color> + <color name="settingslib_spinner_dropdown_color">@android:color/system_neutral2_700</color> </resources> diff --git a/packages/SettingsLib/SettingsSpinner/res/values-v31/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values-v31/styles.xml new file mode 100644 index 000000000000..fc3ec4344712 --- /dev/null +++ b/packages/SettingsLib/SettingsSpinner/res/values-v31/styles.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <style name="SettingsSpinnerTitleBar"> + <item name="android:textAppearance">?android:attr/textAppearanceButton</item> + <item name="android:textColor">@color/settingslib_spinner_title_color</item> + <item name="android:maxLines">1</item> + <item name="android:ellipsize">marquee</item> + <item name="android:minHeight">@dimen/spinner_height</item> + <item name="android:paddingStart">16dp</item> + <item name="android:paddingEnd">36dp</item> + <item name="android:paddingTop">@dimen/spinner_padding_top_or_bottom</item> + <item name="android:paddingBottom">@dimen/spinner_padding_top_or_bottom</item> + </style> + + <style name="SettingsSpinnerDropdown"> + <item name="android:textAppearance">?android:attr/textAppearanceButton</item> + <item name="android:textColor">@color/settingslib_spinner_dropdown_color</item> + <item name="android:maxLines">1</item> + <item name="android:ellipsize">marquee</item> + <item name="android:minHeight">@dimen/spinner_height</item> + <item name="android:paddingStart">16dp</item> + <item name="android:paddingEnd">36dp</item> + <item name="android:paddingTop">@dimen/spinner_padding_top_or_bottom</item> + <item name="android:paddingBottom">@dimen/spinner_padding_top_or_bottom</item> + </style> +</resources> diff --git a/packages/SettingsLib/SettingsSpinner/res/values/colors.xml b/packages/SettingsLib/SettingsSpinner/res/values/colors.xml deleted file mode 100644 index 799b35ee3a92..000000000000 --- a/packages/SettingsLib/SettingsSpinner/res/values/colors.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?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. ---> - -<resources> - <color name="ripple_color">?android:attr/colorControlHighlight</color> -</resources> diff --git a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml index f665f3836002..8ea1f9a794bc 100644 --- a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml +++ b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml @@ -18,10 +18,8 @@ <resources> <style name="SettingsSpinnerTitleBar"> <item name="android:textAppearance">?android:attr/textAppearanceButton</item> - <item name="android:textColor">@android:color/black</item> <item name="android:maxLines">1</item> <item name="android:ellipsize">marquee</item> - <item name="android:minHeight">@dimen/spinner_height</item> <item name="android:paddingStart">16dp</item> <item name="android:paddingEnd">36dp</item> <item name="android:paddingTop">@dimen/spinner_padding_top_or_bottom</item> diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinnerAdapter.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java index 83da512ce879..26112074a130 100644 --- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinnerAdapter.java +++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.android.settingslib.widget.settingsspinner; +package com.android.settingslib.widget; import android.content.Context; +import android.os.Build; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import com.android.settingslib.widget.R; - /** - * An ArrayAdapter which was used by {@link SettingsSpinner} with settings style. + * An ArrayAdapter which was used by Spinner with settings style. + * @param <T> the data type to be loaded. */ public class SettingsSpinnerAdapter<T> extends ArrayAdapter<T> { @@ -43,7 +43,7 @@ public class SettingsSpinnerAdapter<T> extends ArrayAdapter<T> { public SettingsSpinnerAdapter(Context context) { super(context, DEFAULT_RESOURCE); - setDropDownViewResource(DFAULT_DROPDOWN_RESOURCE); + setDropDownViewResource(getDropdownResource()); mDefaultInflater = LayoutInflater.from(context); } @@ -59,6 +59,11 @@ public class SettingsSpinnerAdapter<T> extends ArrayAdapter<T> { * drop down view. */ public View getDefaultDropDownView(int position, View convertView, ViewGroup parent) { - return mDefaultInflater.inflate(DFAULT_DROPDOWN_RESOURCE, parent, false /* attachToRoot */); + return mDefaultInflater.inflate(getDropdownResource(), parent, false /* attachToRoot */); + } + + private int getDropdownResource() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + ? DFAULT_DROPDOWN_RESOURCE : android.R.layout.simple_spinner_dropdown_item; } } diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java index d993e4465343..69528757c0e3 100644 --- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java +++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java @@ -20,16 +20,14 @@ import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.AdapterView; +import android.widget.Spinner; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceViewHolder; -import com.android.settingslib.widget.settingsspinner.SettingsSpinner; -import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter; - /** - * This preference uses SettingsSpinner & SettingsSpinnerAdapter which provide default layouts for + * This preference uses Spinner & SettingsSpinnerAdapter which provide default layouts for * both view and drop down view of the Spinner. */ public class SettingsSpinnerPreference extends Preference implements OnPreferenceClickListener { @@ -113,7 +111,7 @@ public class SettingsSpinnerPreference extends Preference implements OnPreferenc @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - final SettingsSpinner spinner = (SettingsSpinner) holder.findViewById(R.id.spinner); + final Spinner spinner = (Spinner) holder.findViewById(R.id.spinner); spinner.setAdapter(mAdapter); spinner.setSelection(mPosition); spinner.setOnItemSelectedListener(mOnSelectedListener); diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinner.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinner.java deleted file mode 100644 index 14286fa76d8a..000000000000 --- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinner.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.widget.settingsspinner; - -import android.content.Context; -import android.os.Build; -import android.util.AttributeSet; -import android.widget.Spinner; - -import androidx.annotation.RequiresApi; - -import com.android.settingslib.widget.R; - -/** - * A {@link Spinner} with settings style. - * - * The items in the SettingsSpinner come from the {@link SettingsSpinnerAdapter} associated with - * this view. - */ -public class SettingsSpinner extends Spinner { - - /** - * Constructs a new SettingsSpinner with the given context's theme. - * And it also set a background resource with settings style. - * - * @param context The Context the view is running in, through which it can - * access the current theme, resources, etc. - */ - public SettingsSpinner(Context context) { - super(context); - setBackgroundResource(R.drawable.settings_spinner_background); - } - - /** - * Constructs a new SettingsSpinner with the given context's theme and the supplied - * mode of displaying choices. <code>mode</code> may be one of - * {@link Spinner#MODE_DIALOG} or {@link Spinner#MODE_DROPDOWN}. - * And it also set a background resource with settings style. - * - * @param context The Context the view is running in, through which it can - * access the current theme, resources, etc. - * @param mode Constant describing how the user will select choices from - * the spinner. - * - * @see Spinner#MODE_DIALOG - * @see Spinner#MODE_DROPDOWN - */ - public SettingsSpinner(Context context, int mode) { - super(context, mode); - setBackgroundResource(R.drawable.settings_spinner_background); - } - - /** - * Constructs a new SettingsSpinner with the given context's theme and the supplied - * attribute set. - * And it also set a background resource with settings style. - * - * @param context The Context the view is running in, through which it can - * access the current theme, resources, etc. - * @param attrs The attributes of the XML tag that is inflating the view. - */ - public SettingsSpinner(Context context, AttributeSet attrs) { - super(context, attrs); - setBackgroundResource(R.drawable.settings_spinner_background); - } - - /** - * Constructs a new SettingsSpinner with the given context's theme, the supplied - * attribute set, and default style attribute. - * And it also set a background resource with settings style. - * - * @param context The Context the view is running in, through which it can - * access the current theme, resources, etc. - * @param attrs The attributes of the XML tag that is inflating the view. - * @param defStyleAttr An attribute in the current theme that contains a - * reference to a style resource that supplies default - * values for the view. Can be 0 to not look for - * defaults. - */ - public SettingsSpinner(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setBackgroundResource(R.drawable.settings_spinner_background); - } - - /** - * Constructs a new SettingsSpinner with the given context's theme, the supplied - * attribute set, and default styles. <code>mode</code> may be one of - * {@link Spinner#MODE_DIALOG} or {@link Spinner#MODE_DROPDOWN} and determines how the - * user will select choices from the spinner. - * And it also set a background resource with settings style. - * - * @param context The Context the view is running in, through which it can - * access the current theme, resources, etc. - * @param attrs The attributes of the XML tag that is inflating the view. - * @param defStyleAttr An attribute in the current theme that contains a - * reference to a style resource that supplies default - * values for the view. Can be 0 to not look for - * defaults. - * @param defStyleRes A resource identifier of a style resource that - * supplies default values for the view, used only if - * defStyleAttr is 0 or can not be found in the theme. - * Can be 0 to not look for defaults. - * @param mode Constant describing how the user will select choices from - * the spinner. - * - * @see Spinner#MODE_DIALOG - * @see Spinner#MODE_DROPDOWN - */ - @RequiresApi(Build.VERSION_CODES.M) - public SettingsSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, - int mode) { - super(context, attrs, defStyleAttr, defStyleRes, mode, null); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - setDropDownVerticalOffset(getMeasuredHeight() - (int) getContext().getResources() - .getDimension(R.dimen.spinner_padding_top_or_bottom)); - } -} diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_arrow_drop_down.xml index 054452629c88..770a69d3e8e6 100644 --- a/packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_arrow_drop_down.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2018 The Android Open Source Project + Copyright (C) 2022 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. diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml index a4c780bc20de..ae63928e66b3 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml @@ -27,11 +27,12 @@ <item android:id="@android:id/progress"> - <clip> + <scale android:scaleWidth="100%" android:useIntrinsicSizeAsMinimum="true"> <shape> <corners android:radius="8dp" /> <solid android:color="?android:attr/textColorPrimary" /> + <size android:width="8dp"/> </shape> - </clip> + </scale> </item> </layer-list> diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_background.xml new file mode 100644 index 000000000000..e1764afe5d91 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_background.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. + --> + +<ripple +xmlns:android="http://schemas.android.com/apk/res/android" +android:color="@color/settingslib_ripple_color"> + +<item android:id="@android:id/background"> + <layer-list android:paddingMode="stack"> + <item + android:top="8dp" + android:bottom="8dp"> + + <shape> + <corners android:radius="28dp"/> + <solid android:color="@android:color/system_accent1_100"/> + <size android:height="@dimen/settingslib_spinner_height"/> + </shape> + </item> + + <item + android:gravity="center|end" + android:width="18dp" + android:height="18dp" + android:end="12dp" + android:drawable="@drawable/settingslib_arrow_drop_down"/> + </layer-list> +</item> +</ripple> diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_dropdown_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_dropdown_background.xml index aa451aee41c2..056fb828baad 100644 --- a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_dropdown_background.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_dropdown_background.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2018 The Android Open Source Project + Copyright (C) 2022 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. @@ -17,12 +17,12 @@ <ripple xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:priv-android="http://schemas.android.com/apk/prv/res/android" - android:color="@color/ripple_color"> + android:color="@color/settingslib_ripple_color"> <item android:id="@android:id/background"> <shape> - <solid android:color="?priv-android:attr/colorAccentSecondary"/> + <corners android:radius="10dp"/> + <solid android:color="@android:color/system_accent2_100"/> </shape> </item> </ripple> diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_arrow_drop_down.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_arrow_drop_down.xml new file mode 100644 index 000000000000..6ed215dd2dbb --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_arrow_drop_down.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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:viewportWidth="24" + android:viewportHeight="24" + android:width="24dp" + android:height="24dp"> + <path + android:pathData="M7 10l5 5 5 -5z" + android:fillColor="?android:attr/textColorPrimary"/> +</vector> diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_spinner_background.xml index 139cd95684be..746671254afd 100644 --- a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_background.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_spinner_background.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2018 The Android Open Source Project + Copyright (C) 2022 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,9 +16,8 @@ --> <ripple - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:priv-android="http://schemas.android.com/apk/prv/res/android" - android:color="@color/ripple_color"> +xmlns:android="http://schemas.android.com/apk/res/android" +android:color="@color/settingslib_ripple_color"> <item android:id="@android:id/background"> <layer-list android:paddingMode="stack"> @@ -27,18 +26,24 @@ android:bottom="8dp"> <shape> - <corners android:radius="28dp"/> - <solid android:color="?priv-android:attr/colorAccentPrimary"/> - <size android:height="@dimen/spinner_height"/> + <corners + android:radius="20dp"/> + <solid + android:color="?android:attr/colorPrimary"/> + <stroke + android:color="#1f000000" + android:width="1dp"/> + <size + android:height="32dp"/> </shape> </item> <item android:gravity="center|end" - android:width="18dp" - android:height="18dp" - android:end="8dp" - android:drawable="@drawable/arrow_drop_down"/> + android:width="24dp" + android:height="24dp" + android:end="4dp" + android:drawable="@drawable/settingslib_arrow_drop_down"/> </layer-list> </item> </ripple> diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml index 3f2b8ac7609d..77c4533b9f46 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml @@ -47,4 +47,6 @@ <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_200</color> <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color> + + <color name="settingslib_ripple_color">@color/settingslib_material_grey_900</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml index ec3c336eba46..6adb7899fcc5 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml @@ -69,4 +69,7 @@ <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color> <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color> + + <color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color> + <color name="settingslib_material_grey_900">#ff212121</color> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml index 11546c8ed3d9..29fdab13c717 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml @@ -25,4 +25,6 @@ <dimen name="settingslib_listPreferredItemPaddingStart">24dp</dimen> <!-- Right padding of the preference --> <dimen name="settingslib_listPreferredItemPaddingEnd">24dp</dimen> + + <dimen name="settingslib_spinner_height">36dp</dimen> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml index 8e7226b0eb53..9d3991119338 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml @@ -45,4 +45,13 @@ <item name="android:progressDrawable">@drawable/settingslib_progress_horizontal</item> <item name="android:scaleY">0.5</item> </style> + + <style name="Spinner.SettingsLib" + parent="android:style/Widget.Material.Spinner"> + <item name="android:background">@drawable/settingslib_spinner_background</item> + <item name="android:popupBackground">@drawable/settingslib_spinner_dropdown_background</item> + <item name="android:dropDownVerticalOffset">48dp</item> + <item name="android:layout_marginTop">16dp</item> + <item name="android:layout_marginBottom">8dp</item> + </style> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml index 7bf75bcc8a53..e9bbcc785a2f 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml @@ -26,6 +26,7 @@ <item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item> <item name="android:switchStyle">@style/Switch.SettingsLib</item> <item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item> + <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item> </style> <!-- Using in SubSettings page including injected settings page --> diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml index aaab0f041fe3..fa27bb692fe8 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml @@ -26,4 +26,10 @@ <style name="TextAppearance.CategoryTitle.SettingsLib" parent="@android:style/TextAppearance.DeviceDefault.Medium"> </style> + + <style name="Spinner.SettingsLib" + parent="android:style/Widget.Material.Spinner"> + <item name="android:background">@drawable/settingslib_spinner_background</item> + <item name="android:dropDownVerticalOffset">48dp</item> + </style> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml index 2d881d1a8a7b..8dc0f3c1ff2b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml @@ -19,6 +19,7 @@ <!-- Only using in Settings application --> <style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings"> <item name="preferenceTheme">@style/PreferenceThemeOverlay</item> + <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item> </style> <!-- Using in SubSettings page including injected settings page --> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 25d6c5556f5e..d893d09a9e3c 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -22,8 +22,6 @@ <!-- The translation for disappearing security views after having solved them. --> <dimen name="disappear_y_translation">-32dp</dimen> - <dimen name="circle_avatar_size">190dp</dimen> - <!-- Height of a user icon view --> <dimen name="user_icon_view_height">24dp</dimen> <!-- User spinner --> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 883e0806f5f5..f6e3557b5a49 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -144,7 +144,7 @@ public class Utils { * Returns a circular icon for a user. */ public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) { - final int iconSize = UserIconDrawable.getSizeForList(context); + final int iconSize = UserIconDrawable.getDefaultSize(context); if (user.isManagedProfile()) { Drawable drawable = UserIconDrawable.getManagedUserDrawable(context); drawable.setBounds(0, 0, iconSize, iconSize); diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java index fc38ada9b832..afd3626ab889 100644 --- a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java @@ -22,7 +22,6 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCK import android.content.ContentResolver; import android.content.Context; -import android.content.res.Resources; import android.database.ContentObserver; import android.os.Handler; import android.os.UserHandle; @@ -34,10 +33,7 @@ import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; -import java.util.Objects; import java.util.Set; /** @@ -52,12 +48,11 @@ public final class DeviceStateRotationLockSettingsManager { private static DeviceStateRotationLockSettingsManager sSingleton; private final ContentResolver mContentResolver; + private final String[] mDeviceStateRotationLockDefaults; private final Handler mMainHandler = Handler.getMain(); private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>(); - private String[] mDeviceStateRotationLockDefaults; private SparseIntArray mDeviceStateRotationLockSettings; private SparseIntArray mDeviceStateRotationLockFallbackSettings; - private List<SettableDeviceState> mSettableDeviceStates; private DeviceStateRotationLockSettingsManager(Context context) { mContentResolver = context.getContentResolver(); @@ -78,12 +73,6 @@ public final class DeviceStateRotationLockSettingsManager { return sSingleton; } - /** Resets the singleton instance of this class. Only used for testing. */ - @VisibleForTesting - public static synchronized void resetInstance() { - sSingleton = null; - } - /** Returns true if device-state based rotation lock settings are enabled. */ public static boolean isDeviceStateRotationLockEnabled(Context context) { return context.getResources() @@ -191,12 +180,6 @@ public final class DeviceStateRotationLockSettingsManager { return true; } - /** Returns a list of device states and their respective auto-rotation setting availability. */ - public List<SettableDeviceState> getSettableDeviceStates() { - // Returning a copy to make sure that nothing outside can mutate our internal list. - return new ArrayList<>(mSettableDeviceStates); - } - private void initializeInMemoryMap() { String serializedSetting = Settings.Secure.getStringForUser( @@ -232,17 +215,6 @@ public final class DeviceStateRotationLockSettingsManager { } } - /** - * Resets the state of the class and saved settings back to the default values provided by the - * resources config. - */ - @VisibleForTesting - public void resetStateForTesting(Resources resources) { - mDeviceStateRotationLockDefaults = - resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults); - fallbackOnDefaults(); - } - private void fallbackOnDefaults() { loadDefaults(); persistSettings(); @@ -279,7 +251,6 @@ public final class DeviceStateRotationLockSettingsManager { } private void loadDefaults() { - mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length); mDeviceStateRotationLockSettings = new SparseIntArray( mDeviceStateRotationLockDefaults.length); mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1); @@ -300,8 +271,6 @@ public final class DeviceStateRotationLockSettingsManager { + values.length); } } - boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED; - mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable)); mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting); } catch (NumberFormatException e) { Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e); @@ -331,38 +300,4 @@ public final class DeviceStateRotationLockSettingsManager { /** Called whenever the settings have changed. */ void onSettingsChanged(); } - - /** Represents a device state and whether it has an auto-rotation setting. */ - public static class SettableDeviceState { - private final int mDeviceState; - private final boolean mIsSettable; - - SettableDeviceState(int deviceState, boolean isSettable) { - mDeviceState = deviceState; - mIsSettable = isSettable; - } - - /** Returns the device state associated with this object. */ - public int getDeviceState() { - return mDeviceState; - } - - /** Returns whether there is an auto-rotation setting for this device state. */ - public boolean isSettable() { - return mIsSettable; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SettableDeviceState)) return false; - SettableDeviceState that = (SettableDeviceState) o; - return mDeviceState == that.mDeviceState && mIsSettable == that.mIsSettable; - } - - @Override - public int hashCode() { - return Objects.hash(mDeviceState, mIsSettable); - } - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java index e5ea4467517b..7db00f34b2a2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java @@ -31,8 +31,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import com.android.settingslib.R; - /** * Converts the user avatar icon to a circularly clipped one. * TODO: Move this to an internal framework class and share with the one in Keyguard. @@ -49,9 +47,9 @@ public class CircleFramedDrawable extends Drawable { public static CircleFramedDrawable getInstance(Context context, Bitmap icon) { Resources res = context.getResources(); - float iconSize = res.getDimension(R.dimen.circle_avatar_size); + int iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.user_icon_size); - CircleFramedDrawable instance = new CircleFramedDrawable(icon, (int) iconSize); + CircleFramedDrawable instance = new CircleFramedDrawable(icon, iconSize); return instance; } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java index 035fafd5bb58..d01f2b42f338 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java @@ -49,8 +49,6 @@ import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import androidx.core.os.BuildCompat; -import com.android.settingslib.R; - /** * Converts the user avatar icon to a circularly clipped one with an optional badge and frame */ @@ -120,8 +118,9 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { * @param context * @return size in pixels */ - public static int getSizeForList(Context context) { - return (int) context.getResources().getDimension(R.dimen.circle_avatar_size); + public static int getDefaultSize(Context context) { + return context.getResources() + .getDimensionPixelSize(com.android.internal.R.dimen.user_icon_size); } public UserIconDrawable() { diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java index f8bb38b5978e..5862f6095018 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java @@ -16,19 +16,17 @@ package com.android.settingslib.users; -import android.annotation.NonNull; import android.app.Activity; import android.content.Intent; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.Log; import android.widget.ImageView; import com.android.internal.util.UserIcons; -import com.android.settingslib.R; import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.settingslib.utils.ThreadUtils; @@ -114,10 +112,10 @@ public class EditUserPhotoController { private void onDefaultIconSelected(int tintColor) { try { ThreadUtils.postOnBackgroundThread(() -> { + Resources res = mActivity.getResources(); Drawable drawable = - UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor); - Bitmap bitmap = convertToBitmap(drawable, - (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size)); + UserIcons.getDefaultUserIconInColor(res, tintColor); + Bitmap bitmap = UserIcons.convertToBitmapAtUserIconSize(res, drawable); ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap)); }).get(); @@ -126,14 +124,6 @@ public class EditUserPhotoController { } } - private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) { - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - icon.setBounds(0, 0, size, size); - icon.draw(canvas); - return bitmap; - } - private void onPhotoCropped(final Uri data) { ThreadUtils.postOnBackgroundThread(() -> { InputStream imageStream = null; diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java deleted file mode 100644 index 8d687b60b6eb..000000000000 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.devicestate; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.res.Resources; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.R; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.List; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class DeviceStateRotationLockSettingsManagerTest { - - @Mock private Context mMockContext; - @Mock private Resources mMockResources; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - Context context = InstrumentationRegistry.getTargetContext(); - when(mMockContext.getApplicationContext()).thenReturn(mMockContext); - when(mMockContext.getResources()).thenReturn(mMockResources); - when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver()); - } - - @Test - public void getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() { - when(mMockResources.getStringArray( - R.array.config_perDeviceStateRotationLockDefaults)).thenReturn( - new String[]{"2:2", "4:0", "1:1", "0:0"}); - - List<SettableDeviceState> settableDeviceStates = - DeviceStateRotationLockSettingsManager.getInstance( - mMockContext).getSettableDeviceStates(); - - assertThat(settableDeviceStates).containsExactly( - new SettableDeviceState(/* deviceState= */ 2, /* isSettable= */ true), - new SettableDeviceState(/* deviceState= */ 4, /* isSettable= */ false), - new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ true), - new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false) - ).inOrder(); - } -} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java index 53a382a9ebf6..b81d13d985e4 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java @@ -22,14 +22,12 @@ import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; +import android.widget.Spinner; import androidx.preference.PreferenceViewHolder; import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; -import com.android.settingslib.widget.settingsspinner.SettingsSpinner; -import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,7 +40,7 @@ public class SettingsSpinnerPreferenceTest { private Context mContext; private PreferenceViewHolder mViewHolder; - private SettingsSpinner mSpinner; + private Spinner mSpinner; private SettingsSpinnerPreference mSpinnerPreference; @Before @@ -53,7 +51,7 @@ public class SettingsSpinnerPreferenceTest { final View rootView = inflater.inflate(mSpinnerPreference.getLayoutResource(), new LinearLayout(mContext), false /* attachToRoot */); mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView); - mSpinner = (SettingsSpinner) mViewHolder.findViewById(R.id.spinner); + mSpinner = (Spinner) mViewHolder.findViewById(R.id.spinner); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java index d7b366e60a1d..bb6b293b0c27 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java @@ -206,9 +206,8 @@ public class A2dpProfileTest { when(config.isMandatoryCodec()).thenReturn(false); when(config.getCodecType()).thenReturn(4); - when(config.getCodecName()).thenReturn("LDAC"); assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo( - String.format(KNOWN_CODEC_LABEL, config.getCodecName())); + String.format(KNOWN_CODEC_LABEL, "LDAC")); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index d53a3e896868..298ee90d311d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -28,6 +28,7 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; import android.content.Context; +import android.os.Parcel; import android.os.ParcelUuid; import org.junit.Before; @@ -60,9 +61,9 @@ public class CachedBluetoothDeviceManagerTest { private final static Map<Integer, ParcelUuid> CAP_GROUP2 = Map.of(2, BluetoothUuid.CAP); private final BluetoothClass DEVICE_CLASS_1 = - new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); + createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); private final BluetoothClass DEVICE_CLASS_2 = - new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE); + createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE); @Mock private LocalBluetoothProfileManager mLocalProfileManager; @Mock @@ -92,6 +93,16 @@ public class CachedBluetoothDeviceManagerTest { private HearingAidDeviceManager mHearingAidDeviceManager; private Context mContext; + private BluetoothClass createBtClass(int deviceClass) { + Parcel p = Parcel.obtain(); + p.writeInt(deviceClass); + p.setDataPosition(0); // reset position of parcel before passing to constructor + + BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p); + p.recycle(); + return bluetoothClass; + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java index 7be176a37bb4..a8e607549ab1 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java @@ -28,6 +28,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.os.Parcel; import org.junit.Before; import org.junit.Test; @@ -48,7 +49,7 @@ public class HearingAidDeviceManagerTest { private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11"; private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22"; private final BluetoothClass DEVICE_CLASS = - new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE); + createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE); @Mock private LocalBluetoothProfileManager mLocalProfileManager; @Mock @@ -67,6 +68,16 @@ public class HearingAidDeviceManagerTest { private HearingAidDeviceManager mHearingAidDeviceManager; private Context mContext; + private BluetoothClass createBtClass(int deviceClass) { + Parcel p = Parcel.obtain(); + p.writeInt(deviceClass); + p.setDataPosition(0); // reset position of parcel before passing to constructor + + BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p); + p.recycle(); + return bluetoothClass; + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index 6f7f73a3e79b..c122a37d0f79 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -31,6 +31,7 @@ import android.bluetooth.BluetoothDevice; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.os.Parcel; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -65,9 +66,9 @@ public class MediaDeviceTest { private static final String ROUTER_ID_3 = "RouterId_3"; private static final String TEST_PACKAGE_NAME = "com.test.playmusic"; private final BluetoothClass mHeadreeClass = - new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); + createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); private final BluetoothClass mCarkitClass = - new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO); + createBtClass(BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO); @Mock private BluetoothDevice mDevice1; @@ -118,6 +119,16 @@ public class MediaDeviceTest { private List<MediaDevice> mMediaDevices = new ArrayList<>(); private PhoneMediaDevice mPhoneMediaDevice; + private BluetoothClass createBtClass(int deviceClass) { + Parcel p = Parcel.obtain(); + p.writeInt(deviceClass); + p.setDataPosition(0); // reset position of parcel before passing to constructor + + BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p); + p.recycle(); + return bluetoothClass; + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java index 3b7fbc73522f..c7e96bcdb856 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java @@ -69,7 +69,7 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto } @Implementation - protected boolean removeActiveDevice(@BluetoothAdapter.ActiveDeviceUse int profiles) { + protected boolean removeActiveDevice(int profiles) { if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL && profiles != ACTIVE_DEVICE_ALL) { return false; @@ -78,8 +78,7 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto } @Implementation - protected boolean setActiveDevice(BluetoothDevice device, - @BluetoothAdapter.ActiveDeviceUse int profiles) { + protected boolean setActiveDevice(BluetoothDevice device, int profiles) { if (device == null) { return false; } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 741fe4f22e74..1df326ad9676 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -536,6 +536,9 @@ <uses-permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" /> <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" /> <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" /> + <!-- Permission needed for CTS test - ConcurrencyTest#testP2pExternalApprover + P2P external approver API sets require MANAGE_WIFI_AUTO_JOIN permission. --> + <uses-permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN" /> <!-- Permission required for CTS tests to enable/disable rate limiting toasts. --> <uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" /> diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp index 46adfeba0fb0..f7bcf1faf33a 100644 --- a/packages/SystemUI/animation/Android.bp +++ b/packages/SystemUI/animation/Android.bp @@ -39,5 +39,5 @@ android_library { ], manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 4540b77d0a40..c3f6a5d18b74 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -48,9 +48,16 @@ private const val TAG = "ActivityLaunchAnimator" * nicely into the starting window. */ class ActivityLaunchAnimator( - private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS) + /** The animator used when animating a View into an app. */ + private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS), + + /** The animator used when animating a Dialog into an app. */ + // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to + // TIMINGS.contentBeforeFadeOutDuration. + private val dialogToAppAnimator: LaunchAnimator = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS) ) { companion object { + /** The timings when animating a View into an app. */ @JvmField val TIMINGS = LaunchAnimator.Timings( totalDuration = 500L, @@ -60,6 +67,17 @@ class ActivityLaunchAnimator( contentAfterFadeInDuration = 183L ) + /** + * The timings when animating a Dialog into an app. We need to wait at least 200ms before + * showing the app (which is under the dialog window) so that the dialog window dim is fully + * faded out, to avoid flicker. + */ + val DIALOG_TIMINGS = TIMINGS.copy( + contentBeforeFadeOutDuration = 200L, + contentAfterFadeInDelay = 200L + ) + + /** The interpolators when animating a View or a dialog into an app. */ val INTERPOLATORS = LaunchAnimator.Interpolators( positionInterpolator = Interpolators.EMPHASIZED, positionXInterpolator = createPositionXInterpolator(), @@ -298,10 +316,17 @@ class ActivityLaunchAnimator( } /** + * Whether this controller is controlling a dialog launch. This will be used to adapt the + * timings, making sure we don't show the app until the dialog dim had the time to fade out. + */ + // TODO(b/218989950): Remove this. + val isDialogLaunch: Boolean + get() = false + + /** * The intent was started. If [willAnimate] is false, nothing else will happen and the * animation will not be started. */ - @JvmDefault fun onIntentStarted(willAnimate: Boolean) {} /** @@ -309,7 +334,6 @@ class ActivityLaunchAnimator( * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called * before the cancellation. */ - @JvmDefault fun onLaunchAnimationCancelled() {} } @@ -317,7 +341,9 @@ class ActivityLaunchAnimator( inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() { private val launchContainer = controller.launchContainer private val context = launchContainer.context - private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer) + private val transactionApplierView = + controller.openingWindowSyncView ?: controller.launchContainer + private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView) private val matrix = Matrix() private val invertMatrix = Matrix() @@ -405,6 +431,13 @@ class ActivityLaunchAnimator( val callback = this@ActivityLaunchAnimator.callback!! val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo) + // Make sure we use the modified timings when animating a dialog into an app. + val launchAnimator = if (controller.isDialogLaunch) { + dialogToAppAnimator + } else { + launchAnimator + } + // TODO(b/184121838): We should somehow get the top and bottom radius of the window // instead of recomputing isExpandingFullyAbove here. val isExpandingFullyAbove = @@ -440,19 +473,29 @@ class ActivityLaunchAnimator( progress: Float, linearProgress: Float ) { - applyStateToWindow(window, state) + // Apply the state to the window only if it is visible, i.e. when the expanding + // view is *not* visible. + if (!state.visible) { + applyStateToWindow(window, state) + } navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } + listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } delegate.onLaunchAnimationProgress(state, progress, linearProgress) } } - // We draw a hole when the additional layer is fading out to reveal the opening window. animation = launchAnimator.startAnimation( controller, endState, windowBackgroundColor, drawHole = true) } private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) { + if (transactionApplierView.viewRootImpl == null) { + // If the view root we synchronize with was detached, don't apply any transaction + // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw). + return + } + val screenBounds = window.screenSpaceBounds val centerX = (screenBounds.left + screenBounds.right) / 2f val centerY = (screenBounds.top + screenBounds.bottom) / 2f @@ -510,6 +553,12 @@ class ActivityLaunchAnimator( state: LaunchAnimator.State, linearProgress: Float ) { + if (transactionApplierView.viewRootImpl == null) { + // If the view root we synchronize with was detached, don't apply any transaction + // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw). + return + } + val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 3051d8056a89..a3c5649e3fec 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -19,7 +19,6 @@ package com.android.systemui.animation import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator -import android.app.ActivityManager import android.app.Dialog import android.graphics.Color import android.graphics.Rect @@ -28,12 +27,12 @@ import android.service.dreams.IDreamManager import android.util.Log import android.util.MathUtils import android.view.GhostView -import android.view.SurfaceControl import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewRootImpl +import android.view.WindowInsets import android.view.WindowManager +import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS import android.widget.FrameLayout import kotlin.math.roundToInt @@ -42,12 +41,17 @@ private const val TAG = "DialogLaunchAnimator" /** * A class that allows dialogs to be started in a seamless way from a view that is transforming * nicely into the starting dialog. + * + * This animator also allows to easily animate a dialog into an activity. + * + * @see showFromView + * @see showFromDialog + * @see createActivityLaunchController */ class DialogLaunchAnimator @JvmOverloads constructor( private val dreamManager: IDreamManager, private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS), - // TODO(b/217621394): Remove special handling for low-RAM devices after animation sync is fixed - private var forceDisableSynchronization: Boolean = ActivityManager.isLowRamDeviceStatic() + private val isForTesting: Boolean = false ) { private companion object { private val TIMINGS = ActivityLaunchAnimator.TIMINGS @@ -113,7 +117,7 @@ class DialogLaunchAnimator @JvmOverloads constructor( dialog = dialog, animateBackgroundBoundsChange, animatedParent, - forceDisableSynchronization + isForTesting ) openedDialogs.add(animatedDialog) @@ -141,6 +145,100 @@ class DialogLaunchAnimator @JvmOverloads constructor( } /** + * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the + * dialog that contains [View]. Note that the dialog must have been show using [showFromView] + * and be currently showing, otherwise this will return null. + * + * The returned controller will take care of dismissing the dialog at the right time after the + * activity started, when the dialog to app animation is done (or when it is cancelled). If this + * method returns null, then the dialog won't be dismissed. + * + * @param view any view inside the dialog to animate. + */ + @JvmOverloads + fun createActivityLaunchController( + view: View, + cujType: Int? = null + ): ActivityLaunchAnimator.Controller? { + val animatedDialog = openedDialogs + .firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl } + ?: return null + + // At this point, we know that the intent of the caller is to dismiss the dialog to show + // an app, so we disable the exit animation into the touch surface because we will never + // want to run it anyways. + animatedDialog.exitAnimationDisabled = true + + val dialog = animatedDialog.dialog + + // Don't animate if the dialog is not showing. + if (!dialog.isShowing) { + return null + } + + val dialogContentWithBackground = animatedDialog.dialogContentWithBackground ?: return null + val controller = + ActivityLaunchAnimator.Controller.fromView(dialogContentWithBackground, cujType) + ?: return null + + // Wrap the controller into one that will instantly dismiss the dialog when the animation is + // done or dismiss it normally (fading it out) if the animation is cancelled. + return object : ActivityLaunchAnimator.Controller by controller { + override val isDialogLaunch = true + + override fun onIntentStarted(willAnimate: Boolean) { + controller.onIntentStarted(willAnimate) + + if (!willAnimate) { + dialog.dismiss() + } + } + + override fun onLaunchAnimationCancelled() { + controller.onLaunchAnimationCancelled() + enableDialogDismiss() + dialog.dismiss() + } + + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + controller.onLaunchAnimationStart(isExpandingFullyAbove) + + // Make sure the dialog is not dismissed during the animation. + disableDialogDismiss() + + // If this dialog was shown from a cascade of other dialogs, make sure those ones + // are dismissed too. + animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss() + + // Remove the dim. + dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + controller.onLaunchAnimationEnd(isExpandingFullyAbove) + + // Hide the dialog then dismiss it to instantly dismiss it without playing the + // animation. + dialog.hide() + enableDialogDismiss() + dialog.dismiss() + } + + private fun disableDialogDismiss() { + dialog.setDismissOverride { /* Do nothing */ } + } + + private fun enableDialogDismiss() { + // We don't set the override to null given that [AnimatedDialog.OnDialogDismissed] + // will still properly dismiss the dialog but will also make sure to clean up + // everything (like making sure that the touched view that triggered the dialog is + // made VISIBLE again). + dialog.setDismissOverride(animatedDialog::onDialogDismissed) + } + } + } + + /** * Ensure that all dialogs currently shown won't animate into their touch surface when * dismissed. * @@ -358,6 +456,21 @@ private class AnimatedDialog( // Make sure the dialog is visible instantly and does not do any window animation. window.attributes.windowAnimations = R.style.Animation_LaunchAnimation + // Ensure that the animation is not clipped by the display cut-out when animating this + // dialog into an app. + window.attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + window.attributes = window.attributes + + // We apply the insets ourselves to make sure that the paddings are set on the correct + // View. + window.setDecorFitsSystemWindows(false) + val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup) + viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsets.Type.displayCutout()) + view.setPadding(insets.left, insets.top, insets.right, insets.bottom) + WindowInsets.CONSUMED + } + // Start the animation once the background view is properly laid out. dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { override fun onLayoutChange( @@ -421,45 +534,12 @@ private class AnimatedDialog( * (or inversely, removed from the UI when the touch surface is made visible). */ private fun synchronizeNextDraw(then: () -> Unit) { - if (forceDisableSynchronization || - !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null || - !decorView.isAttachedToWindow || decorView.viewRootImpl == null) { - // No need to synchronize if either the touch surface or dialog view is not attached - // to a window. + if (forceDisableSynchronization) { then() return } - // Consume the next frames of both view roots to make sure the ghost view is drawn at - // exactly the same time as when the touch surface is made invisible. - var remainingTransactions = 0 - val mergedTransactions = SurfaceControl.Transaction() - - fun onTransaction(transaction: SurfaceControl.Transaction?) { - remainingTransactions-- - transaction?.let { mergedTransactions.merge(it) } - - if (remainingTransactions == 0) { - mergedTransactions.apply() - then() - } - } - - fun consumeNextDraw(viewRootImpl: ViewRootImpl) { - if (viewRootImpl.consumeNextDraw(::onTransaction)) { - remainingTransactions++ - - // Make sure we trigger a traversal. - viewRootImpl.view.invalidate() - } - } - - consumeNextDraw(touchSurface.viewRootImpl) - consumeNextDraw(decorView.viewRootImpl) - - if (remainingTransactions == 0) { - then() - } + ViewRootSync.synchronizeNextDraw(touchSurface, decorView, then) } private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { @@ -523,7 +603,7 @@ private class AnimatedDialog( ) } - private fun onDialogDismissed() { + fun onDialogDismissed() { if (Looper.myLooper() != Looper.getMainLooper()) { dialog.context.mainExecutor.execute { onDialogDismissed() } return diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt index 77386cf2ff10..a4c5c3025220 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt @@ -77,8 +77,8 @@ class LaunchAnimator( * This will be used to: * - Get the associated [Context]. * - Compute whether we are expanding fully above the launch container. - * - Apply surface transactions in sync with RenderThread when animating an activity - * launch. + * - Get to overlay to which we initially put the window background layer, until the + * opening window is made visible (see [openingWindowSyncView]). * * This container can be changed to force this [Controller] to animate the expanding view * inside a different location, for instance to ensure correct layering during the @@ -87,6 +87,18 @@ class LaunchAnimator( var launchContainer: ViewGroup /** + * The [View] with which the opening app window should be synchronized with once it starts + * to be visible. + * + * We will also move the window background layer to this view's overlay once the opening + * window is visible. + * + * If null, this will default to [launchContainer]. + */ + val openingWindowSyncView: View? + get() = null + + /** * Return the [State] of the view that will be animated. We will animate from this state to * the final window state. * @@ -100,11 +112,9 @@ class LaunchAnimator( * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding * fully above the [launchContainer]. */ - @JvmDefault fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {} /** The animation made progress and the expandable view [state] should be updated. */ - @JvmDefault fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {} /** @@ -112,7 +122,6 @@ class LaunchAnimator( * called previously. This is typically used to clean up the resources initialized when the * animation was started. */ - @JvmDefault fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {} } @@ -154,7 +163,7 @@ class LaunchAnimator( } /** The timings (durations and delays) used by this animator. */ - class Timings( + data class Timings( /** The total duration of the animation. */ val totalDuration: Long, @@ -257,8 +266,17 @@ class LaunchAnimator( animator.duration = timings.totalDuration animator.interpolator = LINEAR + // Whether we should move the [windowBackgroundLayer] into the overlay of + // [Controller.openingWindowSyncView] once the opening app window starts to be visible. + val openingWindowSyncView = controller.openingWindowSyncView + val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay + val moveBackgroundLayerWhenAppIsVisible = openingWindowSyncView != null && + openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl + val launchContainerOverlay = launchContainer.overlay var cancelled = false + var movedBackgroundLayer = false + animator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { if (DEBUG) { @@ -278,6 +296,10 @@ class LaunchAnimator( } controller.onLaunchAnimationEnd(isExpandingFullyAbove) launchContainerOverlay.remove(windowBackgroundLayer) + + if (moveBackgroundLayerWhenAppIsVisible) { + openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + } } }) @@ -318,11 +340,29 @@ class LaunchAnimator( timings.contentBeforeFadeOutDuration ) < 1 + if (moveBackgroundLayerWhenAppIsVisible && !state.visible && !movedBackgroundLayer) { + // The expanding view is not visible, so the opening app is visible. If this is the + // first frame when it happens, trigger a one-off sync and move the background layer + // in its new container. + movedBackgroundLayer = true + + launchContainerOverlay.remove(windowBackgroundLayer) + openingWindowSyncViewOverlay!!.add(windowBackgroundLayer) + + ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {}) + } + + val container = if (movedBackgroundLayer) { + openingWindowSyncView!! + } else { + controller.launchContainer + } + applyStateToWindowBackgroundLayer( windowBackgroundLayer, state, linearProgress, - launchContainer, + container, drawHole ) controller.onLaunchAnimationProgress(state, progress, linearProgress) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt new file mode 100644 index 000000000000..5b3e45c9704d --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt @@ -0,0 +1,75 @@ +package com.android.systemui.animation + +import android.app.ActivityManager +import android.view.SurfaceControl +import android.view.View +import android.view.ViewRootImpl + +/** A util class to synchronize 2 view roots. */ +// TODO(b/200284684): Remove this class. +object ViewRootSync { + // TODO(b/217621394): Remove special handling for low-RAM devices after animation sync is fixed + private val forceDisableSynchronization = ActivityManager.isLowRamDeviceStatic() + + /** + * Synchronize the next draw between the view roots of [view] and [otherView], then run [then]. + * + * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the + * next transactions) or disabled (temporarily, on low ram devices). In this case, [then] will + * be called without synchronizing. + */ + fun synchronizeNextDraw( + view: View, + otherView: View, + then: () -> Unit + ) { + if (forceDisableSynchronization || + !view.isAttachedToWindow || view.viewRootImpl == null || + !otherView.isAttachedToWindow || otherView.viewRootImpl == null || + view.viewRootImpl == otherView.viewRootImpl) { + // No need to synchronize if either the touch surface or dialog view is not attached + // to a window. + then() + return + } + + // Consume the next frames of both view roots to make sure the ghost view is drawn at + // exactly the same time as when the touch surface is made invisible. + var remainingTransactions = 0 + val mergedTransactions = SurfaceControl.Transaction() + + fun onTransaction(transaction: SurfaceControl.Transaction?) { + remainingTransactions-- + transaction?.let { mergedTransactions.merge(it) } + + if (remainingTransactions == 0) { + mergedTransactions.apply() + then() + } + } + + fun consumeNextDraw(viewRootImpl: ViewRootImpl) { + if (viewRootImpl.consumeNextDraw(::onTransaction)) { + remainingTransactions++ + + // Make sure we trigger a traversal. + viewRootImpl.view.invalidate() + } + } + + consumeNextDraw(view.viewRootImpl) + consumeNextDraw(otherView.viewRootImpl) + + if (remainingTransactions == 0) { + then() + } + } + + /** + * A Java-friendly API for [synchronizeNextDraw]. + */ + @JvmStatic + fun synchronizeNextDraw(view: View, otherView: View, then: Runnable) { + synchronizeNextDraw(view, otherView, then::run) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 208825ccc8cf..d8b050aa8e2c 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -76,14 +76,14 @@ internal class CoreSpec( enum class Style(internal val coreSpec: CoreSpec) { SPRITZ(CoreSpec( - a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), - a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), - a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), + a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)), + a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)), + a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), - n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)) + n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)) )), TONAL_SPOT(CoreSpec( - a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), + a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 32.0)), a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)), n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), @@ -91,17 +91,17 @@ enum class Style(internal val coreSpec: CoreSpec) { )), VIBRANT(CoreSpec( a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), - a2 = TonalSpec(Hue(HueStrategy.ADD, 10.0), Chroma(ChromaStrategy.EQ, 24.0)), - a3 = TonalSpec(Hue(HueStrategy.ADD, 20.0), Chroma(ChromaStrategy.GTE, 32.0)), + a2 = TonalSpec(Hue(HueStrategy.ADD, 15.0), Chroma(ChromaStrategy.EQ, 24.0)), + a3 = TonalSpec(Hue(HueStrategy.ADD, 30.0), Chroma(ChromaStrategy.GTE, 32.0)), n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)), n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)) )), EXPRESSIVE(CoreSpec( - a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 40.0), Chroma(ChromaStrategy.GTE, 64.0)), - a2 = TonalSpec(Hue(HueStrategy.ADD, 20.0), Chroma(ChromaStrategy.EQ, 24.0)), - a3 = TonalSpec(Hue(HueStrategy.SUBTRACT, 80.0), Chroma(ChromaStrategy.GTE, 64.0)), - n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), - n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 32.0)) + a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 60.0), Chroma(ChromaStrategy.GTE, 64.0)), + a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 30.0), Chroma(ChromaStrategy.EQ, 24.0)), + a3 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), + n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)), + n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)) )), RAINBOW(CoreSpec( a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), diff --git a/packages/SystemUI/res/drawable/ic_chevron_icon.xml b/packages/SystemUI/res/drawable/ic_chevron_icon.xml new file mode 100644 index 000000000000..acbbbcb21503 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_chevron_icon.xml @@ -0,0 +1,28 @@ + +<!-- + ~ Copyright (C) 2022 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="18dp" + android:height="31dp" + android:viewportWidth="18" + android:viewportHeight="31"> + <path + android:pathData="M0.0061,27.8986L2.6906,30.5831L17.9219,15.3518L2.6906,0.1206L0.0061,2.8051L12.5338,15.3518" + android:strokeAlpha="0.7" + android:fillColor="#FFFFFF" + android:fillAlpha="0.7"/> +</vector> diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 5d80da8cb9aa..e1dbe695bd0e 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -113,4 +113,16 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> + <ImageView + android:id="@+id/chevron_icon" + android:autoMirrored="true" + android:src="@drawable/ic_chevron_icon" + android:visibility="invisible" + android:layout_width="@dimen/control_chevron_icon_size" + android:layout_height="@dimen/control_chevron_icon_size" + android:clickable="false" + android:focusable="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 39d7f4f5db4e..c7910afc73fc 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -89,11 +89,6 @@ layout="@layout/keyguard_status_view" android:visibility="gone"/> - <include layout="@layout/idle_host_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone"/> - <include layout="@layout/dock_info_overlay"/> <FrameLayout diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index b318bbc157ca..f1e93c274e40 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -67,10 +67,10 @@ <!-- media output dialog--> <color name="media_dialog_background">@android:color/system_neutral1_900</color> - <color name="media_dialog_active_item_main_content">@android:color/system_accent2_800</color> - <color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_100</color> - <color name="media_dialog_item_status">@android:color/system_accent1_100</color> - <color name="media_dialog_item_background">@android:color/system_neutral2_800</color> + <color name="media_dialog_active_item_main_content">@android:color/system_neutral1_900</color> + <color name="media_dialog_inactive_item_main_content">@android:color/system_neutral1_900</color> + <color name="media_dialog_item_status">@android:color/system_neutral1_900</color> + <color name="media_dialog_item_background">@android:color/system_accent2_50</color> <!-- Biometric dialog colors --> <color name="biometric_dialog_gray">#ffcccccc</color> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index faf518e73e6d..cbda439e5cb2 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -155,6 +155,7 @@ <color name="GM2_grey_900">#202124</color> <color name="GM2_red_300">#F28B82</color> + <color name="GM2_red_500">#EA4335</color> <color name="GM2_red_700">#C5221F</color> <color name="GM2_blue_300">#8AB4F8</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index bb1ffa8fec38..178f93ae3c93 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -608,10 +608,6 @@ <!-- Component name of communal source service --> <string name="config_communalSourceComponent" translatable="false">@null</string> - <!-- Whether idle mode should be enabled. When enabled, the lock screen will timeout to an idle - screen on inactivity. --> - <bool name="config_enableIdleMode">false</bool> - <!-- This value is used when calculating whether the device is in ambient light mode. It is light mode when the light sensor sample value exceeds above this value. --> <integer name="config_ambientLightModeThreshold">5</integer> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a08d824b837e..370413400766 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1038,6 +1038,7 @@ <dimen name="control_spinner_padding_horizontal">20dp</dimen> <dimen name="control_text_size">14sp</dimen> <dimen name="control_icon_size">24dp</dimen> + <dimen name="control_chevron_icon_size">20dp</dimen> <dimen name="control_spacing">8dp</dimen> <dimen name="control_list_divider">1dp</dimen> <dimen name="control_corner_radius">14dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b98354523e94..09ae3f9e3376 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -399,6 +399,11 @@ <!-- that would otherwise be intercepted by the Shade. --> <item name="android:windowFullscreen">true</item> <item name="android:windowBackground">@android:color/transparent</item> + + <!-- Empty enter/exit animation, we will animate in-window. Note that the implementation --> + <!-- of ActionsDialogLite relies on this to be null (resource=0) to detect when to run --> + <!-- the in-window animation. --> + <item name="android:windowAnimationStyle">@null</item> </style> <style name="QSBorderlessButton"> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 11fffd053143..f5084f5cff76 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Handler; +import android.os.HandlerThread; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -28,6 +29,7 @@ import com.android.systemui.dagger.DaggerGlobalRootComponent; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dagger.WMComponent; +import com.android.wm.shell.dagger.WMShellConcurrencyModule; import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider; import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider; import com.android.wm.shell.transition.ShellTransitions; @@ -96,8 +98,9 @@ public class SystemUIFactory { && android.os.Process.myUserHandle().isSystem() && ActivityThread.currentProcessName().equals(ActivityThread.currentPackageName()); mRootComponent = buildGlobalRootComponent(context); + // Stand up WMComponent - mWMComponent = mRootComponent.getWMComponentBuilder().build(); + setupWmComponent(context); if (mInitializeComponents) { // Only initialize when not starting from tests since this currently initializes some // components that shouldn't be run in the test environment @@ -124,8 +127,8 @@ public class SystemUIFactory { .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) - .setCompatUI(Optional.of(mWMComponent.getCompatUI())) - .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop())) + .setCompatUI(mWMComponent.getCompatUI()) + .setDragAndDrop(mWMComponent.getDragAndDrop()) .setBackAnimation(mWMComponent.getBackAnimation()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option @@ -161,6 +164,36 @@ public class SystemUIFactory { } /** + * Sets up {@link #mWMComponent}. On devices where the Shell runs on its own main thread, + * this will pre-create the thread to ensure that the components are constructed on the + * same thread, to reduce the likelihood of side effects from running the constructors on + * a different thread than the rest of the class logic. + */ + private void setupWmComponent(Context context) { + WMComponent.Builder wmBuilder = mRootComponent.getWMComponentBuilder(); + if (!mInitializeComponents || !WMShellConcurrencyModule.enableShellMainThread(context)) { + // If running under tests or shell thread is not enabled, we don't need anything special + mWMComponent = wmBuilder.build(); + return; + } + + // If the shell main thread is enabled, initialize the component on that thread + HandlerThread shellThread = WMShellConcurrencyModule.createShellMainThread(); + shellThread.start(); + + // Use an async handler since we don't care about synchronization + Handler shellHandler = Handler.createAsync(shellThread.getLooper()); + boolean built = shellHandler.runWithScissors(() -> { + wmBuilder.setShellMainThread(shellThread); + mWMComponent = wmBuilder.build(); + }, 5000); + if (!built) { + Log.w(TAG, "Failed to initialize WMComponent"); + throw new RuntimeException(); + } + } + + /** * Prepares the SysUIComponent builder before it is built. * @param sysUIBuilder the builder provided by the root component's getSysUIComponent() method * @param wm the built WMComponent from the root component's getWMComponent() method diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java deleted file mode 100644 index 2d59e1390083..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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.communal.conditions; - -import android.database.ContentObserver; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; -import android.net.wifi.WifiInfo; -import android.os.Handler; -import android.os.UserHandle; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.condition.Condition; -import com.android.systemui.util.settings.SecureSettings; - -import java.util.Arrays; -import java.util.HashSet; - -import javax.inject.Inject; - -/** - * Monitors Wi-Fi connections and triggers callback, if any, when the device is connected to and - * disconnected from a trusted network. - */ -public class CommunalTrustedNetworkCondition extends Condition { - private final String mTag = getClass().getSimpleName(); - private final ConnectivityManager mConnectivityManager; - private final ContentObserver mTrustedNetworksObserver; - private final SecureSettings mSecureSettings; - - // The SSID of the connected Wi-Fi network. Null if not connected to Wi-Fi. - private String mWifiSSID; - - // Set of SSIDs of trusted networks. - private final HashSet<String> mTrustedNetworks = new HashSet<>(); - - /** - * The deliminator used to separate trusted network keys saved as a string in secure settings. - */ - public static final String SETTINGS_STRING_DELIMINATOR = ",/"; - - @Inject - public CommunalTrustedNetworkCondition(@Main Handler handler, - ConnectivityManager connectivityManager, SecureSettings secureSettings) { - mConnectivityManager = connectivityManager; - mSecureSettings = secureSettings; - - mTrustedNetworksObserver = new ContentObserver(handler) { - @Override - public void onChange(boolean selfChange) { - fetchTrustedNetworks(); - } - }; - } - - /** - * Starts monitoring for trusted network connection. Ignores if already started. - */ - @Override - protected void start() { - if (shouldLog()) Log.d(mTag, "start listening for wifi connections"); - - fetchTrustedNetworks(); - - final NetworkRequest wifiNetworkRequest = new NetworkRequest.Builder().addTransportType( - NetworkCapabilities.TRANSPORT_WIFI).build(); - mConnectivityManager.registerNetworkCallback(wifiNetworkRequest, mNetworkCallback); - mSecureSettings.registerContentObserverForUser( - Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS, false, mTrustedNetworksObserver, - UserHandle.USER_SYSTEM); - } - - /** - * Stops monitoring for trusted network connection. - */ - @Override - protected void stop() { - if (shouldLog()) Log.d(mTag, "stop listening for wifi connections"); - - mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); - mSecureSettings.unregisterContentObserver(mTrustedNetworksObserver); - } - - private void updateWifiInfo(WifiInfo wifiInfo) { - if (wifiInfo == null) { - mWifiSSID = null; - } else { - // Remove the wrapping quotes around the SSID. - mWifiSSID = wifiInfo.getSSID().replace("\"", ""); - } - - checkIfConnectedToTrustedNetwork(); - } - - private void fetchTrustedNetworks() { - final String trustedNetworksString = mSecureSettings.getStringForUser( - Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS, UserHandle.USER_SYSTEM); - mTrustedNetworks.clear(); - - if (shouldLog()) Log.d(mTag, "fetched trusted networks: " + trustedNetworksString); - - if (TextUtils.isEmpty(trustedNetworksString)) { - return; - } - - mTrustedNetworks.addAll( - Arrays.asList(trustedNetworksString.split(SETTINGS_STRING_DELIMINATOR))); - - checkIfConnectedToTrustedNetwork(); - } - - private void checkIfConnectedToTrustedNetwork() { - final boolean connectedToTrustedNetwork = mWifiSSID != null && mTrustedNetworks.contains( - mWifiSSID); - - if (shouldLog()) { - Log.d(mTag, (connectedToTrustedNetwork ? "connected to" : "disconnected from") - + " a trusted network"); - } - - updateCondition(connectedToTrustedNetwork); - } - - private final ConnectivityManager.NetworkCallback mNetworkCallback = - new ConnectivityManager.NetworkCallback( - ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { - private boolean mIsConnected = false; - private WifiInfo mWifiInfo; - - @Override - public void onAvailable(@NonNull Network network) { - super.onAvailable(network); - - if (shouldLog()) Log.d(mTag, "connected to wifi"); - - mIsConnected = true; - if (mWifiInfo != null) { - updateWifiInfo(mWifiInfo); - } - } - - @Override - public void onLost(@NonNull Network network) { - super.onLost(network); - - if (shouldLog()) Log.d(mTag, "disconnected from wifi"); - - mIsConnected = false; - mWifiInfo = null; - updateWifiInfo(null); - } - - @Override - public void onCapabilitiesChanged(@NonNull Network network, - @NonNull NetworkCapabilities networkCapabilities) { - super.onCapabilitiesChanged(network, networkCapabilities); - - mWifiInfo = (WifiInfo) networkCapabilities.getTransportInfo(); - - if (mIsConnected) { - updateWifiInfo(mWifiInfo); - } - } - }; - - private boolean shouldLog() { - return Log.isLoggable(mTag, Log.DEBUG); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java index e1f1ac42884d..814b251d08ab 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java @@ -20,8 +20,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; import android.text.TextUtils; -import android.view.View; -import android.widget.FrameLayout; import androidx.annotation.Nullable; @@ -30,9 +28,6 @@ import com.android.systemui.communal.CommunalSource; import com.android.systemui.communal.PackageObserver; import com.android.systemui.communal.conditions.CommunalSettingCondition; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.idle.AmbientLightModeMonitor; -import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm; -import com.android.systemui.idle.dagger.IdleViewComponent; import com.android.systemui.util.condition.Condition; import com.android.systemui.util.condition.Monitor; import com.android.systemui.util.condition.dagger.MonitorComponent; @@ -46,7 +41,6 @@ import java.util.Set; import javax.inject.Named; import javax.inject.Provider; -import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.ElementsIntoSet; @@ -58,22 +52,12 @@ import dagger.multibindings.StringKey; */ @Module(subcomponents = { CommunalViewComponent.class, - IdleViewComponent.class, }) public interface CommunalModule { - String IDLE_VIEW = "idle_view"; String COMMUNAL_CONDITIONS = "communal_conditions"; /** */ @Provides - @Named(IDLE_VIEW) - static View provideIdleView(Context context) { - FrameLayout view = new FrameLayout(context); - return view; - } - - /** */ - @Provides static Optional<CommunalSource.Observer> provideCommunalSourcePackageObserver( Context context, @Main Resources resources) { final String componentName = resources.getString(R.string.config_communalSourceComponent); @@ -87,15 +71,6 @@ public interface CommunalModule { } /** - * Provides LightSensorEventsDebounceAlgorithm as an instance to DebounceAlgorithm interface. - * @param algorithm the instance of algorithm that is bound to the interface. - * @return the interface that is bound to. - */ - @Binds - AmbientLightModeMonitor.DebounceAlgorithm ambientLightDebounceAlgorithm( - LightSensorEventsDebounceAlgorithm algorithm); - - /** * Provides a set of conditions that need to be fulfilled in order for Communal Mode to display. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 47e749cd1185..4819bf565cbc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -118,6 +118,7 @@ class ControlViewHolder( private var nextStatusText: CharSequence = "" val title: TextView = layout.requireViewById(R.id.title) val subtitle: TextView = layout.requireViewById(R.id.subtitle) + val chevronIcon: ImageView = layout.requireViewById(R.id.chevron_icon) val context: Context = layout.getContext() val clipLayer: ClipDrawable lateinit var cws: ControlWithState @@ -163,6 +164,7 @@ class ControlViewHolder( cws.control?.let { title.setText(it.title) subtitle.setText(it.subtitle) + chevronIcon.visibility = if (usePanel()) View.VISIBLE else View.INVISIBLE } } @@ -469,6 +471,7 @@ class ControlViewHolder( updateContentDescription() status.setTextColor(color) + chevronIcon.imageTintList = color control?.getCustomIcon()?.let { icon.setImageIcon(it) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index b9266923a157..b02074a65e9a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -17,6 +17,9 @@ package com.android.systemui.dagger; import android.content.Context; +import android.os.HandlerThread; + +import androidx.annotation.Nullable; import com.android.systemui.SystemUIFactory; import com.android.systemui.tv.TvWMComponent; @@ -26,6 +29,7 @@ import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.dagger.TvWMShellModule; import com.android.wm.shell.dagger.WMShellModule; @@ -44,6 +48,7 @@ import com.android.wm.shell.transition.ShellTransitions; import java.util.Optional; +import dagger.BindsInstance; import dagger.Subcomponent; /** @@ -64,6 +69,10 @@ public interface WMComponent { */ @Subcomponent.Builder interface Builder { + + @BindsInstance + Builder setShellMainThread(@Nullable @ShellMainThread HandlerThread t); + WMComponent build(); } @@ -120,10 +129,10 @@ public interface WMComponent { Optional<RecentTasks> getRecentTasks(); @WMSingleton - CompatUI getCompatUI(); + Optional<CompatUI> getCompatUI(); @WMSingleton - DragAndDrop getDragAndDrop(); + Optional<DragAndDrop> getDragAndDrop(); @WMSingleton Optional<BackAnimation> getBackAnimation(); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index befb648152d4..789ad6223e79 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -118,9 +118,11 @@ public class DozeMachine { switch (this) { case UNINITIALIZED: case INITIALIZED: - case DOZE_REQUEST_PULSE: return parameters.shouldControlScreenOff() ? Display.STATE_ON : Display.STATE_OFF; + case DOZE_REQUEST_PULSE: + return parameters.getDisplayNeedsBlanking() ? Display.STATE_OFF + : Display.STATE_ON; case DOZE_AOD_PAUSED: case DOZE: return Display.STATE_OFF; diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index f0371fc1f0cd..84fa6a6772fe 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -31,6 +31,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_G import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Dialog; @@ -76,8 +77,10 @@ import android.view.GestureDetector; import android.view.IWindowManager; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.Surface; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -109,6 +112,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.MultiListLayout; import com.android.systemui.MultiListLayout.MultiListAdapter; import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.animation.Interpolators; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Background; @@ -2170,6 +2174,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private Optional<StatusBar> mStatusBarOptional; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private LockPatternUtils mLockPatternUtils; + private float mWindowDimAmount; protected ViewGroup mContainer; @@ -2251,6 +2256,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initializeLayout(); + mWindowDimAmount = getWindow().getAttributes().dimAmount; } @Override @@ -2459,6 +2465,96 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mNotificationShadeWindowController.setRequestTopUi(true, TAG); mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) .commitUpdate(mContext.getDisplayId()); + + // By default this dialog windowAnimationStyle is null, and therefore windowAnimations + // should be equal to 0 which means we need to animate the dialog in-window. If it's not + // equal to 0, it means it has been overridden to animate (e.g. by the + // DialogLaunchAnimator) so we don't run the animation. + boolean shouldAnimateInWindow = getWindow().getAttributes().windowAnimations == 0; + if (shouldAnimateInWindow) { + startAnimation(true /* isEnter */, null /* then */); + + // Override the dialog dismiss so that we can animate in-window before dismissing + // the dialog. + setDismissOverride(() -> { + startAnimation(false /* isEnter */, /* then */ () -> { + setDismissOverride(null); + + // Hide then dismiss to instantly dismiss. + hide(); + dismiss(); + }); + }); + } + } + + /** Run either the enter or exit animation, then run {@code then}. */ + private void startAnimation(boolean isEnter, @Nullable Runnable then) { + ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + + // Note: these specs should be the same as in popup_enter_material and + // popup_exit_material. + float translationPx; + Resources resources = getContext().getResources(); + if (isEnter) { + translationPx = resources.getDimension(R.dimen.popup_enter_animation_from_y_delta); + animator.setInterpolator(Interpolators.STANDARD); + animator.setDuration(resources.getInteger(R.integer.config_activityDefaultDur)); + } else { + translationPx = resources.getDimension(R.dimen.popup_exit_animation_to_y_delta); + animator.setInterpolator(Interpolators.STANDARD_ACCELERATE); + animator.setDuration(resources.getInteger(R.integer.config_activityShortDur)); + } + + Window window = getWindow(); + int rotation = window.getWindowManager().getDefaultDisplay().getRotation(); + + animator.addUpdateListener(valueAnimator -> { + float progress = (float) valueAnimator.getAnimatedValue(); + + float alpha = isEnter ? progress : 1 - progress; + mGlobalActionsLayout.setAlpha(alpha); + window.setDimAmount(mWindowDimAmount * alpha); + + // TODO(b/213872558): Support devices that don't have their power button on the + // right. + float translation = + isEnter ? translationPx * (1 - progress) : translationPx * progress; + switch (rotation) { + case Surface.ROTATION_0: + mGlobalActionsLayout.setTranslationX(translation); + break; + case Surface.ROTATION_90: + mGlobalActionsLayout.setTranslationY(-translation); + break; + case Surface.ROTATION_180: + mGlobalActionsLayout.setTranslationX(-translation); + break; + case Surface.ROTATION_270: + mGlobalActionsLayout.setTranslationY(translation); + break; + } + }); + + animator.addListener(new AnimatorListenerAdapter() { + private int mPreviousLayerType; + + @Override + public void onAnimationStart(Animator animation, boolean isReverse) { + mPreviousLayerType = mGlobalActionsLayout.getLayerType(); + mGlobalActionsLayout.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + + @Override + public void onAnimationEnd(Animator animation) { + mGlobalActionsLayout.setLayerType(mPreviousLayerType, null); + if (then != null) { + then.run(); + } + } + }); + + animator.start(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt b/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt deleted file mode 100644 index fa00dbb808fb..000000000000 --- a/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.idle - -import android.annotation.IntDef -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager -import android.util.Log -import com.android.systemui.util.sensors.AsyncSensorManager -import javax.inject.Inject - -/** - * Monitors ambient light signals, applies a debouncing algorithm, and produces the current - * ambient light mode. - * - * @property algorithm the debounce algorithm which transforms light sensor events into an - * ambient light mode. - * @property sensorManager the sensor manager used to register sensor event updates. - */ -class AmbientLightModeMonitor @Inject constructor( - private val algorithm: DebounceAlgorithm, - private val sensorManager: AsyncSensorManager -) { - companion object { - private const val TAG = "AmbientLightModeMonitor" - private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) - - const val AMBIENT_LIGHT_MODE_LIGHT = 0 - const val AMBIENT_LIGHT_MODE_DARK = 1 - const val AMBIENT_LIGHT_MODE_UNDECIDED = 2 - } - - // Light sensor used to detect ambient lighting conditions. - private val lightSensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) - - // Represents all ambient light modes. - @Retention(AnnotationRetention.SOURCE) - @IntDef(AMBIENT_LIGHT_MODE_LIGHT, AMBIENT_LIGHT_MODE_DARK, AMBIENT_LIGHT_MODE_UNDECIDED) - annotation class AmbientLightMode - - /** - * Start monitoring the current ambient light mode. - * - * @param callback callback that gets triggered when the ambient light mode changes. - */ - fun start(callback: Callback) { - if (DEBUG) Log.d(TAG, "start monitoring ambient light mode") - - if (lightSensor == null) { - if (DEBUG) Log.w(TAG, "light sensor not available") - return - } - - algorithm.start(callback) - sensorManager.registerListener(mSensorEventListener, lightSensor, - SensorManager.SENSOR_DELAY_NORMAL) - } - - /** - * Stop monitoring the current ambient light mode. - */ - fun stop() { - if (DEBUG) Log.d(TAG, "stop monitoring ambient light mode") - - algorithm.stop() - sensorManager.unregisterListener(mSensorEventListener) - } - - private val mSensorEventListener: SensorEventListener = object : SensorEventListener { - override fun onSensorChanged(event: SensorEvent) { - if (event.values.isEmpty()) { - if (DEBUG) Log.w(TAG, "SensorEvent doesn't have any value") - return - } - - algorithm.onUpdateLightSensorEvent(event.values[0]) - } - - override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { - // Do nothing. - } - } - - /** - * Interface of the ambient light mode callback, which gets triggered when the mode changes. - */ - interface Callback { - fun onChange(@AmbientLightMode mode: Int) - } - - /** - * Interface of the algorithm that transforms light sensor events to an ambient light mode. - */ - interface DebounceAlgorithm { - // Setting Callback to nullable so mockito can verify without throwing NullPointerException. - fun start(callback: Callback?) - fun stop() - fun onUpdateLightSensorEvent(value: Float) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/idle/IdleHostView.java b/packages/SystemUI/src/com/android/systemui/idle/IdleHostView.java deleted file mode 100644 index 7d79279799ea..000000000000 --- a/packages/SystemUI/src/com/android/systemui/idle/IdleHostView.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.idle; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * {@link IdleHostView} houses a surface to be displayed when the device idle. - */ -public class IdleHostView extends FrameLayout { - public IdleHostView(@NonNull Context context) { - this(context, null); - } - - public IdleHostView(@NonNull Context context, - @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public IdleHostView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java b/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java deleted file mode 100644 index 624d01f6f3c6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * 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.idle; - -import static com.android.systemui.communal.dagger.CommunalModule.IDLE_VIEW; - -import android.annotation.IntDef; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.os.PowerManager; -import android.os.SystemClock; -import android.util.Log; -import android.view.View; - -import com.android.systemui.R; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.ViewController; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; - -/** - * {@link IdleHostViewController} processes signals to control the lifecycle of the idle screen. - */ -public class IdleHostViewController extends ViewController<IdleHostView> { - private static final String TAG = "IdleHostViewController"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - @Retention(RetentionPolicy.RUNTIME) - @IntDef({STATE_IDLE_MODE_ENABLED, STATE_KEYGUARD_SHOWING, STATE_DOZING, STATE_DREAMING, - STATE_LOW_LIGHT}) - public @interface State {} - - // Set at construction to indicate idle mode is available. - private static final int STATE_IDLE_MODE_ENABLED = 1 << 0; - - // Set when keyguard is present, even below a dream or AOD. AKA the device is locked. - private static final int STATE_KEYGUARD_SHOWING = 1 << 1; - - // Set when the device has entered a dozing / low power state. - private static final int STATE_DOZING = 1 << 2; - - // Set when the device has entered a dreaming state, which includes dozing. - private static final int STATE_DREAMING = 1 << 3; - - // Set when the device is in a low light environment. - private static final int STATE_LOW_LIGHT = 1 << 4; - - // The aggregate current state. - private int mState; - private boolean mIsMonitoringLowLight; - private boolean mIsMonitoringDream; - - private final BroadcastDispatcher mBroadcastDispatcher; - - private final PowerManager mPowerManager; - - // Keyguard state controller for monitoring keyguard show state. - private final KeyguardStateController mKeyguardStateController; - - // Status bar state controller for monitoring when the device is dozing. - private final StatusBarStateController mStatusBarStateController; - - // Intent filter for receiving dream broadcasts. - private IntentFilter mDreamIntentFilter; - - // Monitor for the current ambient light mode. Used to trigger / exit low-light mode. - private final AmbientLightModeMonitor mAmbientLightModeMonitor; - - private final KeyguardStateController.Callback mKeyguardCallback = - new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - setState(STATE_KEYGUARD_SHOWING, mKeyguardStateController.isShowing()); - } - }; - - private final StatusBarStateController.StateListener mStatusBarCallback = - new StatusBarStateController.StateListener() { - @Override - public void onDozingChanged(boolean isDozing) { - setState(STATE_DOZING, isDozing); - } - }; - - private final BroadcastReceiver mDreamStateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_DREAMING_STARTED.equals(intent.getAction())) { - setState(STATE_DREAMING, true); - } else if (Intent.ACTION_DREAMING_STOPPED.equals(intent.getAction())) { - setState(STATE_DREAMING, false); - } - } - }; - - private final AmbientLightModeMonitor.Callback mAmbientLightModeCallback = - mode -> { - boolean shouldBeLowLight; - switch (mode) { - case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED: - return; - case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT: - shouldBeLowLight = false; - break; - case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK: - shouldBeLowLight = true; - break; - default: - Log.w(TAG, "invalid ambient light mode"); - return; - } - - if (DEBUG) Log.d(TAG, "ambient light mode changed to " + mode); - - final boolean isLowLight = getState(STATE_LOW_LIGHT); - if (shouldBeLowLight != isLowLight) { - setState(STATE_LOW_LIGHT, shouldBeLowLight); - } - }; - - final Provider<View> mIdleViewProvider; - - @Inject - protected IdleHostViewController( - BroadcastDispatcher broadcastDispatcher, - PowerManager powerManager, - IdleHostView view, - @Main Resources resources, - @Named(IDLE_VIEW) Provider<View> idleViewProvider, - KeyguardStateController keyguardStateController, - StatusBarStateController statusBarStateController, - AmbientLightModeMonitor ambientLightModeMonitor) { - super(view); - mBroadcastDispatcher = broadcastDispatcher; - mPowerManager = powerManager; - mIdleViewProvider = idleViewProvider; - mKeyguardStateController = keyguardStateController; - mStatusBarStateController = statusBarStateController; - mAmbientLightModeMonitor = ambientLightModeMonitor; - - mState = STATE_KEYGUARD_SHOWING; - - final boolean enabled = resources.getBoolean(R.bool.config_enableIdleMode); - if (enabled) { - mState |= STATE_IDLE_MODE_ENABLED; - } - - setState(mState, true); - - if (DEBUG) { - Log.d(TAG, "initial state:" + mState + " enabled:" + enabled); - } - } - - @Override - public void init() { - super.init(); - - setState(STATE_KEYGUARD_SHOWING, mKeyguardStateController.isShowing()); - setState(STATE_DOZING, mStatusBarStateController.isDozing()); - } - - private void setState(@State int state, boolean active) { - final int oldState = mState; - - if (active) { - mState |= state; - } else { - mState &= ~state; - } - - if (oldState == mState) { - return; - } - - if (DEBUG) { - Log.d(TAG, "set " + getStateName(state) + " to " + active); - logCurrentState(); - } - - final boolean inCommunalMode = getState(STATE_IDLE_MODE_ENABLED) - && getState(STATE_KEYGUARD_SHOWING); - - enableDreamMonitoring(inCommunalMode); - enableLowLightMonitoring(inCommunalMode); - - if (state == STATE_LOW_LIGHT) { - enableLowLightMode(inCommunalMode && active); - } - } - - private void enableDreamMonitoring(boolean enable) { - if (mIsMonitoringDream == enable) { - return; - } - - mIsMonitoringDream = enable; - - if (DEBUG) { - Log.d(TAG, (enable ? "enable" : "disable") + " dream monitoring"); - } - - if (mDreamIntentFilter == null) { - mDreamIntentFilter = new IntentFilter(); - mDreamIntentFilter.addAction(Intent.ACTION_DREAMING_STARTED); - mDreamIntentFilter.addAction(Intent.ACTION_DREAMING_STOPPED); - } - - if (enable) { - mBroadcastDispatcher.registerReceiver(mDreamStateReceiver, mDreamIntentFilter); - } else { - mBroadcastDispatcher.unregisterReceiver(mDreamStateReceiver); - } - } - - private void enableLowLightMonitoring(boolean enable) { - if (enable == mIsMonitoringLowLight) { - return; - } - - mIsMonitoringLowLight = enable; - - if (mIsMonitoringLowLight) { - if (DEBUG) Log.d(TAG, "enable low light monitoring"); - mAmbientLightModeMonitor.start(mAmbientLightModeCallback); - } else { - if (DEBUG) Log.d(TAG, "disable low light monitoring"); - mAmbientLightModeMonitor.stop(); - } - } - - private void enableLowLightMode(boolean enable) { - if (enable == getState(STATE_DOZING)) { - return; - } - - if (enable) { - if (DEBUG) Log.d(TAG, "enter low light, start dozing"); - - mPowerManager.goToSleep( - SystemClock.uptimeMillis(), - PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0); - } else { - if (DEBUG) Log.d(TAG, "exit low light, stop dozing"); - mPowerManager.wakeUp(SystemClock.uptimeMillis(), - PowerManager.WAKE_REASON_APPLICATION, "Exit low light condition"); - } - } - - @Override - protected void onViewAttached() { - if (DEBUG) { - Log.d(TAG, "onViewAttached"); - } - - mKeyguardStateController.addCallback(mKeyguardCallback); - mStatusBarStateController.addCallback(mStatusBarCallback); - } - - @Override - protected void onViewDetached() { - mKeyguardStateController.removeCallback(mKeyguardCallback); - mStatusBarStateController.removeCallback(mStatusBarCallback); - } - - private String getStateName(@State int state) { - switch (state) { - case STATE_IDLE_MODE_ENABLED: - return "STATE_IDLE_MODE_ENABLED"; - case STATE_KEYGUARD_SHOWING: - return "STATE_KEYGUARD_SHOWING"; - case STATE_DOZING: - return "STATE_DOZING"; - case STATE_DREAMING: - return "STATE_DREAMING"; - case STATE_LOW_LIGHT: - return "STATE_LOW_LIGHT"; - default: - return "STATE_UNKNOWN"; - } - } - - private boolean getState(@State int state) { - return getState(state, mState); - } - - private boolean getState(@State int state, int oldState) { - return (oldState & state) == state; - } - - private String getStateLog(@State int state) { - return getStateName(state) + " = " + getState(state); - } - - private void logCurrentState() { - Log.d(TAG, "current state: {\n" - + "\t" + getStateLog(STATE_IDLE_MODE_ENABLED) + "\n" - + "\t" + getStateLog(STATE_KEYGUARD_SHOWING) + "\n" - + "\t" + getStateLog(STATE_DOZING) + "\n" - + "\t" + getStateLog(STATE_DREAMING) + "\n" - + "\t" + getStateLog(STATE_LOW_LIGHT) + "\n" - + "}"); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/idle/InputMonitorFactory.java b/packages/SystemUI/src/com/android/systemui/idle/InputMonitorFactory.java deleted file mode 100644 index be0a378d10a5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/idle/InputMonitorFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.idle; - -import com.android.systemui.dagger.qualifiers.DisplayId; -import com.android.systemui.shared.system.InputMonitorCompat; - -import javax.inject.Inject; - -/** - * {@link InputMonitorFactory} generates instances of {@link InputMonitorCompat}. - */ -public class InputMonitorFactory { - private final int mDisplayId; - - @Inject - public InputMonitorFactory(@DisplayId int displayId) { - mDisplayId = displayId; - } - - /** - * Generates a new {@link InputMonitorCompat}. - * - * @param identifier Identifier to generate monitor with. - * @return A {@link InputMonitorCompat} instance. - */ - public InputMonitorCompat getInputMonitor(String identifier) { - return new InputMonitorCompat(identifier, mDisplayId); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/idle/LightSensorEventsDebounceAlgorithm.kt b/packages/SystemUI/src/com/android/systemui/idle/LightSensorEventsDebounceAlgorithm.kt deleted file mode 100644 index 7b06b965a0b3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/idle/LightSensorEventsDebounceAlgorithm.kt +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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.idle - -import android.content.res.Resources -import android.util.Log -import com.android.systemui.R -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.util.concurrency.DelayableExecutor -import javax.inject.Inject - -/** - * An algorithm that receives light sensor events, debounces the signals, and transforms into an - * ambient light mode: light, dark, or undecided. - * - * More about the algorithm at go/titan-light-sensor-debouncer. - */ -class LightSensorEventsDebounceAlgorithm @Inject constructor( - @Main private val executor: DelayableExecutor, - @Main resources: Resources -) : AmbientLightModeMonitor.DebounceAlgorithm { - companion object { - private const val TAG = "LightDebounceAlgorithm" - private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) - } - - // The ambient mode is considered light mode when the light sensor value increases exceeding - // this value. - private val lightModeThreshold = - resources.getInteger(R.integer.config_ambientLightModeThreshold) - - // The ambient mode is considered dark mode when the light sensor value drops below this - // value. - private val darkModeThreshold = resources.getInteger(R.integer.config_ambientDarkModeThreshold) - - // Each sample for calculating light mode contains light sensor events collected for this - // duration of time in milliseconds. - private val lightSamplingSpanMillis = - resources.getInteger(R.integer.config_ambientLightModeSamplingSpanMillis) - - // Each sample for calculating dark mode contains light sensor events collected for this - // duration of time in milliseconds. - private val darkSamplingSpanMillis = - resources.getInteger(R.integer.config_ambientDarkModeSamplingSpanMillis) - - // The calculations for light mode is performed at this frequency in milliseconds. - private val lightSamplingFrequencyMillis = - resources.getInteger(R.integer.config_ambientLightModeSamplingFrequencyMillis) - - // The calculations for dark mode is performed at this frequency in milliseconds. - private val darkSamplingFrequencyMillis = - resources.getInteger(R.integer.config_ambientDarkModeSamplingFrequencyMillis) - - // Registered callback, which gets triggered when the ambient light mode changes. - private var callback: AmbientLightModeMonitor.Callback? = null - - // Queue of bundles used for calculating [isLightMode], ordered from oldest to latest. - val bundlesQueueLightMode = ArrayList<ArrayList<Float>>() - - // Queue of bundles used for calculating [isDarkMode], ordered from oldest to latest - val bundlesQueueDarkMode = ArrayList<ArrayList<Float>>() - - // The current ambient light mode. - @AmbientLightModeMonitor.AmbientLightMode - var mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED - set(value) { - if (field == value) return - field = value - - if (DEBUG) Log.d(TAG, "ambient light mode changed to $value") - - callback?.onChange(value) - } - - // The latest claim of whether it should be light mode. - var isLightMode = false - set(value) { - if (field == value) return - field = value - - if (DEBUG) Log.d(TAG, "isLightMode: $value") - - mode = when { - isDarkMode -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK - value -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT - else -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED - } - } - - // The latest claim of whether it should be dark mode. - var isDarkMode = false - set(value) { - if (field == value) return - field = value - - if (DEBUG) Log.d(TAG, "isDarkMode: $value") - - mode = when { - value -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK - isLightMode -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT - else -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED - } - } - - // The latest average of the light mode bundle. - var bundleAverageLightMode = 0.0 - set(value) { - field = value - - if (DEBUG) Log.d(TAG, "light mode average: $value") - - isLightMode = value > lightModeThreshold - } - - // The latest average of the dark mode bundle. - var bundleAverageDarkMode = 0.0 - set(value) { - field = value - - if (DEBUG) Log.d(TAG, "dark mode average: $value") - - isDarkMode = value < darkModeThreshold - } - - // The latest bundle for calculating light mode claim. - var bundleLightMode = ArrayList<Float>() - set(value) { - field = value - - val average = value.average() - - if (!average.isNaN()) { - bundleAverageLightMode = average - } - } - - // The latest bundle for calculating dark mode claim. - var bundleDarkMode = ArrayList<Float>() - set(value) { - field = value - - val average = value.average() - - if (!average.isNaN()) { - bundleAverageDarkMode = average - } - } - - // The latest light level from light sensor event updates. - var lightSensorLevel = 0.0f - set(value) { - field = value - - bundlesQueueLightMode.forEach { bundle -> bundle.add(value) } - bundlesQueueDarkMode.forEach { bundle -> bundle.add(value) } - } - - // Creates a new bundle that collects light sensor events for calculating the light mode claim, - // and adds it to the end of the queue. It schedules a call to dequeue this bundle after - // [LIGHT_SAMPLING_SPAN_MILLIS]. Once started, it also repeatedly calls itself at - // [LIGHT_SAMPLING_FREQUENCY_MILLIS]. - val enqueueLightModeBundle: Runnable = object : Runnable { - override fun run() { - if (DEBUG) Log.d(TAG, "enqueueing a light mode bundle") - - bundlesQueueLightMode.add(ArrayList()) - - executor.executeDelayed(dequeueLightModeBundle, lightSamplingSpanMillis.toLong()) - executor.executeDelayed(this, lightSamplingFrequencyMillis.toLong()) - } - } - - // Creates a new bundle that collects light sensor events for calculating the dark mode claim, - // and adds it to the end of the queue. It schedules a call to dequeue this bundle after - // [DARK_SAMPLING_SPAN_MILLIS]. Once started, it also repeatedly calls itself at - // [DARK_SAMPLING_FREQUENCY_MILLIS]. - val enqueueDarkModeBundle: Runnable = object : Runnable { - override fun run() { - if (DEBUG) Log.d(TAG, "enqueueing a dark mode bundle") - - bundlesQueueDarkMode.add(ArrayList()) - - executor.executeDelayed(dequeueDarkModeBundle, darkSamplingSpanMillis.toLong()) - executor.executeDelayed(this, darkSamplingFrequencyMillis.toLong()) - } - } - - // Collects the oldest bundle from the light mode bundles queue, and as a result triggering a - // calculation of the light mode claim. - val dequeueLightModeBundle: Runnable = object : Runnable { - override fun run() { - if (bundlesQueueLightMode.isEmpty()) return - - bundleLightMode = bundlesQueueLightMode.removeAt(0) - - if (DEBUG) Log.d(TAG, "dequeued a light mode bundle of size ${bundleLightMode.size}") - } - } - - // Collects the oldest bundle from the dark mode bundles queue, and as a result triggering a - // calculation of the dark mode claim. - val dequeueDarkModeBundle: Runnable = object : Runnable { - override fun run() { - if (bundlesQueueDarkMode.isEmpty()) return - - bundleDarkMode = bundlesQueueDarkMode.removeAt(0) - - if (DEBUG) Log.d(TAG, "dequeued a dark mode bundle of size ${bundleDarkMode.size}") - } - } - - /** - * Start the algorithm. - * - * @param callback callback that gets triggered when the ambient light mode changes. - */ - override fun start(callback: AmbientLightModeMonitor.Callback?) { - if (DEBUG) Log.d(TAG, "start algorithm") - - if (callback == null) { - if (DEBUG) Log.w(TAG, "callback is null") - return - } - - if (this.callback != null) { - if (DEBUG) Log.w(TAG, "already started") - return - } - - this.callback = callback - - executor.execute(enqueueLightModeBundle) - executor.execute(enqueueDarkModeBundle) - } - - /** - * Stop the algorithm. - */ - override fun stop() { - if (DEBUG) Log.d(TAG, "stop algorithm") - - if (callback == null) { - if (DEBUG) Log.w(TAG, "haven't started") - return - } - - callback = null - - // Resets bundle queues. - bundlesQueueLightMode.clear() - bundlesQueueDarkMode.clear() - - // Resets ambient light mode. - mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED - } - - /** - * Update the light sensor event value. - * - * @param value light sensor update value. - */ - override fun onUpdateLightSensorEvent(value: Float) { - if (callback == null) { - if (DEBUG) Log.w(TAG, "ignore light sensor event because algorithm not started") - return - } - - lightSensorLevel = value - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/idle/dagger/IdleViewComponent.java b/packages/SystemUI/src/com/android/systemui/idle/dagger/IdleViewComponent.java deleted file mode 100644 index 9754b1f009f5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/idle/dagger/IdleViewComponent.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.idle.dagger; - -import com.android.systemui.idle.IdleHostView; -import com.android.systemui.idle.IdleHostViewController; - -import dagger.BindsInstance; -import dagger.Subcomponent; - -/** - * Subcomponent for working with {@link IdleHostView}. - */ -@Subcomponent -public interface IdleViewComponent { - /** Simple factory for {@link Factory}. */ - @Subcomponent.Factory - interface Factory { - IdleViewComponent build(@BindsInstance IdleHostView idleHostView); - } - - /** Builds a {@link IdleHostViewController}. */ - IdleHostViewController getIdleHostViewController(); -} diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 2dff9472be68..c3b4354ebabe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -16,12 +16,7 @@ package com.android.systemui.media.dagger; -import android.content.Context; -import android.os.Handler; -import android.view.WindowManager; - import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaFlags; import com.android.systemui.media.MediaHierarchyManager; @@ -34,11 +29,8 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.MediaTttFlags; import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver; import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.commandline.CommandRegistry; import java.util.Optional; -import java.util.concurrent.Executor; import javax.inject.Named; @@ -101,13 +93,11 @@ public interface MediaModule { @SysUISingleton static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender( MediaTttFlags mediaTttFlags, - CommandQueue commandQueue, - Context context, - WindowManager windowManager) { + Lazy<MediaTttChipControllerSender> controllerSenderLazy) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of(new MediaTttChipControllerSender(commandQueue, context, windowManager)); + return Optional.of(controllerSenderLazy.get()); } /** */ @@ -115,16 +105,11 @@ public interface MediaModule { @SysUISingleton static Optional<MediaTttChipControllerReceiver> providesMediaTttChipControllerReceiver( MediaTttFlags mediaTttFlags, - CommandQueue commandQueue, - Context context, - WindowManager windowManager, - @Main Handler mainHandler) { + Lazy<MediaTttChipControllerReceiver> controllerReceiverLazy) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of( - new MediaTttChipControllerReceiver( - commandQueue, context, windowManager, mainHandler)); + return Optional.of(controllerReceiverLazy.get()); } /** */ @@ -132,14 +117,11 @@ public interface MediaModule { @SysUISingleton static Optional<MediaTttCommandLineHelper> providesMediaTttCommandLineHelper( MediaTttFlags mediaTttFlags, - CommandRegistry commandRegistry, - Context context, - @Main Executor mainExecutor) { + Lazy<MediaTttCommandLineHelper> helperLazy) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of( - new MediaTttCommandLineHelper(commandRegistry, context, mainExecutor)); + return Optional.of(helperLazy.get()); } /** */ @@ -147,13 +129,12 @@ public interface MediaModule { @SysUISingleton static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli( MediaFlags mediaFlags, - CommandRegistry commandRegistry, - Context context + Lazy<MediaMuteAwaitConnectionCli> muteAwaitConnectionCliLazy ) { if (!mediaFlags.areMuteAwaitConnectionsEnabled()) { return Optional.empty(); } - return Optional.of(new MediaMuteAwaitConnectionCli(commandRegistry, context)); + return Optional.of(muteAwaitConnectionCliLazy.get()); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 5b29719d109c..3cd390533132 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -103,6 +103,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } mCheckBox.setVisibility(View.GONE); mStatusIcon.setVisibility(View.GONE); + mContainerLayout.setOnClickListener(null); mTitleText.setTextColor(mController.getColorInactiveItem()); mSeekBar.getProgressDrawable().setColorFilter( new PorterDuffColorFilter(mController.getColorSeekbarProgress(), @@ -151,6 +152,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setSingleLineLayout(getItemTitle(device), true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, false /* showStatus */); + mCheckBox.setOnCheckedChangeListener(null); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(true); mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -169,6 +171,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { initSeekbar(device); mCurrentActivePosition = position; } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { + mCheckBox.setOnCheckedChangeListener(null); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(false); mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -204,7 +207,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { d.setColorFilter(new PorterDuffColorFilter( Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN)); mTitleIcon.setImageDrawable(d); - mContainerLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW)); + mContainerLayout.setOnClickListener(mController::launchBluetoothPairing); } } @@ -241,11 +244,5 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { notifyDataSetChanged(); } } - - private void onItemClick(int customizedItem) { - if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { - mController.launchBluetoothPairing(); - } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 85d179512de9..1f11d0c62bd7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -177,6 +177,9 @@ public abstract class MediaOutputBaseAdapter extends .mutate() : mContext.getDrawable( R.drawable.media_output_item_background) .mutate(); + backgroundDrawable.setColorFilter(new PorterDuffColorFilter( + mController.getColorItemBackground(), + PorterDuff.Mode.SRC_IN)); mItemLayout.setBackground(backgroundDrawable); mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSeekBar.setAlpha(1); @@ -212,8 +215,13 @@ public abstract class MediaOutputBaseAdapter extends mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE); mSeekBar.setAlpha(1); mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); - mItemLayout.setBackground(mContext.getDrawable(R.drawable.media_output_item_background) - .mutate()); + final Drawable backgroundDrawable = mContext.getDrawable( + R.drawable.media_output_item_background) + .mutate(); + backgroundDrawable.setColorFilter(new PorterDuffColorFilter( + mController.getColorItemBackground(), + PorterDuff.Mode.SRC_IN)); + mItemLayout.setBackground(backgroundDrawable); mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE); mTwoLineTitleText.setTranslationY(0); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index e141cccc5b0f..7bc0f5202c91 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -59,6 +59,7 @@ import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; @@ -111,6 +112,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private int mColorInactiveItem; private int mColorSeekbarProgress; private int mColorButtonBackground; + private int mColorItemBackground; @Inject public MediaOutputController(@NonNull Context context, String packageName, @@ -142,6 +144,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { android.R.color.system_accent1_200); mColorButtonBackground = Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_item_background); + mColorItemBackground = Utils.getColorStateListDefaultColor(mContext, + android.R.color.system_accent2_50); } void start(@NonNull Callback cb) { @@ -350,14 +354,16 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { isDarkTheme); if (isDarkTheme) { mColorActiveItem = mCurrentColorScheme.getNeutral1().get(10); - mColorInactiveItem = mCurrentColorScheme.getAccent1().get(2); - mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(3); + mColorInactiveItem = mCurrentColorScheme.getNeutral1().get(10); + mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(2); mColorButtonBackground = mCurrentColorScheme.getAccent1().get(2); + mColorItemBackground = mCurrentColorScheme.getAccent2().get(0); } else { mColorActiveItem = mCurrentColorScheme.getNeutral1().get(10); mColorInactiveItem = mCurrentColorScheme.getAccent1().get(7); mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(3); - mColorButtonBackground = mCurrentColorScheme.getAccent2().get(1); + mColorButtonBackground = mCurrentColorScheme.getAccent1().get(3); + mColorItemBackground = mCurrentColorScheme.getAccent2().get(0); } } @@ -377,6 +383,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { return mColorButtonBackground; } + public int getColorItemBackground() { + return mColorItemBackground; + } + private void buildMediaDevices(List<MediaDevice> devices) { // For the first time building list, to make sure the top device is the connected device. if (mMediaDevices.isEmpty()) { @@ -568,12 +578,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { return false; } - void launchBluetoothPairing() { - // Dismissing a dialog into its touch surface and starting an activity at the same time - // looks bad, so let's make sure the dialog just fades out quickly. - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); + void launchBluetoothPairing(View view) { + ActivityLaunchAnimator.Controller controller = + mDialogLaunchAnimator.createActivityLaunchController(view); + + if (controller == null) { + mCallback.dismissDialog(); + } - mCallback.dismissDialog(); Intent launchIntent = new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -588,10 +600,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { deepLinkIntent.putExtra( Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, PAGE_CONNECTED_DEVICES_KEY); - mActivityStarter.startActivity(deepLinkIntent, true); + mActivityStarter.startActivity(deepLinkIntent, true, controller); return; } - mActivityStarter.startActivity(launchIntent, true); + mActivityStarter.startActivity(launchIntent, true, controller); } void launchMediaOutputGroupDialog(View mediaOutputDialog) { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 4993105fa681..ee2fba09358b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -25,8 +25,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager +import androidx.annotation.VisibleForTesting import com.android.internal.widget.CachingIconView import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.concurrency.DelayableExecutor /** * A superclass controller that provides common functionality for showing chips on the sender device @@ -38,6 +41,7 @@ import com.android.systemui.R abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( internal val context: Context, private val windowManager: WindowManager, + @Main private val mainExecutor: DelayableExecutor, @LayoutRes private val chipLayoutRes: Int ) { /** The window layout parameters we'll use when attaching the view to a window. */ @@ -56,6 +60,9 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( /** The chip view currently being displayed. Null if the chip is not being displayed. */ var chipView: ViewGroup? = null + /** A [Runnable] that, when run, will cancel the pending timeout of the chip. */ + var cancelChipViewTimeout: Runnable? = null + /** * Displays the chip with the current state. * @@ -77,8 +84,11 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( if (oldChipView == null) { windowManager.addView(chipView, windowLayoutParams) } - } + // Cancel and re-set the chip timeout each time we get a new state. + cancelChipViewTimeout?.run() + cancelChipViewTimeout = mainExecutor.executeDelayed(this::removeChip, TIMEOUT_MILLIS) + } /** Hides the chip. */ fun removeChip() { @@ -118,3 +128,5 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and // UpdateMediaTapToTransferReceiverDisplayTest private const val WINDOW_TITLE = "Media Transfer Chip View" +@VisibleForTesting +const val TIMEOUT_MILLIS = 3000L diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 18623c0686fd..214a8888e0b5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -18,7 +18,6 @@ package com.android.systemui.media.taptotransfer.receiver import android.app.StatusBarManager import android.content.Context -import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.media.MediaRoute2Info import android.os.Handler @@ -27,10 +26,10 @@ import android.view.ViewGroup import android.view.WindowManager import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject /** @@ -43,9 +42,10 @@ class MediaTttChipControllerReceiver @Inject constructor( commandQueue: CommandQueue, context: Context, windowManager: WindowManager, + mainExecutor: DelayableExecutor, @Main private val mainHandler: Handler, ) : MediaTttChipControllerCommon<ChipStateReceiver>( - context, windowManager, R.layout.media_ttt_chip_receiver + context, windowManager, mainExecutor, R.layout.media_ttt_chip_receiver ) { private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferReceiverDisplay( diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index da767ea90055..482e604a0635 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -27,8 +27,10 @@ import android.widget.TextView import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject /** @@ -40,8 +42,9 @@ class MediaTttChipControllerSender @Inject constructor( commandQueue: CommandQueue, context: Context, windowManager: WindowManager, + @Main private val mainExecutor: DelayableExecutor, ) : MediaTttChipControllerCommon<ChipStateSender>( - context, windowManager, R.layout.media_ttt_chip + context, windowManager, mainExecutor, R.layout.media_ttt_chip ) { private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferSenderDisplay( diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index d16c019769b0..8b39e5c24ded 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -542,7 +542,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, return mNavigationBarView; } - public View createView(Bundle savedState) { + public View createView(Bundle savedState, boolean initialVisibility) { mFrame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate( R.layout.navigation_bar_window, null); View barView = LayoutInflater.from(mFrame.getContext()).inflate( @@ -550,6 +550,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, barView.addOnAttachStateChangeListener(this); mNavigationBarView = barView.findViewById(R.id.navigation_bar_view); + mNavigationBarView.setVisibility(initialVisibility ? View.VISIBLE : View.INVISIBLE); + if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView); mWindowManager.addView(mFrame, getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index aa1117ca23f7..a049736a53d5 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -59,6 +59,7 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; @@ -85,6 +86,7 @@ public class NavigationBarController implements private final NavigationBar.Factory mNavigationBarFactory; private final DisplayManager mDisplayManager; private final TaskbarDelegate mTaskbarDelegate; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private int mNavMode; @VisibleForTesting boolean mIsTablet; @@ -108,6 +110,7 @@ public class NavigationBarController implements NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, NavigationBar.Factory navigationBarFactory, + StatusBarKeyguardViewManager statusBarKeyguardViewManager, DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, @@ -122,6 +125,7 @@ public class NavigationBarController implements mConfigChanges.applyNewConfig(mContext.getResources()); mNavMode = navigationModeController.addListener(this); mTaskbarDelegate = taskbarDelegate; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, navBarHelper, navigationModeController, sysUiFlagsContainer, dumpManager, autoHideController, lightBarController, pipOptional, @@ -323,7 +327,8 @@ public class NavigationBarController implements mNavigationBars.put(displayId, navBar); - View navigationBarView = navBar.createView(savedState); + boolean navBarVisible = mStatusBarKeyguardViewManager.isNavBarVisible(); + View navigationBarView = navBar.createView(savedState, navBarVisible); navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 919991189676..9ea27634df6a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -263,15 +263,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker // Notify FalsingManager that an intentional gesture has occurred. // TODO(b/186519446): use a different method than isFalseTouch mFalsingManager.isFalseTouch(BACK_GESTURE); - // Only inject back keycodes when ahead-of-time back dispatching is disabled. - if (mBackAnimation == null) { - boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); - boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" - + sendDown + ", up=" + sendUp); - } - } mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x, (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 17f85eedbbc1..3c7933f0d218 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -381,14 +381,17 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca : View.INVISIBLE); mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController); - boolean footerVisible = !mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating + boolean qsPanelVisible = !mQsDisabled && expandVisually; + boolean footerVisible = qsPanelVisible && (expanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard); mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); mQSFooterActionController.setVisible(footerVisible); mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) || (expanded && !mStackScrollerOverscrolling)); - mQSPanelController.setVisibility( - !mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE); + mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE); + if (DEBUG) { + Log.d(TAG, "Footer: " + footerVisible + ", QS Panel: " + qsPanelVisible); + } } private boolean isKeyguardState() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index a70f534c8f0a..e088f548a356 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -36,6 +36,7 @@ import com.android.internal.app.MediaRouteDialogPresenter; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -191,10 +192,16 @@ public class CastTile extends QSTileImpl<BooleanState> { mContext, ROUTE_TYPE_REMOTE_DISPLAY, v -> { - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - holder.mDialog.dismiss(); + ActivityLaunchAnimator.Controller controller = + mDialogLaunchAnimator.createActivityLaunchController(v); + + if (controller == null) { + holder.mDialog.dismiss(); + } + mActivityStarter - .postStartActivityDismissingKeyguard(getLongClickIntent(), 0); + .postStartActivityDismissingKeyguard(getLongClickIntent(), 0, + controller); }); holder.init(dialog); SystemUIDialog.setShowForAllUsers(dialog, true); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java index 4fe155cfaeb9..e1d20706c625 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -164,7 +164,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) { mWifiListLayout.setOnClickListener( v -> mInternetDialogController.launchWifiNetworkDetailsSetting( - wifiEntry.getKey())); + wifiEntry.getKey(), v)); return; } mWifiListLayout.setOnClickListener(v -> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 8b6ddb44460b..d1c784457c9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -355,8 +355,8 @@ public class InternetDialog extends SystemUIDialog implements isChecked, false); } }); - mConnectedWifListLayout.setOnClickListener(v -> onClickConnectedWifi()); - mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton()); + mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi); + mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton); mWiFiToggle.setOnCheckedChangeListener( (buttonView, isChecked) -> { if (mWifiManager == null) return; @@ -519,7 +519,7 @@ public class InternetDialog extends SystemUIDialog implements if (TextUtils.isEmpty(mWifiScanNotifyText.getText())) { final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo( AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION, - v -> mInternetDialogController.launchWifiScanningSetting()); + mInternetDialogController::launchWifiScanningSetting); mWifiScanNotifyText.setText(AnnotationLinkSpan.linkify( getContext().getText(R.string.wifi_scan_notify_message), linkInfo)); mWifiScanNotifyText.setMovementMethod(LinkMovementMethod.getInstance()); @@ -527,15 +527,16 @@ public class InternetDialog extends SystemUIDialog implements mWifiScanNotifyLayout.setVisibility(View.VISIBLE); } - void onClickConnectedWifi() { + void onClickConnectedWifi(View view) { if (mConnectedWifiEntry == null) { return; } - mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey()); + mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey(), + view); } - void onClickSeeMoreButton() { - mInternetDialogController.launchNetworkSetting(); + void onClickSeeMoreButton(View view) { + mInternetDialogController.launchNetworkSetting(view); } CharSequence getDialogTitleText() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index f89b7a3c0971..b3bc3be852fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -72,6 +72,7 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.settingslib.wifi.WifiUtils; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; @@ -620,36 +621,32 @@ public class InternetDialogController implements AccessPointController.AccessPoi return summary; } - void launchNetworkSetting() { - // Dismissing a dialog into its touch surface and starting an activity at the same time - // looks bad, so let's make sure the dialog just fades out quickly. - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - mCallback.dismissDialog(); + private void startActivity(Intent intent, View view) { + ActivityLaunchAnimator.Controller controller = + mDialogLaunchAnimator.createActivityLaunchController(view); - mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0); + if (controller == null) { + mCallback.dismissDialog(); + } + + mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller); + } + + void launchNetworkSetting(View view) { + startActivity(getSettingsIntent(), view); } - void launchWifiNetworkDetailsSetting(String key) { + void launchWifiNetworkDetailsSetting(String key, View view) { Intent intent = getWifiDetailsSettingsIntent(key); if (intent != null) { - // Dismissing a dialog into its touch surface and starting an activity at the same time - // looks bad, so let's make sure the dialog just fades out quickly. - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - mCallback.dismissDialog(); - - mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + startActivity(intent, view); } } - void launchWifiScanningSetting() { - // Dismissing a dialog into its touch surface and starting an activity at the same time - // looks bad, so let's make sure the dialog just fades out quickly. - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - mCallback.dismissDialog(); - + void launchWifiScanningSetting(View view) { final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + startActivity(intent, view); } void connectCarrierNetwork() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt index 8c8c5c8c0097..88aa734df2b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.user import android.app.Dialog import android.content.Context import android.content.DialogInterface +import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.Intent import android.provider.Settings import android.view.LayoutInflater @@ -84,16 +85,20 @@ class UserSwitchDialogController @VisibleForTesting constructor( setPositiveButton(R.string.quick_settings_done) { _, _ -> uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE) } - setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ -> + setNeutralButton(R.string.quick_settings_more_user_settings, { _, _ -> if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS) + val controller = dialogLaunchAnimator.createActivityLaunchController( + getButton(BUTTON_NEUTRAL)) + + if (controller == null) { + dismiss() + } + activityStarter.postStartActivityDismissingKeyguard( - USER_SETTINGS_INTENT, - 0 - ) + USER_SETTINGS_INTENT, 0, controller) } - } + }, false /* dismissOnClick */) val gridFrame = LayoutInflater.from(this.context) .inflate(R.layout.qs_user_dialog_content, null) setView(gridFrame) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index f9827905b69a..4a9a1f1f055d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -263,22 +263,29 @@ public class ScreenshotView extends FrameLayout implements inoutInfo.touchableRegion.set(getTouchRegion(true)); } - private Region getTouchRegion(boolean includeScrim) { - Region touchRegion = new Region(); + private Region getSwipeRegion() { + Region swipeRegion = new Region(); final Rect tmpRect = new Rect(); mScreenshotPreview.getBoundsOnScreen(tmpRect); tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP), (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP)); - touchRegion.op(tmpRect, Region.Op.UNION); + swipeRegion.op(tmpRect, Region.Op.UNION); mActionsContainerBackground.getBoundsOnScreen(tmpRect); tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP), (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP)); - touchRegion.op(tmpRect, Region.Op.UNION); + swipeRegion.op(tmpRect, Region.Op.UNION); mDismissButton.getBoundsOnScreen(tmpRect); - touchRegion.op(tmpRect, Region.Op.UNION); + swipeRegion.op(tmpRect, Region.Op.UNION); + + return swipeRegion; + } + + private Region getTouchRegion(boolean includeScrim) { + Region touchRegion = getSwipeRegion(); if (includeScrim && mScrollingScrim.getVisibility() == View.VISIBLE) { + final Rect tmpRect = new Rect(); mScrollingScrim.getBoundsOnScreen(tmpRect); touchRegion.op(tmpRect, Region.Op.UNION); } @@ -328,7 +335,7 @@ public class ScreenshotView extends FrameLayout implements @Override // ViewGroup public boolean onInterceptTouchEvent(MotionEvent ev) { // scrolling scrim should not be swipeable; return early if we're on the scrim - if (!getTouchRegion(false).contains((int) ev.getRawX(), (int) ev.getRawY())) { + if (!getSwipeRegion().contains((int) ev.getRawX(), (int) ev.getRawY())) { return false; } // always pass through the down event so the swipe handler knows the initial state diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index b312ce20b313..8366bddaab9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -67,7 +67,7 @@ class LockscreenShadeTransitionController @Inject constructor( wakefulnessLifecycle: WakefulnessLifecycle, configurationController: ConfigurationController, falsingManager: FalsingManager, - dumpManager: DumpManager, + dumpManager: DumpManager ) : Dumpable { private var pulseHeight: Float = 0f private var useSplitShade: Boolean = false @@ -363,6 +363,7 @@ class LockscreenShadeTransitionController @Inject constructor( notificationPanelController.setKeyguardOnlyContentAlpha(1.0f - scrimProgress) depthController.transitionToFullShadeProgress = scrimProgress udfpsKeyguardViewController?.setTransitionToFullShadeProgress(scrimProgress) + statusbar.setTransitionToFullShadeProgress(scrimProgress) } private fun setDragDownAmountAnimated( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index fcbe17957d99..02870a382056 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -32,6 +32,7 @@ import android.os.Trace; import android.text.format.DateFormat; import android.util.FloatProperty; import android.util.Log; +import android.view.Choreographer; import android.view.InsetsFlags; import android.view.InsetsVisibilities; import android.view.View; @@ -44,6 +45,7 @@ import androidx.annotation.NonNull; import com.android.internal.annotations.GuardedBy; import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.logging.UiEventLogger; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; @@ -352,8 +354,17 @@ public class StatusBarStateControllerImpl implements } private void beginInteractionJankMonitor() { + final boolean shouldPost = + (mIsDozing && mDozeAmount == 0) || (!mIsDozing && mDozeAmount == 1); if (mInteractionJankMonitor != null && mView != null && mView.isAttachedToWindow()) { - mInteractionJankMonitor.begin(mView, getCujType()); + if (shouldPost) { + Choreographer.getInstance().postCallback( + Choreographer.CALLBACK_ANIMATION, this::beginInteractionJankMonitor, null); + } else { + Configuration.Builder builder = Configuration.Builder.withView(getCujType(), mView) + .setDeferMonitorForAnimationStart(false); + mInteractionJankMonitor.begin(builder); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index 5361a6716044..2b924a420306 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -34,6 +34,7 @@ import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.wifi.WifiStatusTracker; import com.android.systemui.R; +import com.android.systemui.util.Assert; import java.io.PrintWriter; @@ -190,6 +191,7 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> } private void handleStatusUpdated() { + Assert.isMainThread(); copyWifiStates(); notifyListenersIfNecessary(); } 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 d93c013bd034..a0f8d05ebd0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -135,9 +135,6 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.fragments.FragmentService; -import com.android.systemui.idle.IdleHostView; -import com.android.systemui.idle.IdleHostViewController; -import com.android.systemui.idle.dagger.IdleViewComponent; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.MediaDataManager; @@ -322,7 +319,6 @@ public class NotificationPanelViewController extends PanelViewController private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; - private final IdleViewComponent.Factory mIdleViewComponentFactory; private final FragmentService mFragmentService; private final ScrimController mScrimController; private final PrivacyDotViewController mPrivacyDotViewController; @@ -356,8 +352,6 @@ public class NotificationPanelViewController extends PanelViewController @Nullable private CommunalHostViewController mCommunalViewController; private KeyguardStatusViewController mKeyguardStatusViewController; - @Nullable - private IdleHostViewController mIdleHostViewController; private LockIconViewController mLockIconViewController; private NotificationsQuickSettingsContainer mNotificationContainerParent; private NotificationsQSContainerController mNotificationsQSContainerController; @@ -369,7 +363,6 @@ public class NotificationPanelViewController extends PanelViewController private VelocityTracker mQsVelocityTracker; private boolean mQsTracking; - private IdleHostView mIdleHostView; private CommunalHostView mCommunalView; /** @@ -768,7 +761,6 @@ public class NotificationPanelViewController extends PanelViewController KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory, KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory, CommunalViewComponent.Factory communalViewComponentFactory, - IdleViewComponent.Factory idleViewComponentFactory, LockscreenShadeTransitionController lockscreenShadeTransitionController, NotificationGroupManagerLegacy groupManager, NotificationIconAreaController notificationIconAreaController, @@ -839,7 +831,6 @@ public class NotificationPanelViewController extends PanelViewController mCommunalViewComponentFactory = communalViewComponentFactory; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory; - mIdleViewComponentFactory = idleViewComponentFactory; mDepthController = notificationShadeDepthController; mContentResolver = contentResolver; mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory; @@ -966,7 +957,6 @@ public class NotificationPanelViewController extends PanelViewController private void onFinishInflate() { loadDimens(); mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header); - mIdleHostView = mView.findViewById(R.id.idle_host_view); mCommunalView = mView.findViewById(R.id.communal_host); FrameLayout userAvatarContainer = null; @@ -989,12 +979,6 @@ public class NotificationPanelViewController extends PanelViewController .getKeyguardStatusBarViewController(); mKeyguardStatusBarViewController.init(); - if (mIdleHostView != null) { - IdleViewComponent idleViewComponent = mIdleViewComponentFactory.build(mIdleHostView); - mIdleHostViewController = idleViewComponent.getIdleHostViewController(); - mIdleHostViewController.init(); - } - if (mCommunalView != null) { CommunalViewComponent communalViewComponent = mCommunalViewComponentFactory.build(mCommunalView); @@ -1008,7 +992,6 @@ public class NotificationPanelViewController extends PanelViewController mView.findViewById(R.id.keyguard_status_view), userAvatarContainer, keyguardUserSwitcherView, - mIdleHostView, mCommunalView); NotificationStackScrollLayout stackScrollLayout = mView.findViewById( @@ -1101,7 +1084,6 @@ public class NotificationPanelViewController extends PanelViewController private void updateViewControllers(KeyguardStatusView keyguardStatusView, FrameLayout userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView, - IdleHostView idleHostView, CommunalHostView communalView) { // Re-associate the KeyguardStatusViewController KeyguardStatusViewComponent statusViewComponent = @@ -1109,13 +1091,6 @@ public class NotificationPanelViewController extends PanelViewController mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); - if (idleHostView != null && idleHostView != mIdleHostView) { - mIdleHostView = idleHostView; - IdleViewComponent idleViewComponent = mIdleViewComponentFactory.build(idleHostView); - mIdleHostViewController = idleViewComponent.getIdleHostViewController(); - mIdleHostViewController.init(); - } - if (mKeyguardUserSwitcherController != null) { // Try to close the switcher so that callbacks are triggered if necessary. // Otherwise, NPV can get into a state where some of the views are still hidden @@ -1287,7 +1262,7 @@ public class NotificationPanelViewController extends PanelViewController showKeyguardUserSwitcher /* enabled */); updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView, - keyguardUserSwitcherView, mView.findViewById(R.id.idle_host_view), mCommunalView); + keyguardUserSwitcherView, mCommunalView); // Update keyguard bottom area int index = mView.indexOfChild(mKeyguardBottomArea); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 8e4feb83e72d..82e0e6726437 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -344,6 +344,7 @@ public class StatusBar extends CoreStartable implements private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final DreamOverlayStateController mDreamOverlayStateController; private StatusBarCommandQueueCallbacks mCommandQueueCallbacks; + private float mTransitionToFullShadeProgress = 0f; void onStatusBarWindowStateChanged(@WindowVisibleState int state) { updateBubblesVisibility(); @@ -2583,9 +2584,9 @@ public class StatusBar extends CoreStartable implements return controllerFromStatusBar.get(); } - if (dismissShade && rootView == mNotificationShadeWindowView) { - // We are animating a view in the shade. We have to make sure that we collapse it when - // the animation ends or is cancelled. + if (dismissShade) { + // If the view is not in the status bar, then we are animating a view in the shade. + // We have to make sure that we collapse it when the animation ends or is cancelled. return new StatusBarLaunchAnimatorController(animationController, this, true /* isLaunchForActivity */); } @@ -3777,6 +3778,15 @@ public class StatusBar extends CoreStartable implements updateScrimController(); } + /** + * Set the amount of progress we are currently in if we're transitioning to the full shade. + * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full + * shade. + */ + public void setTransitionToFullShadeProgress(float transitionToFullShadeProgress) { + mTransitionToFullShadeProgress = transitionToFullShadeProgress; + } + @VisibleForTesting public void updateScrimController() { Trace.beginSection("StatusBar#updateScrimController"); @@ -3795,7 +3805,8 @@ public class StatusBar extends CoreStartable implements mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { - if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED) { + if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED + || mTransitionToFullShadeProgress > 0f) { mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); } else { mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index d42a42364f21..11d9c311fb6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -68,10 +68,13 @@ import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.unfold.FoldAodAnimationController; +import com.android.systemui.unfold.SysUIUnfoldComponent; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Objects; +import java.util.Optional; import javax.inject.Inject; @@ -87,7 +90,7 @@ import dagger.Lazy; public class StatusBarKeyguardViewManager implements RemoteInputController.Callback, StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener, PanelExpansionListener, NavigationModeController.ModeChangedListener, - KeyguardViewController { + KeyguardViewController, FoldAodAnimationController.FoldAodAnimationStatus { // When hiding the Keyguard with timing supplied from WindowManager, better be early than late. private static final long HIDE_TIMING_CORRECTION_MS = - 16 * 3; @@ -113,6 +116,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final KeyguardBouncer.Factory mKeyguardBouncerFactory; private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; private final DreamOverlayStateController mDreamOverlayStateController; + @Nullable + private final FoldAodAnimationController mFoldAodAnimationController; private KeyguardMessageAreaController mKeyguardMessageAreaController; private final Lazy<ShadeController> mShadeController; private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() { @@ -186,6 +191,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mPulsing; private boolean mGesturalNav; private boolean mIsDocked; + private boolean mScreenOffAnimationPlaying; protected boolean mFirstUpdate = true; protected boolean mLastShowing; @@ -199,6 +205,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastIsDocked; private boolean mLastPulsing; private int mLastBiometricMode; + private boolean mLastScreenOffAnimationPlaying; private boolean mQsExpanded; private OnDismissAction mAfterKeyguardGoneAction; @@ -246,6 +253,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb NotificationMediaManager notificationMediaManager, KeyguardBouncer.Factory keyguardBouncerFactory, KeyguardMessageAreaController.Factory keyguardMessageAreaFactory, + Optional<SysUIUnfoldComponent> sysUIUnfoldComponent, Lazy<ShadeController> shadeController, LatencyTracker latencyTracker) { mContext = context; @@ -264,6 +272,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardMessageAreaFactory = keyguardMessageAreaFactory; mShadeController = shadeController; mLatencyTracker = latencyTracker; + mFoldAodAnimationController = sysUIUnfoldComponent + .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); } @Override @@ -317,6 +327,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mConfigurationController.addCallback(this); mGesturalNav = QuickStepContract.isGesturalMode( mNavigationModeController.addListener(this)); + if (mFoldAodAnimationController != null) { + mFoldAodAnimationController.addCallback(this); + } if (mDockManager != null) { mDockManager.addListener(mDockEventListener); mIsDocked = mDockManager.isDocked(); @@ -965,6 +978,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() { @Override public void run() { + NavigationBarView view = mStatusBar.getNavigationBarView(); + if (view != null) { + view.setVisibility(View.VISIBLE); + } mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() .show(navigationBars()); } @@ -1019,6 +1036,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastRemoteInputActive = remoteInputActive; mLastDozing = mDozing; mLastPulsing = mPulsing; + mLastScreenOffAnimationPlaying = mScreenOffAnimationPlaying; mLastBiometricMode = mBiometricUnlockController.getMode(); mLastGesturalNav = mGesturalNav; mLastIsDocked = mIsDocked; @@ -1054,14 +1072,15 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb /** * @return Whether the navigation bar should be made visible based on the current state. */ - protected boolean isNavBarVisible() { - int biometricMode = mBiometricUnlockController.getMode(); + public boolean isNavBarVisible() { + boolean isWakeAndUnlockPulsing = mBiometricUnlockController != null + && mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING; boolean keyguardShowing = mShowing && !mOccluded; - boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING; + boolean hideWhileDozing = mDozing && !isWakeAndUnlockPulsing; boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked) && mGesturalNav; - return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing() - || mRemoteInputActive || keyguardWithGestureNav + return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying + || mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav || mGlobalActionsVisible); } @@ -1073,8 +1092,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING; boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing || mLastPulsing && !mLastIsDocked) && mLastGesturalNav; - return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing - || mLastRemoteInputActive || keyguardWithGestureNav + return (!keyguardShowing && !hideWhileDozing && !mLastScreenOffAnimationPlaying + || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav || mLastGlobalActionsVisible); } @@ -1224,6 +1243,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb setDozing(isDozing); } + @Override + public void onFoldToAodAnimationChanged() { + if (mFoldAodAnimationController != null) { + mScreenOffAnimationPlaying = mFoldAodAnimationController.shouldPlayAnimation(); + } + } + /** * Whether qs is currently expanded. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index 2ba37c2ec29f..09fca100749c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -1,5 +1,6 @@ package com.android.systemui.statusbar.phone +import android.view.View import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.LaunchAnimator @@ -12,6 +13,11 @@ class StatusBarLaunchAnimatorController( private val statusBar: StatusBar, private val isLaunchForActivity: Boolean = true ) : ActivityLaunchAnimator.Controller by delegate { + // Always sync the opening window with the shade, given that we draw a hole punch in the shade + // of the same size and position as the opening app to make it visible. + override val openingWindowSyncView: View? + get() = statusBar.notificationShadeWindowView + override fun onIntentStarted(willAnimate: Boolean) { delegate.onIntentStarted(willAnimate) if (!willAnimate) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 6e1ec9ce703a..97225284f208 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -44,6 +44,9 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; +import java.util.ArrayList; +import java.util.List; + /** * Base class for dialogs that should appear over panels and keyguard. * @@ -68,6 +71,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private int mLastConfigurationWidthDp = -1; private int mLastConfigurationHeightDp = -1; + private List<Runnable> mOnCreateRunnables = new ArrayList<>(); + public SystemUIDialog(Context context) { this(context, R.style.Theme_SystemUI_Dialog); } @@ -110,6 +115,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mLastConfigurationWidthDp = config.screenWidthDp; mLastConfigurationHeightDp = config.screenHeightDp; updateWindowSize(); + + for (int i = 0; i < mOnCreateRunnables.size(); i++) { + mOnCreateRunnables.get(i).run(); + } } private void updateWindowSize() { @@ -197,16 +206,67 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh setMessage(mContext.getString(resId)); } + /** + * Set a listener to be invoked when the positive button of the dialog is pressed. The dialog + * will automatically be dismissed when the button is clicked. + */ public void setPositiveButton(int resId, OnClickListener onClick) { - setButton(BUTTON_POSITIVE, mContext.getString(resId), onClick); + setPositiveButton(resId, onClick, true /* dismissOnClick */); + } + + /** + * Set a listener to be invoked when the positive button of the dialog is pressed. The dialog + * will be dismissed when the button is clicked iff {@code dismissOnClick} is true. + */ + public void setPositiveButton(int resId, OnClickListener onClick, boolean dismissOnClick) { + setButton(BUTTON_POSITIVE, resId, onClick, dismissOnClick); } + /** + * Set a listener to be invoked when the negative button of the dialog is pressed. The dialog + * will automatically be dismissed when the button is clicked. + */ public void setNegativeButton(int resId, OnClickListener onClick) { - setButton(BUTTON_NEGATIVE, mContext.getString(resId), onClick); + setNegativeButton(resId, onClick, true /* dismissOnClick */); } + /** + * Set a listener to be invoked when the negative button of the dialog is pressed. The dialog + * will be dismissed when the button is clicked iff {@code dismissOnClick} is true. + */ + public void setNegativeButton(int resId, OnClickListener onClick, boolean dismissOnClick) { + setButton(BUTTON_NEGATIVE, resId, onClick, dismissOnClick); + } + + /** + * Set a listener to be invoked when the neutral button of the dialog is pressed. The dialog + * will automatically be dismissed when the button is clicked. + */ public void setNeutralButton(int resId, OnClickListener onClick) { - setButton(BUTTON_NEUTRAL, mContext.getString(resId), onClick); + setNeutralButton(resId, onClick, true /* dismissOnClick */); + } + + /** + * Set a listener to be invoked when the neutral button of the dialog is pressed. The dialog + * will be dismissed when the button is clicked iff {@code dismissOnClick} is true. + */ + public void setNeutralButton(int resId, OnClickListener onClick, boolean dismissOnClick) { + setButton(BUTTON_NEUTRAL, resId, onClick, dismissOnClick); + } + + private void setButton(int whichButton, int resId, OnClickListener onClick, + boolean dismissOnClick) { + if (dismissOnClick) { + setButton(whichButton, mContext.getString(resId), onClick); + } else { + // Set a null OnClickListener to make sure the button is still created and shown. + setButton(whichButton, mContext.getString(resId), (OnClickListener) null); + + // When the dialog is created, set the click listener but don't dismiss the dialog when + // it is clicked. + mOnCreateRunnables.add(() -> getButton(whichButton).setOnClickListener( + view -> onClick.onClick(this, whichButton))); + } } public static void setShowForAllUsers(Dialog dialog, boolean show) { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index 2e627a872c24..2a9076e8f848 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -48,17 +48,17 @@ constructor( private var pendingScrimReadyCallback: Runnable? = null private var shouldPlayAnimation = false + private var isAnimationPlaying = false + private val statusListeners = arrayListOf<FoldAodAnimationStatus>() private val startAnimationRunnable = Runnable { statusBar.notificationPanelViewController.startFoldToAodAnimation { // End action - isAnimationPlaying = false + setAnimationState(playing = false) } } - private var isAnimationPlaying = false - override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) { this.statusBar = statusBar @@ -71,17 +71,13 @@ constructor( override fun startAnimation(): Boolean = if (alwaysOnEnabled && wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD && - globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0") { - shouldPlayAnimation = true - - isAnimationPlaying = true + globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0" + ) { + setAnimationState(playing = true) statusBar.notificationPanelViewController.prepareFoldToAodAnimation() - - statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged) - true } else { - shouldPlayAnimation = false + setAnimationState(playing = false) false } @@ -91,8 +87,13 @@ constructor( statusBar.notificationPanelViewController.cancelFoldToAodAnimation(); } - shouldPlayAnimation = false - isAnimationPlaying = false + setAnimationState(playing = false) + } + + private fun setAnimationState(playing: Boolean) { + shouldPlayAnimation = playing + isAnimationPlaying = playing + statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged) } /** diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java index 3a270bb77e46..0686071c1718 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java +++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java @@ -19,6 +19,7 @@ package com.android.systemui.user; import android.app.Dialog; import android.content.Context; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.UserManager; @@ -70,10 +71,12 @@ public class UserCreator { } Drawable newUserIcon = userIcon; + Resources res = mContext.getResources(); if (newUserIcon == null) { - newUserIcon = UserIcons.getDefaultUserIcon(mContext.getResources(), user.id, false); + newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false); } - mUserManager.setUserIcon(user.id, UserIcons.convertToBitmap(newUserIcon)); + mUserManager.setUserIcon( + user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon)); userCreationProgressDialog.dismiss(); successCallback.accept(user); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index 589eeb5e0761..d5df9fe0c2e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -46,7 +46,7 @@ import kotlin.concurrent.thread @RunWithLooper class ActivityLaunchAnimatorTest : SysuiTestCase() { private val launchContainer = LinearLayout(mContext) - private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) + private val testLaunchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) @Mock lateinit var callback: ActivityLaunchAnimator.Callback @Mock lateinit var listener: ActivityLaunchAnimator.Listener @Spy private val controller = TestLaunchAnimatorController(launchContainer) @@ -58,7 +58,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { @Before fun setup() { - activityLaunchAnimator = ActivityLaunchAnimator(launchAnimator) + activityLaunchAnimator = ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator) activityLaunchAnimator.callback = callback activityLaunchAnimator.addListener(listener) } @@ -129,13 +129,11 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { @Test fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() { `when`(callback.isOnKeyguard()).thenReturn(true) - val animator = ActivityLaunchAnimator(launchAnimator) - animator.callback = callback val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java) var animationAdapter: RemoteAnimationAdapter? = null - startIntentWithAnimation(animator) { adapter -> + startIntentWithAnimation(activityLaunchAnimator) { adapter -> animationAdapter = adapter ActivityManager.START_DELIVERED_TO_TOP } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index 61e78f5cb2fc..fe2efa5ea30d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -20,8 +20,10 @@ import com.android.systemui.SysuiTestCase import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNotNull +import junit.framework.Assert.assertNull import junit.framework.Assert.assertTrue import org.junit.After +import org.junit.Assert.assertNotEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -43,7 +45,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { @Before fun setUp() { dialogLaunchAnimator = DialogLaunchAnimator( - dreamManager, launchAnimator, forceDisableSynchronization = true) + dreamManager, launchAnimator, isForTesting = true) } @After @@ -92,11 +94,6 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { // Clicking the transparent background should dismiss the dialog. runOnMainThreadAndWaitForIdleSync { - // TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and - // make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in - // Forrest. - dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() - transparentBackground.performClick() } assertFalse(dialog.isShowing) @@ -110,7 +107,6 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertTrue(firstDialog.isShowing) assertTrue(secondDialog.isShowing) runOnMainThreadAndWaitForIdleSync { - dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() dialogLaunchAnimator.dismissStack(secondDialog) } @@ -118,7 +114,63 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertFalse(secondDialog.isShowing) } + @Test + fun testActivityLaunchControllerFromDialog() { + val firstDialog = createAndShowDialog() + val secondDialog = createDialogAndShowFromDialog(firstDialog) + + val controller = + dialogLaunchAnimator.createActivityLaunchController(secondDialog.contentView)!! + + // The dialog shouldn't be dismissable during the animation. + runOnMainThreadAndWaitForIdleSync { + controller.onLaunchAnimationStart(isExpandingFullyAbove = true) + secondDialog.dismiss() + } + assertTrue(secondDialog.isShowing) + + // Both dialogs should be dismissed at the end of the animation. + runOnMainThreadAndWaitForIdleSync { + controller.onLaunchAnimationEnd(isExpandingFullyAbove = true) + } + assertFalse(firstDialog.isShowing) + assertFalse(secondDialog.isShowing) + } + + @Test + fun testActivityLaunchFromHiddenDialog() { + val dialog = createAndShowDialog() + runOnMainThreadAndWaitForIdleSync { + dialog.hide() + } + assertNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView)) + } + + @Test + fun testDialogAnimationIsChangedByAnimator() { + // Important: the power menu animation relies on this behavior to know when to animate (see + // http://ag/16774605). + val dialog = runOnMainThreadAndWaitForIdleSync { TestDialog(context) } + dialog.window.setWindowAnimations(0) + assertEquals(0, dialog.window.attributes.windowAnimations) + + val touchSurface = createTouchSurface() + runOnMainThreadAndWaitForIdleSync { + dialogLaunchAnimator.showFromView(dialog, touchSurface) + } + assertNotEquals(0, dialog.window.attributes.windowAnimations) + } + private fun createAndShowDialog(): TestDialog { + val touchSurface = createTouchSurface() + return runOnMainThreadAndWaitForIdleSync { + val dialog = TestDialog(context) + dialogLaunchAnimator.showFromView(dialog, touchSurface) + dialog + } + } + + private fun createTouchSurface(): View { return runOnMainThreadAndWaitForIdleSync { val touchSurfaceRoot = LinearLayout(context) val touchSurface = View(context) @@ -129,9 +181,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { ViewUtils.attachView(touchSurfaceRoot) attachedViews.add(touchSurfaceRoot) - val dialog = TestDialog(context) - dialogLaunchAnimator.showFromView(dialog, touchSurface) - dialog + touchSurface } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java deleted file mode 100644 index 500205c49ad2..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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.communal; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; -import android.net.wifi.WifiInfo; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.communal.conditions.CommunalTrustedNetworkCondition; -import com.android.systemui.util.settings.FakeSettings; -import com.android.systemui.utils.os.FakeHandler; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class CommunalTrustedNetworkConditionTest extends SysuiTestCase { - @Mock private ConnectivityManager mConnectivityManager; - - @Captor private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor; - - private final Handler mHandler = new FakeHandler(Looper.getMainLooper()); - private CommunalTrustedNetworkCondition mCondition; - - private final String mTrustedWifi1 = "wifi-1"; - private final String mTrustedWifi2 = "wifi-2"; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - final FakeSettings secureSettings = new FakeSettings(); - secureSettings.putStringForUser(Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS, - mTrustedWifi1 + CommunalTrustedNetworkCondition.SETTINGS_STRING_DELIMINATOR - + mTrustedWifi2, UserHandle.USER_SYSTEM); - mCondition = new CommunalTrustedNetworkCondition(mHandler, mConnectivityManager, - secureSettings); - } - - @Test - public void updateCallback_connectedToTrustedNetwork_reportsTrue() { - final CommunalTrustedNetworkCondition.Callback callback = - mock(CommunalTrustedNetworkCondition.Callback.class); - mCondition.addCallback(callback); - - final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback(); - - // Connected to trusted Wi-Fi network. - final Network network = mock(Network.class); - networkCallback.onAvailable(network); - networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1)); - - // Verifies that the callback is triggered. - verify(callback).onConditionChanged(mCondition); - assertThat(mCondition.isConditionMet()).isTrue(); - } - - @Test - public void updateCallback_switchedToAnotherTrustedNetwork_reportsNothing() { - final CommunalTrustedNetworkCondition.Callback callback = - mock(CommunalTrustedNetworkCondition.Callback.class); - mCondition.addCallback(callback); - - final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback(); - - // Connected to a trusted Wi-Fi network. - final Network network = mock(Network.class); - networkCallback.onAvailable(network); - networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1)); - clearInvocations(callback); - - // Connected to another trusted Wi-Fi network. - networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi2)); - - // Verifies that the callback is not triggered. - verify(callback, never()).onConditionChanged(eq(mCondition)); - } - - @Test - public void updateCallback_connectedToNonTrustedNetwork_reportsFalse() { - final CommunalTrustedNetworkCondition.Callback callback = - mock(CommunalTrustedNetworkCondition.Callback.class); - mCondition.addCallback(callback); - - final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback(); - - // Connected to trusted Wi-Fi network. - final Network network = mock(Network.class); - networkCallback.onAvailable(network); - networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1)); - - Mockito.clearInvocations(callback); - // Connected to non-trusted Wi-Fi network. - networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities("random-wifi")); - - // Verifies that the callback is triggered. - verify(callback).onConditionChanged(mCondition); - assertThat(mCondition.isConditionMet()).isFalse(); - } - - @Test - public void updateCallback_disconnectedFromNetwork_reportsFalse() { - final CommunalTrustedNetworkCondition.Callback callback = - mock(CommunalTrustedNetworkCondition.Callback.class); - mCondition.addCallback(callback); - - final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback(); - - // Connected to Wi-Fi. - final Network network = mock(Network.class); - networkCallback.onAvailable(network); - networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1)); - clearInvocations(callback); - - // Disconnected from Wi-Fi. - networkCallback.onLost(network); - - // Verifies that the callback is triggered. - verify(callback).onConditionChanged(mCondition); - assertThat(mCondition.isConditionMet()).isFalse(); - } - - // Captures and returns the network callback, assuming it is registered with the connectivity - // manager. - private ConnectivityManager.NetworkCallback captureNetworkCallback() { - verify(mConnectivityManager).registerNetworkCallback(any(NetworkRequest.class), - mNetworkCallbackCaptor.capture()); - return mNetworkCallbackCaptor.getValue(); - } - - private NetworkCapabilities fakeNetworkCapabilities(String ssid) { - final NetworkCapabilities networkCapabilities = mock(NetworkCapabilities.class); - final WifiInfo wifiInfo = mock(WifiInfo.class); - when(wifiInfo.getSSID()).thenReturn(ssid); - when(networkCapabilities.getTransportInfo()).thenReturn(wifiInfo); - return networkCapabilities; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt index 47ab17dd7ed0..d3c465dab438 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt @@ -23,22 +23,24 @@ import android.graphics.drawable.GradientDrawable import android.graphics.drawable.Icon import android.service.controls.Control import android.service.controls.DeviceTypes +import android.service.controls.templates.ControlTemplate import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.R -import com.android.systemui.controls.controller.ControlsController -import com.android.systemui.util.time.FakeSystemClock -import org.junit.runner.RunWith import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.controls.controller.ControlsController import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.mock @SmallTest @@ -49,11 +51,12 @@ class ControlViewHolderTest : SysuiTestCase() { private val clock = FakeSystemClock() private lateinit var cvh: ControlViewHolder + private lateinit var baseLayout: ViewGroup @Before fun setUp() { TestableLooper.get(this).runWithLooper { - val baseLayout = LayoutInflater.from(mContext).inflate( + baseLayout = LayoutInflater.from(mContext).inflate( R.layout.controls_base_item, null, false) as ViewGroup cvh = ControlViewHolder( @@ -106,6 +109,25 @@ class ControlViewHolderTest : SysuiTestCase() { assertThat(cvh.icon.imageTintList).isEqualTo(customIconTintList) } + + @Test + fun chevronIcon() { + val control = Control.StatefulBuilder(CONTROL_ID, mock(PendingIntent::class.java)) + .setStatus(Control.STATUS_OK) + .setControlTemplate(ControlTemplate.NO_TEMPLATE) + .build() + val cws = ControlWithState( + ComponentName.createRelative("pkg", "cls"), + ControlInfo( + CONTROL_ID, CONTROL_TITLE, "subtitle", DeviceTypes.TYPE_AIR_FRESHENER + ), + control + ) + cvh.bindData(cws, false) + val chevronIcon = baseLayout.findViewById<View>(R.id.chevron_icon) + + assertThat(chevronIcon.visibility).isEqualTo(View.VISIBLE) + } } private const val CONTROL_ID = "CONTROL_ID" diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index 8078b6c8bda4..56844292ce4a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -42,12 +42,14 @@ import static org.mockito.Mockito.when; import android.hardware.display.AmbientDisplayConfiguration; import android.testing.AndroidTestingRunner; import android.testing.UiThreadTest; +import android.view.Display; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.wakelock.WakeLockFake; @@ -444,4 +446,20 @@ public class DozeMachineTest extends SysuiTestCase { assertTrue(mServiceFake.requestedWakeup); } + + @Test + public void testDozePulsing_displayRequiresBlanking_screenState() { + DozeParameters dozeParameters = mock(DozeParameters.class); + when(dozeParameters.getDisplayNeedsBlanking()).thenReturn(true); + + assertEquals(Display.STATE_OFF, DOZE_REQUEST_PULSE.screenState(dozeParameters)); + } + + @Test + public void testDozePulsing_displayDoesNotRequireBlanking_screenState() { + DozeParameters dozeParameters = mock(DozeParameters.class); + when(dozeParameters.getDisplayNeedsBlanking()).thenReturn(false); + + assertEquals(Display.STATE_ON, DOZE_REQUEST_PULSE.screenState(dozeParameters)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt deleted file mode 100644 index 98b9252ff52b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.idle - -import android.hardware.Sensor -import android.hardware.SensorEventListener -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.util.sensors.AsyncSensorManager -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.mock -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.any -import org.mockito.Mockito.eq -import org.mockito.Mockito.never -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class AmbientLightModeMonitorTest : SysuiTestCase() { - @Mock private lateinit var sensorManager: AsyncSensorManager - @Mock private lateinit var sensor: Sensor - @Mock private lateinit var algorithm: AmbientLightModeMonitor.DebounceAlgorithm - - private lateinit var ambientLightModeMonitor: AmbientLightModeMonitor - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - `when`(sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(sensor) - - ambientLightModeMonitor = AmbientLightModeMonitor(algorithm, sensorManager) - } - - @Test - fun shouldRegisterSensorEventListenerOnStart() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - ambientLightModeMonitor.start(callback) - - verify(sensorManager).registerListener(any(), eq(sensor), anyInt()) - } - - @Test - fun shouldUnregisterSensorEventListenerOnStop() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - ambientLightModeMonitor.start(callback) - - val sensorEventListener = captureSensorEventListener() - - ambientLightModeMonitor.stop() - - verify(sensorManager).unregisterListener(eq(sensorEventListener)) - } - - @Test - fun shouldStartDebounceAlgorithmOnStart() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - ambientLightModeMonitor.start(callback) - - verify(algorithm).start(eq(callback)) - } - - @Test - fun shouldStopDebounceAlgorithmOnStop() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - ambientLightModeMonitor.start(callback) - ambientLightModeMonitor.stop() - - verify(algorithm).stop() - } - - @Test - fun shouldNotRegisterForSensorUpdatesIfSensorNotAvailable() { - `when`(sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null) - val ambientLightModeMonitor = AmbientLightModeMonitor(algorithm, sensorManager) - - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - ambientLightModeMonitor.start(callback) - - verify(sensorManager, never()).registerListener(any(), any(Sensor::class.java), anyInt()) - } - - // Captures [SensorEventListener], assuming it has been registered with [sensorManager]. - private fun captureSensorEventListener(): SensorEventListener { - val captor = ArgumentCaptor.forClass(SensorEventListener::class.java) - verify(sensorManager).registerListener(captor.capture(), any(), anyInt()) - return captor.value - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java deleted file mode 100644 index 3c24a3adb64c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.idle; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.Resources; -import android.os.PowerManager; -import android.testing.AndroidTestingRunner; -import android.view.View; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import javax.inject.Provider; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class IdleHostViewControllerTest extends SysuiTestCase { - @Mock private BroadcastDispatcher mBroadcastDispatcher; - @Mock private PowerManager mPowerManager; - @Mock private IdleHostView mIdleHostView; - @Mock private Resources mResources; - @Mock private Provider<View> mViewProvider; - @Mock private KeyguardStateController mKeyguardStateController; - @Mock private StatusBarStateController mStatusBarStateController; - @Mock private AmbientLightModeMonitor mAmbientLightModeMonitor; - - private KeyguardStateController.Callback mKeyguardStateCallback; - private StatusBarStateController.StateListener mStatusBarStateListener; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - when(mResources.getBoolean(R.bool.config_enableIdleMode)).thenReturn(true); - when(mStatusBarStateController.isDozing()).thenReturn(false); - - final IdleHostViewController controller = new IdleHostViewController(mBroadcastDispatcher, - mPowerManager, mIdleHostView, mResources, mViewProvider, mKeyguardStateController, - mStatusBarStateController, mAmbientLightModeMonitor); - controller.init(); - controller.onViewAttached(); - - // Captures keyguard state controller callback. - ArgumentCaptor<KeyguardStateController.Callback> keyguardStateCallbackCaptor = - ArgumentCaptor.forClass(KeyguardStateController.Callback.class); - verify(mKeyguardStateController).addCallback(keyguardStateCallbackCaptor.capture()); - mKeyguardStateCallback = keyguardStateCallbackCaptor.getValue(); - - // Captures status bar state listener. - ArgumentCaptor<StatusBarStateController.StateListener> statusBarStateListenerCaptor = - ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); - verify(mStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture()); - mStatusBarStateListener = statusBarStateListenerCaptor.getValue(); - } - - @Test - public void testStartDozingWhenLowLight() { - // Keyguard showing. - when(mKeyguardStateController.isShowing()).thenReturn(true); - mKeyguardStateCallback.onKeyguardShowingChanged(); - - // Regular ambient lighting. - final AmbientLightModeMonitor.Callback lightMonitorCallback = - captureAmbientLightModeMonitorCallback(); - lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT); - - // Verifies it doesn't go to sleep yet. - verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt()); - - // Ambient lighting becomes dim. - lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); - - // Verifies it goes to sleep. - verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt()); - } - - @Test - public void testWakeUpWhenRegularLight() { - // Keyguard showing. - when(mKeyguardStateController.isShowing()).thenReturn(true); - mKeyguardStateCallback.onKeyguardShowingChanged(); - - // In low light / dozing. - final AmbientLightModeMonitor.Callback lightMonitorCallback = - captureAmbientLightModeMonitorCallback(); - lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK); - mStatusBarStateListener.onDozingChanged(true /*isDozing*/); - - // Regular ambient lighting. - lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT); - - // Verifies it wakes up from sleep. - verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString()); - } - - // Captures [AmbientLightModeMonitor.Callback] assuming that the ambient light mode monitor - // has been started. - private AmbientLightModeMonitor.Callback captureAmbientLightModeMonitorCallback() { - ArgumentCaptor<AmbientLightModeMonitor.Callback> captor = - ArgumentCaptor.forClass(AmbientLightModeMonitor.Callback.class); - verify(mAmbientLightModeMonitor).start(captor.capture()); - return captor.getValue(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/idle/LightSensorDebounceAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/idle/LightSensorDebounceAlgorithmTest.kt deleted file mode 100644 index ebc73454f59c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/idle/LightSensorDebounceAlgorithmTest.kt +++ /dev/null @@ -1,358 +0,0 @@ -/* - * 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.idle - -import android.content.res.Resources -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.reset -import org.mockito.Mockito.`when` - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class LightSensorDebounceAlgorithmTest : SysuiTestCase() { - @Mock private lateinit var resources: Resources - - private val systemClock = FakeSystemClock() - private val executor = FakeExecutor(systemClock) - - private lateinit var algorithm: LightSensorEventsDebounceAlgorithm - - private val mockLightModeThreshold = 5 - private val mockDarkModeThreshold = 2 - private val mockLightModeSpan = 100 - private val mockDarkModeSpan = 50 - private val mockLightModeFrequency = 10 - private val mockDarkModeFrequency = 5 - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - `when`(resources.getInteger(R.integer.config_ambientLightModeThreshold)) - .thenReturn(mockLightModeThreshold) - `when`(resources.getInteger(R.integer.config_ambientDarkModeThreshold)) - .thenReturn(mockDarkModeThreshold) - `when`(resources.getInteger(R.integer.config_ambientLightModeSamplingSpanMillis)) - .thenReturn(mockLightModeSpan) - `when`(resources.getInteger(R.integer.config_ambientDarkModeSamplingSpanMillis)) - .thenReturn(mockDarkModeSpan) - `when`(resources.getInteger(R.integer.config_ambientLightModeSamplingFrequencyMillis)) - .thenReturn(mockLightModeFrequency) - `when`(resources.getInteger(R.integer.config_ambientDarkModeSamplingFrequencyMillis)) - .thenReturn(mockDarkModeFrequency) - - algorithm = LightSensorEventsDebounceAlgorithm(executor, resources) - } - - @Test - fun shouldOnlyTriggerCallbackWhenValueChanges() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - algorithm.start(callback) - - // Light mode, should trigger callback. - algorithm.mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT - verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT) - reset(callback) - - // Light mode again, should NOT trigger callback. - algorithm.mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT - verify(callback, never()).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT) - reset(callback) - - // Dark mode, should trigger callback. - algorithm.mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK - verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK) - reset(callback) - - // Dark mode again, should not trigger callback. - algorithm.mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK - verify(callback, never()).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK) - } - - @Test - fun shouldReportUndecidedWhenNeitherLightNorDarkClaimIsTrue() { - algorithm.isDarkMode = false - algorithm.isLightMode = false - - assertThat(algorithm.mode).isEqualTo( - AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED) - } - - @Test - fun shouldReportDarkModeAsLongAsDarkModeClaimIsTrue() { - algorithm.isDarkMode = true - algorithm.isLightMode = false - - assertThat(algorithm.mode).isEqualTo( - AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK) - - algorithm.isLightMode = true - assertThat(algorithm.mode).isEqualTo( - AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK) - } - - @Test - fun shouldReportLightModeWhenLightModeClaimIsTrueAndDarkModeClaimIsFalse() { - algorithm.isLightMode = true - algorithm.isDarkMode = false - - assertThat(algorithm.mode).isEqualTo( - AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT) - } - - @Test - fun shouldSetIsLightModeToTrueWhenBundleAverageIsGreaterThanThreshold() { - // Note: [mockLightModeThreshold] is 5.0. - algorithm.bundleAverageLightMode = 5.1 - assertThat(algorithm.isLightMode).isTrue() - - algorithm.bundleAverageLightMode = 10.0 - assertThat(algorithm.isLightMode).isTrue() - - algorithm.bundleAverageLightMode = 20.0 - assertThat(algorithm.isLightMode).isTrue() - - algorithm.bundleAverageLightMode = 5.0 - assertThat(algorithm.isLightMode).isFalse() - - algorithm.bundleAverageLightMode = 3.0 - assertThat(algorithm.isLightMode).isFalse() - - algorithm.bundleAverageLightMode = 0.0 - assertThat(algorithm.isLightMode).isFalse() - } - - @Test - fun shouldSetIsDarkModeToTrueWhenBundleAverageIsLessThanThreshold() { - // Note: [mockDarkModeThreshold] is 2.0. - algorithm.bundleAverageDarkMode = 1.9 - assertThat(algorithm.isDarkMode).isTrue() - - algorithm.bundleAverageDarkMode = 1.0 - assertThat(algorithm.isDarkMode).isTrue() - - algorithm.bundleAverageDarkMode = 0.0 - assertThat(algorithm.isDarkMode).isTrue() - - algorithm.bundleAverageDarkMode = 2.0 - assertThat(algorithm.isDarkMode).isFalse() - - algorithm.bundleAverageDarkMode = 3.0 - assertThat(algorithm.isDarkMode).isFalse() - - algorithm.bundleAverageDarkMode = 10.0 - assertThat(algorithm.isDarkMode).isFalse() - } - - @Test - fun shouldCorrectlyCalculateAverageFromABundle() { - // For light mode. - algorithm.bundleLightMode = arrayListOf(1.0f, 3.0f, 5.0f, 7.0f) - assertThat(algorithm.bundleAverageLightMode).isEqualTo(4.0) - - algorithm.bundleLightMode = arrayListOf(2.0f, 4.0f, 6.0f, 8.0f) - assertThat(algorithm.bundleAverageLightMode).isEqualTo(5.0) - - // For dark mode. - algorithm.bundleDarkMode = arrayListOf(1.0f, 3.0f, 5.0f, 7.0f, 9.0f) - assertThat(algorithm.bundleAverageDarkMode).isEqualTo(5.0) - - algorithm.bundleDarkMode = arrayListOf(2.0f, 4.0f, 6.0f, 8.0f, 10.0f) - assertThat(algorithm.bundleAverageDarkMode).isEqualTo(6.0) - } - - @Test - fun shouldAddSensorEventUpdatesToBundles() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - // On start() one bundle is created for light and dark mode each. - algorithm.start(callback) - executor.runAllReady() - - // Add 1 more bundle to queue for each mode. - algorithm.bundlesQueueLightMode.add(ArrayList()) - algorithm.bundlesQueueDarkMode.add(ArrayList()) - - algorithm.onUpdateLightSensorEvent(1.0f) - algorithm.onUpdateLightSensorEvent(1.0f) - algorithm.onUpdateLightSensorEvent(2.0f) - algorithm.onUpdateLightSensorEvent(3.0f) - algorithm.onUpdateLightSensorEvent(4.0f) - - val expectedValues = listOf(1.0f, 1.0f, 2.0f, 3.0f, 4.0f) - - assertBundleContainsAll(algorithm.bundlesQueueLightMode[0], expectedValues) - assertBundleContainsAll(algorithm.bundlesQueueLightMode[1], expectedValues) - assertBundleContainsAll(algorithm.bundlesQueueDarkMode[0], expectedValues) - assertBundleContainsAll(algorithm.bundlesQueueDarkMode[1], expectedValues) - } - - @Test - fun shouldCorrectlyEnqueueLightModeBundles() { - assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(0) - - algorithm.enqueueLightModeBundle.run() - assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(1) - - algorithm.enqueueLightModeBundle.run() - assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(2) - - algorithm.enqueueLightModeBundle.run() - assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(3) - - // Verifies dark mode bundles queue is not impacted. - assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(0) - } - - @Test - fun shouldCorrectlyEnqueueDarkModeBundles() { - assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(0) - - algorithm.enqueueDarkModeBundle.run() - assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(1) - - algorithm.enqueueDarkModeBundle.run() - assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(2) - - algorithm.enqueueDarkModeBundle.run() - assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(3) - - // Verifies light mode bundles queue is not impacted. - assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(0) - } - - @Test - fun shouldCorrectlyDequeueLightModeBundles() { - // Sets up the light mode bundles queue. - val bundle1 = arrayListOf(1.0f, 3.0f, 6.0f, 9.0f) - val bundle2 = arrayListOf(5.0f, 10f) - val bundle3 = arrayListOf(2.0f, 4.0f) - algorithm.bundlesQueueLightMode.add(bundle1) - algorithm.bundlesQueueLightMode.add(bundle2) - algorithm.bundlesQueueLightMode.add(bundle3) - - // The committed bundle should be the first one in queue. - algorithm.dequeueLightModeBundle.run() - assertBundleContainsAll(algorithm.bundleLightMode, bundle1) - - algorithm.dequeueLightModeBundle.run() - assertBundleContainsAll(algorithm.bundleLightMode, bundle2) - - algorithm.dequeueLightModeBundle.run() - assertBundleContainsAll(algorithm.bundleLightMode, bundle3) - - // Verifies that the dark mode bundle is not impacted. - assertBundleContainsAll(algorithm.bundleDarkMode, listOf()) - } - - @Test - fun shouldCorrectlyDequeueDarkModeBundles() { - // Sets up the dark mode bundles queue. - val bundle1 = arrayListOf(2.0f, 4.0f) - val bundle2 = arrayListOf(5.0f, 10f) - val bundle3 = arrayListOf(1.0f, 3.0f, 6.0f, 9.0f) - algorithm.bundlesQueueDarkMode.add(bundle1) - algorithm.bundlesQueueDarkMode.add(bundle2) - algorithm.bundlesQueueDarkMode.add(bundle3) - - // The committed bundle should be the first one in queue. - algorithm.dequeueDarkModeBundle.run() - assertBundleContainsAll(algorithm.bundleDarkMode, bundle1) - - algorithm.dequeueDarkModeBundle.run() - assertBundleContainsAll(algorithm.bundleDarkMode, bundle2) - - algorithm.dequeueDarkModeBundle.run() - assertBundleContainsAll(algorithm.bundleDarkMode, bundle3) - - // Verifies that the light mode bundle is not impacted. - assertBundleContainsAll(algorithm.bundleLightMode, listOf()) - } - - @Test - fun shouldSetLightSensorLevelFromSensorEventUpdates() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - algorithm.start(callback) - - algorithm.onUpdateLightSensorEvent(1.0f) - assertThat(algorithm.lightSensorLevel).isEqualTo(1.0f) - - algorithm.onUpdateLightSensorEvent(10.0f) - assertThat(algorithm.lightSensorLevel).isEqualTo(10.0f) - - algorithm.onUpdateLightSensorEvent(0.0f) - assertThat(algorithm.lightSensorLevel).isEqualTo(0.0f) - } - - @Test - fun shouldRippleFromSensorEventUpdatesDownToAmbientLightMode() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - algorithm.start(callback) - executor.runAllReady() - - // Sensor event updates. - algorithm.onUpdateLightSensorEvent(10.0f) - algorithm.onUpdateLightSensorEvent(15.0f) - algorithm.onUpdateLightSensorEvent(12.0f) - algorithm.onUpdateLightSensorEvent(10.0f) - - // Advances time so both light and dark claims have been calculated. - systemClock.advanceTime((mockLightModeSpan + 1).toLong()) - - // Verifies the callback is triggered the ambient mode has changed LIGHT. - verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT) - } - - @Test - fun shouldRippleFromSensorEventUpdatesDownToAmbientDarkMode() { - val callback = mock(AmbientLightModeMonitor.Callback::class.java) - algorithm.start(callback) - executor.runAllReady() - - // Sensor event updates. - algorithm.onUpdateLightSensorEvent(1.0f) - algorithm.onUpdateLightSensorEvent(0.5f) - algorithm.onUpdateLightSensorEvent(1.2f) - algorithm.onUpdateLightSensorEvent(0.8f) - - // Advances time so both light and dark claims have been calculated. - systemClock.advanceTime((mockLightModeSpan + 1).toLong()) - - // Verifies the callback is triggered the ambient mode has changed DARK. - verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK) - } - - // Asserts that [bundle] contains the same elements as [expected], not necessarily in the same - // order. - private fun assertBundleContainsAll(bundle: ArrayList<Float>, expected: Collection<Float>) { - assertThat(bundle.size).isEqualTo(expected.size) - assertThat(bundle.containsAll(expected)) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 421ae034c054..11326e76b25e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -59,6 +59,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { private MediaDevice mMediaDevice2 = mock(MediaDevice.class); private Icon mIcon = mock(Icon.class); private IconCompat mIconCompat = mock(IconCompat.class); + private View mDialogLaunchView = mock(View.class); private MediaOutputAdapter mMediaOutputAdapter; private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder; @@ -245,7 +246,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); mViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController).launchBluetoothPairing(); + verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index 14afecebd1f6..794bc09715af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -39,7 +39,6 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.verify @@ -49,7 +48,6 @@ import java.io.StringWriter import java.util.concurrent.Executor @SmallTest -@Ignore("b/216286227") class MediaTttCommandLineHelperTest : SysuiTestCase() { private val inlineExecutor = Executor { command -> command.run() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt index f05d621eef3a..ea0a5a42ad21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.media.taptotransfer.common import android.content.Context import android.graphics.drawable.Drawable -import android.graphics.drawable.Icon import android.view.View import android.view.ViewGroup import android.view.WindowManager @@ -26,10 +25,13 @@ import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.Mock @@ -39,10 +41,12 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@Ignore("b/216286227") class MediaTttChipControllerCommonTest : SysuiTestCase() { private lateinit var controllerCommon: MediaTttChipControllerCommon<MediaTttChipState> + private lateinit var fakeClock: FakeSystemClock + private lateinit var fakeExecutor: FakeExecutor + private lateinit var appIconDrawable: Drawable @Mock private lateinit var windowManager: WindowManager @@ -50,8 +54,11 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) - controllerCommon = TestControllerCommon(context, windowManager) + appIconDrawable = context.getDrawable(R.drawable.ic_cake)!! + fakeClock = FakeSystemClock() + fakeExecutor = FakeExecutor(fakeClock) + + controllerCommon = TestControllerCommon(context, windowManager, fakeExecutor) } @Test @@ -71,6 +78,58 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { } @Test + fun displayChip_chipDoesNotDisappearsBeforeTimeout() { + controllerCommon.displayChip(getState()) + reset(windowManager) + + fakeClock.advanceTime(TIMEOUT_MILLIS - 1) + + verify(windowManager, never()).removeView(any()) + } + + @Test + fun displayChip_chipDisappearsAfterTimeout() { + controllerCommon.displayChip(getState()) + reset(windowManager) + + fakeClock.advanceTime(TIMEOUT_MILLIS + 1) + + verify(windowManager).removeView(any()) + } + + @Test + fun displayChip_calledAgainBeforeTimeout_timeoutReset() { + // First, display the chip + controllerCommon.displayChip(getState()) + + // After some time, re-display the chip + val waitTime = 1000L + fakeClock.advanceTime(waitTime) + controllerCommon.displayChip(getState()) + + // Wait until the timeout for the first display would've happened + fakeClock.advanceTime(TIMEOUT_MILLIS - waitTime + 1) + + // Verify we didn't hide the chip + verify(windowManager, never()).removeView(any()) + } + + @Test + fun displayChip_calledAgainBeforeTimeout_eventuallyTimesOut() { + // First, display the chip + controllerCommon.displayChip(getState()) + + // After some time, re-display the chip + fakeClock.advanceTime(1000L) + controllerCommon.displayChip(getState()) + + // Ensure we still hide the chip eventually + fakeClock.advanceTime(TIMEOUT_MILLIS + 1) + + verify(windowManager).removeView(any()) + } + + @Test fun removeChip_chipRemoved() { // First, add the chip controllerCommon.displayChip(getState()) @@ -93,10 +152,10 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { controllerCommon.displayChip(getState()) val chipView = getChipView() - val state = MediaTttChipState(PACKAGE_NAME) + val state = TestChipState(PACKAGE_NAME) controllerCommon.setIcon(state, chipView) - assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription) .isEqualTo(state.getAppName(context)) } @@ -113,13 +172,18 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { inner class TestControllerCommon( context: Context, - windowManager: WindowManager - ) : MediaTttChipControllerCommon<MediaTttChipState>( - context, windowManager, R.layout.media_ttt_chip + windowManager: WindowManager, + @Main mainExecutor: DelayableExecutor, + ) : MediaTttChipControllerCommon<MediaTttChipState>( + context, windowManager, mainExecutor, R.layout.media_ttt_chip ) { override fun updateChipView(chipState: MediaTttChipState, currentChipView: ViewGroup) { } } + + inner class TestChipState(appPackageName: String?) : MediaTttChipState(appPackageName) { + override fun getAppIcon(context: Context) = appIconDrawable + } } private const val PACKAGE_NAME = "com.android.systemui" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 44f691c3f698..117a6c898510 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -17,7 +17,9 @@ package com.android.systemui.media.taptotransfer.receiver import android.app.StatusBarManager -import android.graphics.drawable.Icon +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable import android.media.MediaRoute2Info import android.os.Handler import android.view.View @@ -28,33 +30,54 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@Ignore("b/216286227") class MediaTttChipControllerReceiverTest : SysuiTestCase() { private lateinit var controllerReceiver: MediaTttChipControllerReceiver @Mock + private lateinit var packageManager: PackageManager + @Mock + private lateinit var applicationInfo: ApplicationInfo + @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var commandQueue: CommandQueue private lateinit var commandQueueCallback: CommandQueue.Callbacks + private lateinit var fakeAppIconDrawable: Drawable @Before fun setUp() { MockitoAnnotations.initMocks(this) + + fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! + whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) + whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) + whenever(packageManager.getApplicationInfo( + eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>() + )).thenReturn(applicationInfo) + context.setMockPackageManager(packageManager) + controllerReceiver = MediaTttChipControllerReceiver( - commandQueue, context, windowManager, Handler.getMain()) + commandQueue, + context, + windowManager, + FakeExecutor(FakeSystemClock()), + Handler.getMain() + ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) verify(commandQueue).addCallback(callbackCaptor.capture()) @@ -113,13 +136,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { controllerReceiver.displayChip(state) - assertThat(getChipView().getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) - + assertThat(getChipView().getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) } @Test fun displayChip_hasAppIconDrawable_iconIsDrawable() { - val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) + val drawable = context.getDrawable(R.drawable.ic_alarm)!! val state = ChipStateReceiver(PACKAGE_NAME, drawable, "appName") controllerReceiver.displayChip(state) @@ -133,13 +155,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { controllerReceiver.displayChip(state) - assertThat(getChipView().getAppIconView().contentDescription) - .isEqualTo(state.getAppName(context)) + assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(APP_NAME) } @Test fun displayChip_hasAppName_iconContentDescriptionIsAppNameOverride() { - val appName = "FakeAppName" + val appName = "Override App Name" val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName) controllerReceiver.displayChip(state) @@ -156,6 +177,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) } +private const val APP_NAME = "Fake app name" private const val PACKAGE_NAME = "com.android.systemui" private val routeInfo = MediaRoute2Info.Builder("id", "Test route name") diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index dc39893d421b..b44006429828 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -17,6 +17,9 @@ package com.android.systemui.media.taptotransfer.sender import android.app.StatusBarManager +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable import android.media.MediaRoute2Info import android.view.View import android.view.WindowManager @@ -28,32 +31,50 @@ import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@Ignore("b/216286227") class MediaTttChipControllerSenderTest : SysuiTestCase() { private lateinit var controllerSender: MediaTttChipControllerSender @Mock + private lateinit var packageManager: PackageManager + @Mock + private lateinit var applicationInfo: ApplicationInfo + @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var commandQueue: CommandQueue private lateinit var commandQueueCallback: CommandQueue.Callbacks + private lateinit var fakeAppIconDrawable: Drawable @Before fun setUp() { MockitoAnnotations.initMocks(this) - controllerSender = MediaTttChipControllerSender(commandQueue, context, windowManager) + + fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! + whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) + whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) + whenever(packageManager.getApplicationInfo( + eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>() + )).thenReturn(applicationInfo) + context.setMockPackageManager(packageManager) + + controllerSender = MediaTttChipControllerSender( + commandQueue, context, windowManager, FakeExecutor(FakeSystemClock()) + ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) verify(commandQueue).addCallback(callbackCaptor.capture()) @@ -192,9 +213,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) - assertThat(chipView.getAppIconView().contentDescription) - .isEqualTo(state.getAppName(context)) + assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -207,9 +227,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) - assertThat(chipView.getAppIconView().contentDescription) - .isEqualTo(state.getAppName(context)) + assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -222,9 +241,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) - assertThat(chipView.getAppIconView().contentDescription) - .isEqualTo(state.getAppName(context)) + assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -237,9 +255,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) - assertThat(chipView.getAppIconView().contentDescription) - .isEqualTo(state.getAppName(context)) + assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -252,9 +269,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) - assertThat(chipView.getAppIconView().contentDescription) - .isEqualTo(state.getAppName(context)) + assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) @@ -314,9 +330,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) - assertThat(chipView.getAppIconView().contentDescription) - .isEqualTo(state.getAppName(context)) + assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) @@ -376,9 +391,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) - assertThat(chipView.getAppIconView().contentDescription) - .isEqualTo(state.getAppName(context)) + assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -451,15 +465,15 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { /** Helper method providing default parameters to not clutter up the tests. */ private fun almostCloseToStartCast() = - AlmostCloseToStartCast(PACKAGE_NAME, DEVICE_NAME) + AlmostCloseToStartCast(PACKAGE_NAME, OTHER_DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun almostCloseToEndCast() = - AlmostCloseToEndCast(PACKAGE_NAME, DEVICE_NAME) + AlmostCloseToEndCast(PACKAGE_NAME, OTHER_DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToReceiverTriggered() = - TransferToReceiverTriggered(PACKAGE_NAME, DEVICE_NAME) + TransferToReceiverTriggered(PACKAGE_NAME, OTHER_DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToThisDeviceTriggered() = @@ -468,23 +482,24 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) = TransferToReceiverSucceeded( - PACKAGE_NAME, DEVICE_NAME, undoCallback + PACKAGE_NAME, OTHER_DEVICE_NAME, undoCallback ) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) = TransferToThisDeviceSucceeded( - PACKAGE_NAME, DEVICE_NAME, undoCallback + PACKAGE_NAME, OTHER_DEVICE_NAME, undoCallback ) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferFailed() = TransferFailed(PACKAGE_NAME) } -private const val DEVICE_NAME = "My Tablet" +private const val APP_NAME = "Fake app name" +private const val OTHER_DEVICE_NAME = "My Tablet" private const val PACKAGE_NAME = "com.android.systemui" -private val routeInfo = MediaRoute2Info.Builder("id", "Test Name") +private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME) .addFeature("feature") .setPackageName(PACKAGE_NAME) .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index 5a0604883863..6df56e98783c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -19,6 +19,7 @@ package com.android.systemui.monet; import android.app.WallpaperColors; import android.graphics.Color; import android.testing.AndroidTestingRunner; +import android.util.Log; import androidx.test.filters.SmallTest; @@ -29,7 +30,11 @@ import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -108,7 +113,7 @@ public class ColorSchemeTest extends SysuiTestCase { Style.SPRITZ /* style */); int primaryMid = colorScheme.getAccent1().get(colorScheme.getAccent1().size() / 2); Cam cam = Cam.fromInt(primaryMid); - Assert.assertEquals(cam.getChroma(), 4.0, 1.0); + Assert.assertEquals(cam.getChroma(), 12.0, 1.0); } @Test @@ -128,6 +133,41 @@ public class ColorSchemeTest extends SysuiTestCase { Style.EXPRESSIVE /* style */); int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2); Cam cam = Cam.fromInt(neutralMid); - Assert.assertEquals(cam.getChroma(), 16.0, 1.0); + Assert.assertEquals(cam.getChroma(), 12.0, 1.0); + } + + /** + * Generate xml for SystemPaletteTest#testThemeStyles(). + */ + @Test + public void generateThemeStyles() { + StringBuilder xml = new StringBuilder(); + for (int hue = 0; hue < 360; hue += 60) { + final int sourceColor = Cam.getInt(hue, 50f, 50f); + final String sourceColorHex = Integer.toHexString(sourceColor); + + xml.append(" <theme color=\"").append(sourceColorHex).append("\">\n"); + + for (Style style : Style.values()) { + String styleName = style.name().toLowerCase(); + ColorScheme colorScheme = new ColorScheme(sourceColor, false, style); + xml.append(" <").append(styleName).append(">"); + + List<String> colors = new ArrayList<>(); + for (Stream<Integer> stream: Arrays.asList(colorScheme.getAccent1().stream(), + colorScheme.getAccent2().stream(), + colorScheme.getAccent3().stream(), + colorScheme.getNeutral1().stream(), + colorScheme.getNeutral2().stream())) { + colors.add("ffffff"); + colors.addAll(stream.map(Integer::toHexString).map(s -> s.substring(2)).collect( + Collectors.toList())); + } + xml.append(String.join(",", colors)); + xml.append("</").append(styleName).append(">\n"); + } + xml.append(" </theme>\n"); + } + Log.d("ColorSchemeXml", xml.toString()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 73d2b0bf1a0f..bb42c1277b90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -46,6 +46,7 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; @@ -90,6 +91,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { mock(NavBarHelper.class), mock(TaskbarDelegate.class), mNavigationBarFactory, + mock(StatusBarKeyguardViewManager.class), mock(DumpManager.class), mock(AutoHideController.class), mock(LightBarController.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 090ce436340b..48d38571cf3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -28,6 +28,7 @@ import static android.view.WindowInsets.Type.ime; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS; import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -228,7 +229,8 @@ public class NavigationBarTest extends SysuiTestCase { @Test public void testHomeLongPress() { - mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); + mNavigationBar.onViewAttachedToWindow(mNavigationBar + .createView(null, /* initialVisibility= */ true)); mNavigationBar.onHomeLongClick(mNavigationBar.getView()); verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS); @@ -241,7 +243,8 @@ public class NavigationBarTest extends SysuiTestCase { .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100) .build()); when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true); - mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); + mNavigationBar.onViewAttachedToWindow(mNavigationBar + .createView(null, /* initialVisibility= */ true)); mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain( /*downTime=*/SystemClock.uptimeMillis(), @@ -263,7 +266,8 @@ public class NavigationBarTest extends SysuiTestCase { @Test public void testRegisteredWithDispatcher() { - mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); + mNavigationBar.onViewAttachedToWindow(mNavigationBar + .createView(null, /* initialVisibility= */ true)); verify(mBroadcastDispatcher).registerReceiverWithHandler( any(BroadcastReceiver.class), any(IntentFilter.class), @@ -283,8 +287,8 @@ public class NavigationBarTest extends SysuiTestCase { doReturn(true).when(mockShadeWindowView).isAttachedToWindow(); doNothing().when(defaultNavBar).checkNavBarModes(); doNothing().when(externalNavBar).checkNavBarModes(); - defaultNavBar.createView(null); - externalNavBar.createView(null); + defaultNavBar.createView(null, /* initialVisibility= */ true); + externalNavBar.createView(null, /* initialVisibility= */ true); defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); @@ -318,7 +322,7 @@ public class NavigationBarTest extends SysuiTestCase { doReturn(mockShadeWindowView).when(mStatusBar).getNotificationShadeWindowView(); doReturn(true).when(mockShadeWindowView).isAttachedToWindow(); doNothing().when(mNavigationBar).checkNavBarModes(); - mNavigationBar.createView(null); + mNavigationBar.createView(null, /* initialVisibility= */ true); WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build(); doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets(); @@ -354,7 +358,7 @@ public class NavigationBarTest extends SysuiTestCase { @Test public void testA11yEventAfterDetach() { - View v = mNavigationBar.createView(null); + View v = mNavigationBar.createView(null, /* initialVisibility= */ true); mNavigationBar.onViewAttachedToWindow(v); verify(mNavBarHelper).registerNavTaskStateUpdater(any( NavBarHelper.NavbarTaskbarStateUpdater.class)); @@ -366,6 +370,20 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.updateAccessibilityStateFlags(); } + @Test + public void testCreateView_initiallyVisible_viewIsVisible() { + mNavigationBar.createView(null, /* initialVisibility= */ true); + + assertThat(mNavigationBar.getView().getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void testCreateView_initiallyNotVisible_viewIsNotVisible() { + mNavigationBar.createView(null, /* initialVisibility= */ false); + + assertThat(mNavigationBar.getView().getVisibility()).isEqualTo(View.INVISIBLE); + } + private NavigationBar createNavBar(Context context) { DeviceProvisionedController deviceProvisionedController = mock(DeviceProvisionedController.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 0d6554103dac..a2959e2fb917 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -136,6 +136,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { private LocationController mLocationController; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock + private View mDialogLaunchView; private TestableResources mTestableResources; private InternetDialogController mInternetDialogController; @@ -384,7 +386,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void launchWifiNetworkDetailsSetting_withNoWifiEntryKey_doNothing() { - mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */); + mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */, + mDialogLaunchView); verify(mActivityStarter, never()) .postStartActivityDismissingKeyguard(any(Intent.class), anyInt()); @@ -392,9 +395,11 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void launchWifiNetworkDetailsSetting_withWifiEntryKey_startActivity() { - mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key"); + mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key", + mDialogLaunchView); - verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt()); + verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt(), + any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index ed35dcbbcfab..cf97bdae9af2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -458,7 +458,8 @@ public class InternetDialogTest extends SysuiTestCase { public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() { mSeeAll.performClick(); - verify(mInternetDialogController).launchNetworkSetting(); + verify(mInternetDialogController).launchNetworkSetting( + mDialogView.requireViewById(R.id.see_all_layout)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index 8695b2990b6a..030c65a0576a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.provider.Settings import android.testing.AndroidTestingRunner import android.view.View +import android.widget.Button import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase @@ -63,6 +64,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { @Mock private lateinit var launchView: View @Mock + private lateinit var neutralButton: Button + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator @Mock private lateinit var uiEventLogger: UiEventLogger @@ -130,14 +133,17 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { controller.showDialog(launchView) - verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor)) + verify(dialog) + .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */) + `when`(dialog.getButton(DialogInterface.BUTTON_NEUTRAL)).thenReturn(neutralButton) clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL) verify(activityStarter) .postStartActivityDismissingKeyguard( argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)), - eq(0) + eq(0), + eq(null) ) verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS) } @@ -148,7 +154,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { controller.showDialog(launchView) - verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor)) + verify(dialog) + .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */) clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index 9f152e1e2fe5..b7b3088a9e26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -56,6 +56,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { @Before public void setUp() throws Exception { super.setUp(); + allowTestableLooperAsMainThread(); when(mWifiInfo.makeCopy(anyLong())).thenReturn(mWifiInfo); when(mWifiInfo.isPrimary()).thenReturn(true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index f6eff8207da3..479c27192c55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -100,8 +100,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; -import com.android.systemui.idle.IdleHostViewController; -import com.android.systemui.idle.dagger.IdleViewComponent; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.MediaDataManager; @@ -266,12 +264,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private KeyguardUserSwitcherController mKeyguardUserSwitcherController; @Mock - private IdleViewComponent.Factory mIdleViewComponentFactory; - @Mock - private IdleViewComponent mIdleViewComponent; - @Mock - private IdleHostViewController mIdleHostViewController; - @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; @@ -475,10 +467,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .thenReturn(mCommunalViewComponent); when(mCommunalViewComponent.getCommunalHostViewController()) .thenReturn(mCommunalHostViewController); - when(mIdleViewComponentFactory.build(any())) - .thenReturn(mIdleViewComponent); - when(mIdleViewComponent.getIdleHostViewController()) - .thenReturn(mIdleHostViewController); when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean())) .thenReturn(mKeyguardStatusView); when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean())) @@ -523,7 +511,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mKeyguardUserSwitcherComponentFactory, mKeyguardStatusBarViewComponentFactory, mCommunalViewComponentFactory, - mIdleViewComponentFactory, mLockscreenShadeTransitionController, mGroupManager, mNotificationAreaController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 107ba8130349..8b93de571cc9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.unfold.SysUIUnfoldComponent; import org.junit.Before; import org.junit.Test; @@ -60,6 +61,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -100,6 +103,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private ShadeController mShadeController; @Mock + private SysUIUnfoldComponent mSysUiUnfoldComponent; + @Mock private DreamOverlayStateController mDreamOverlayStateController; @Mock private LatencyTracker mLatencyTracker; @@ -130,6 +135,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mock(NotificationMediaManager.class), mKeyguardBouncerFactory, mKeyguardMessageAreaFactory, + Optional.of(mSysUiUnfoldComponent), () -> mShadeController, mLatencyTracker); mStatusBarKeyguardViewManager.registerStatusBar( diff --git a/services/Android.bp b/services/Android.bp index e0ca8a63b471..2e4405f35cd6 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -54,7 +54,9 @@ system_optimized_java_defaults { SYSTEM_OPTIMIZE_JAVA: { optimize: { enabled: true, - optimize: true, + // TODO(b/210510433): Enable optimizations after improving + // retracing infra. + optimize: false, shrink: true, proguard_flags_files: ["proguard.flags"], }, diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java index be1bc7907cd5..c39b59ae35b3 100644 --- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -26,6 +26,7 @@ import android.content.ComponentName; import android.content.Context; import android.os.Handler; import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -120,6 +121,12 @@ class CompanionApplicationController { mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors); } + if (serviceConnectors.isEmpty()) { + Slog.e(TAG, "Can't find CompanionDeviceService implementer in package: " + + packageName + ". Please check if they are correctly declared."); + return; + } + // The first connector in the list is always the primary connector: set a listener to it. serviceConnectors.get(0).setListener(this::onPrimaryServiceBindingDied); diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index 4afa96c8072d..bc1f28d1c373 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -37,6 +37,7 @@ import android.util.Slog; import android.view.Display; import android.window.DisplayWindowPolicyController; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.BlockedAppStreamingActivity; import java.util.List; @@ -75,9 +76,11 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController private final ArraySet<ComponentName> mAllowedActivities; @Nullable private final ArraySet<ComponentName> mBlockedActivities; + private final Object mGenericWindowPolicyControllerLock = new Object(); private Consumer<ActivityInfo> mActivityBlockedCallback; @NonNull + @GuardedBy("mGenericWindowPolicyControllerLock") final ArraySet<Integer> mRunningUids = new ArraySet<>(); @Nullable private final ActivityListener mActivityListener; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -149,11 +152,13 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @Override public void onRunningAppsChanged(ArraySet<Integer> runningUids) { - mRunningUids.clear(); - mRunningUids.addAll(runningUids); - if (mActivityListener != null && mRunningUids.isEmpty()) { - // Post callback on the main thread so it doesn't block activity launching - mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY)); + synchronized (mGenericWindowPolicyControllerLock) { + mRunningUids.clear(); + mRunningUids.addAll(runningUids); + if (mActivityListener != null && mRunningUids.isEmpty()) { + // Post callback on the main thread so it doesn't block activity launching + mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY)); + } } if (mRunningAppsChangedListener != null) { mRunningAppsChangedListener.onRunningAppsChanged(runningUids); @@ -165,7 +170,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController * this controller. */ boolean containsUid(int uid) { - return mRunningUids.contains(uid); + synchronized (mGenericWindowPolicyControllerLock) { + return mRunningUids.contains(uid); + } } private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags, diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 387d911672a8..c0a904fe3d9a 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -235,6 +235,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub }); mPerDisplayWakelocks.clear(); } + if (mVirtualAudioController != null) { + mVirtualAudioController.stopListening(); + mVirtualAudioController = null; + } } mListener.onClose(mAssociationInfo.getId()); mAppToken.unlinkToDeath(this, 0); diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index d218af39405d..6986d3bbe585 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -36,11 +36,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IBinaryTransparencyService; import com.android.internal.util.FrameworkStatsLog; -import java.io.File; import java.io.FileDescriptor; -import java.io.IOException; import java.io.PrintWriter; -import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -434,7 +431,7 @@ public class BinaryTransparencyService extends SystemService { entry.setValue(packageInfo.lastUpdateTime); // compute the digest for the updated package - String sha256digest = computeSha256DigestOfFile( + String sha256digest = PackageUtils.computeSha256DigestForLargeFile( packageInfo.applicationInfo.sourceDir); if (sha256digest == null) { Slog.e(TAG, "Failed to compute SHA256sum for file at " @@ -471,7 +468,7 @@ public class BinaryTransparencyService extends SystemService { ApplicationInfo appInfo = packageInfo.applicationInfo; // compute SHA256 for these APEXs - String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir); + String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir); if (sha256digest == null) { Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s", packageInfo.packageName)); @@ -506,7 +503,8 @@ public class BinaryTransparencyService extends SystemService { ApplicationInfo appInfo = packageInfo.applicationInfo; // compute SHA256 digest for these modules - String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir); + String sha256digest = PackageUtils.computeSha256DigestForLargeFile( + appInfo.sourceDir); if (sha256digest == null) { Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s", packageName)); @@ -525,16 +523,4 @@ public class BinaryTransparencyService extends SystemService { } } - @Nullable - private String computeSha256DigestOfFile(@NonNull String pathToFile) { - File apexFile = new File(pathToFile); - - try { - byte[] apexFileBytes = Files.readAllBytes(apexFile.toPath()); - return PackageUtils.computeSha256Digest(apexFileBytes); - } catch (IOException e) { - Slog.e(TAG, String.format("I/O error occurs when reading from %s", pathToFile)); - return null; - } - } } diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 8e53101b0eab..16ff167380bd 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -18,6 +18,7 @@ package com.android.server; import static android.app.ActivityManager.UID_OBSERVER_ACTIVE; import static android.app.ActivityManager.UID_OBSERVER_GONE; +import static android.os.Process.SYSTEM_UID; import android.annotation.IntDef; import android.annotation.NonNull; @@ -261,6 +262,37 @@ public final class PinnerService extends SystemService { } } + /** Returns information about pinned files and sizes for StatsPullAtomService. */ + public List<PinnedFileStats> dumpDataForStatsd() { + List<PinnedFileStats> pinnedFileStats = new ArrayList<>(); + synchronized (PinnerService.this) { + for (PinnedFile pinnedFile : mPinnedFiles) { + pinnedFileStats.add(new PinnedFileStats(SYSTEM_UID, pinnedFile)); + } + + for (int key : mPinnedApps.keySet()) { + PinnedApp app = mPinnedApps.get(key); + for (PinnedFile pinnedFile : mPinnedApps.get(key).mFiles) { + pinnedFileStats.add(new PinnedFileStats(app.uid, pinnedFile)); + } + } + } + return pinnedFileStats; + } + + /** Wrapper class for statistics for a pinned file. */ + public static class PinnedFileStats { + public final int uid; + public final String filename; + public final int sizeKb; + + protected PinnedFileStats(int uid, PinnedFile file) { + this.uid = uid; + this.filename = file.fileName.substring(file.fileName.lastIndexOf('/') + 1); + this.sizeKb = file.bytesPinned / 1024; + } + } + /** * Handler for on start pinning message */ diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 9c8ed5a8edcb..efbc4deaf9f5 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -3019,14 +3019,32 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId); intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId); + // Send the broadcast twice -- once for all apps with READ_PHONE_STATE, then again - // for all apps with READ_PRIV but not READ_PHONE_STATE. This ensures that any app holding - // either READ_PRIV or READ_PHONE get this broadcast exactly once. - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.READ_PHONE_STATE); - mContext.createContextAsUser(UserHandle.ALL, 0) - .sendBroadcastMultiplePermissions(intent, - new String[] { Manifest.permission.READ_PRIVILEGED_PHONE_STATE }, - new String[] { Manifest.permission.READ_PHONE_STATE }); + // for all apps with READ_PRIVILEGED_PHONE_STATE but not READ_PHONE_STATE. + // Do this again twice, the first time for apps with ACCESS_FINE_LOCATION, then again with + // the location-sanitized service state for all apps without ACCESS_FINE_LOCATION. + // This ensures that any app holding either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE + // get this broadcast exactly once, and we are not exposing location without permission. + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent, + new String[] {Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION}); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent, + new String[] {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION}, + new String[] {Manifest.permission.READ_PHONE_STATE}); + + // Replace bundle with location-sanitized ServiceState + data = new Bundle(); + state.createLocationInfoSanitizedCopy(true).fillInNotifierBundle(data); + intent.putExtras(data); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent, + new String[] {Manifest.permission.READ_PHONE_STATE}, + new String[] {Manifest.permission.ACCESS_FINE_LOCATION}); + mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent, + new String[] {Manifest.permission.READ_PRIVILEGED_PHONE_STATE}, + new String[] {Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION}); } private void broadcastSignalStrengthChanged(SignalStrength signalStrength, int phoneId, diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 6c1a00d9fc22..97dd3230176e 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -1018,6 +1018,7 @@ class AppErrors { Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, 0, mService.mUserController.getCurrentUserId()) != 0; + final String packageName = proc.info.packageName; final boolean crashSilenced = mAppsNotReportingCrashes != null && mAppsNotReportingCrashes.contains(proc.info.packageName); final long now = SystemClock.uptimeMillis(); @@ -1026,6 +1027,7 @@ class AppErrors { if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground) && !crashSilenced && !shouldThottle && (showFirstCrash || showFirstCrashDevOption || data.repeating)) { + Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId); errState.getDialogController().showCrashDialogs(data); if (!proc.isolated) { mProcessCrashShowDialogTimes.put(proc.processName, proc.uid, now); diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java index 7f48d527ac77..69f70ca0d0e0 100644 --- a/services/core/java/com/android/server/am/AppPermissionTracker.java +++ b/services/core/java/com/android/server/am/AppPermissionTracker.java @@ -64,6 +64,8 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy @GuardedBy("mLock") private SparseArray<ArraySet<String>> mUidGrantedPermissionsInMonitor = new SparseArray<>(); + private volatile boolean mLockedBootCompleted = false; + AppPermissionTracker(Context context, AppRestrictionController controller) { this(context, controller, null, null); } @@ -85,20 +87,20 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy final PackageManagerInternal pmi = mInjector.getPackageManagerInternal(); final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal(); final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor; for (int userId : allUsers) { final List<ApplicationInfo> apps = pmi.getInstalledApplications(0, userId, SYSTEM_UID); if (apps == null) { continue; } - synchronized (mLock) { - final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor; - final long now = SystemClock.elapsedRealtime(); - for (int i = 0, size = apps.size(); i < size; i++) { - final ApplicationInfo ai = apps.get(i); - for (String permission : permissions) { - if (pm.checkUidPermission(ai.uid, permission) != PERMISSION_GRANTED) { - continue; - } + final long now = SystemClock.elapsedRealtime(); + for (int i = 0, size = apps.size(); i < size; i++) { + final ApplicationInfo ai = apps.get(i); + for (String permission : permissions) { + if (pm.checkUidPermission(ai.uid, permission) != PERMISSION_GRANTED) { + continue; + } + synchronized (mLock) { ArraySet<String> grantedPermissions = uidPerms.get(ai.uid); if (grantedPermissions == null) { grantedPermissions = new ArraySet<String>(); @@ -132,25 +134,30 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy private void handlePermissionsChanged(int uid) { final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); if (permissions != null && permissions.length > 0) { + final PermissionManagerServiceInternal pm = + mInjector.getPermissionManagerServiceInternal(); + final boolean[] states = new boolean[permissions.length]; + for (int i = 0; i < permissions.length; i++) { + states[i] = pm.checkUidPermission(uid, permissions[i]) == PERMISSION_GRANTED; + if (DEBUG_PERMISSION_TRACKER) { + Slog.i(TAG, UserHandle.formatUid(uid) + " " + permissions[i] + "=" + states[i]); + } + } synchronized (mLock) { - handlePermissionsChangedLocked(uid); + handlePermissionsChangedLocked(uid, permissions, states); } } } @GuardedBy("mLock") - private void handlePermissionsChangedLocked(int uid) { - final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal(); + private void handlePermissionsChangedLocked(int uid, String[] permissions, boolean[] states) { final int index = mUidGrantedPermissionsInMonitor.indexOfKey(uid); ArraySet<String> grantedPermissions = index >= 0 ? mUidGrantedPermissionsInMonitor.valueAt(index) : null; - final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); final long now = SystemClock.elapsedRealtime(); - for (String permission: permissions) { - boolean granted = pm.checkUidPermission(uid, permission) == PERMISSION_GRANTED; - if (DEBUG_PERMISSION_TRACKER) { - Slog.i(TAG, UserHandle.formatUid(uid) + " " + permission + "=" + granted); - } + for (int i = 0; i < permissions.length; i++) { + final String permission = permissions[i]; + final boolean granted = states[i]; boolean changed = false; if (granted) { if (grantedPermissions == null) { @@ -200,6 +207,10 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy } private void onPermissionTrackerEnabled(boolean enabled) { + if (!mLockedBootCompleted) { + // Not ready, bail out. + return; + } final PermissionManager pm = mInjector.getPermissionManager(); if (enabled) { pm.addOnPermissionsChangeListener(this); @@ -211,6 +222,12 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy } @Override + void onLockedBootCompleted() { + mLockedBootCompleted = true; + onPermissionTrackerEnabled(mInjector.getPolicy().isEnabled()); + } + + @Override void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.println("APP PERMISSIONS TRACKER:"); diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 1129c19bce87..2ffd48739e47 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -71,6 +71,7 @@ import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE; import static android.os.PowerExemptionManager.REASON_SYSTEM_UID; import static android.os.PowerExemptionManager.reasonCodeToString; import static android.os.Process.SYSTEM_UID; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; @@ -792,7 +793,7 @@ public final class AppRestrictionController { mInjector = injector; mContext = injector.getContext(); mActivityManagerService = service; - mBgHandlerThread = new HandlerThread("bgres-controller"); + mBgHandlerThread = new HandlerThread("bgres-controller", THREAD_PRIORITY_BACKGROUND); mBgHandlerThread.start(); mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector); mBgExecutor = new HandlerExecutor(mBgHandler); @@ -816,9 +817,11 @@ public final class AppRestrictionController { mInjector.getAppStandbyInternal().addListener(mAppIdleStateChangeListener); mInjector.getRoleManager().addOnRoleHoldersChangedListenerAsUser(mBgExecutor, mRoleHolderChangedListener, UserHandle.ALL); - for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) { - mAppStateTrackers.get(i).onSystemReady(); - } + mInjector.scheduleInitTrackers(mBgHandler, () -> { + for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) { + mAppStateTrackers.get(i).onSystemReady(); + } + }); } @VisibleForTesting @@ -2137,6 +2140,10 @@ public final class AppRestrictionController { } return null; } + + void scheduleInitTrackers(Handler handler, Runnable initializers) { + handler.post(initializers); + } } private void registerForSystemBroadcasts() { @@ -2221,6 +2228,21 @@ public final class AppRestrictionController { userFilter.addAction(Intent.ACTION_USER_REMOVED); userFilter.addAction(Intent.ACTION_UID_REMOVED); mContext.registerReceiverForAllUsers(broadcastReceiver, userFilter, null, mBgHandler); + final BroadcastReceiver bootReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + switch (intent.getAction()) { + case Intent.ACTION_LOCKED_BOOT_COMPLETED: { + onLockedBootCompleted(); + } break; + } + } + }; + final IntentFilter bootFilter = new IntentFilter(); + bootFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED); + mContext.registerReceiverAsUser(bootReceiver, UserHandle.SYSTEM, + bootFilter, null, mBgHandler); } void forEachTracker(Consumer<BaseAppStateTracker> sink) { @@ -2275,6 +2297,12 @@ public final class AppRestrictionController { mRestrictionSettings.removeUid(uid); } + private void onLockedBootCompleted() { + for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) { + mAppStateTrackers.get(i).onLockedBootCompleted(); + } + } + boolean isBgAutoRestrictedBucketFeatureFlagEnabled() { return mConstantsObserver.mBgAutoRestrictedBucket; } diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java index 482d69751d70..0fada53d622e 100644 --- a/services/core/java/com/android/server/am/BaseAppStateTracker.java +++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java @@ -204,6 +204,12 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { } /** + * Called when the system sends LOCKED_BOOT_COMPLETED. + */ + void onLockedBootCompleted() { + } + + /** * Called when a device config property in the activity manager namespace * has changed. */ diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 9929ed72c06b..86ca6994eeed 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1115,6 +1115,9 @@ public final class CachedAppOptimizer { int lastOomAdj = msg.arg1; int procState = msg.arg2; synchronized (mProcLock) { + if(mPendingCompactionProcesses.isEmpty()) { + return; + } proc = mPendingCompactionProcesses.remove(0); opt = proc.mOptRecord; diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index 90aefe052e5c..d239c02d4529 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -230,7 +230,9 @@ public final class AppHibernationService extends SystemService { "Caller did not have permission while calling " + methodName); userId = handleIncomingUser(userId, methodName); synchronized (mLock) { - if (!checkUserStatesExist(userId, methodName)) { + // Don't log as this method can be called before user states exist as part of the + // force-stop check. + if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ false)) { return false; } final Map<String, UserLevelState> packageStates = mUserStates.get(userId); @@ -238,8 +240,6 @@ public final class AppHibernationService extends SystemService { if (pkgState == null || !mPackageManagerInternal.canQueryPackage( Binder.getCallingUid(), packageName)) { - Slog.e(TAG, TextUtils.formatSimple("Package %s is not installed for user %s", - packageName, userId)); return false; } return pkgState.hibernated; @@ -289,7 +289,7 @@ public final class AppHibernationService extends SystemService { "Caller does not have MANAGE_APP_HIBERNATION permission."); final int realUserId = handleIncomingUser(userId, methodName); synchronized (mLock) { - if (!checkUserStatesExist(realUserId, methodName)) { + if (!checkUserStatesExist(realUserId, methodName, /* shouldLog= */ true)) { return; } final Map<String, UserLevelState> packageStates = mUserStates.get(realUserId); @@ -382,7 +382,7 @@ public final class AppHibernationService extends SystemService { "Caller does not have MANAGE_APP_HIBERNATION permission."); userId = handleIncomingUser(userId, methodName); synchronized (mLock) { - if (!checkUserStatesExist(userId, methodName)) { + if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ true)) { return hibernatingPackages; } Map<String, UserLevelState> userStates = mUserStates.get(userId); @@ -419,7 +419,7 @@ public final class AppHibernationService extends SystemService { "Caller does not have MANAGE_APP_HIBERNATION permission."); userId = handleIncomingUser(userId, methodName); synchronized (mLock) { - if (!checkUserStatesExist(userId, methodName)) { + if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ true)) { return statsMap; } final Map<String, UserLevelState> userPackageStates = mUserStates.get(userId); @@ -431,7 +431,7 @@ public final class AppHibernationService extends SystemService { } if (!mGlobalHibernationStates.containsKey(pkgName) || !userPackageStates.containsKey(pkgName)) { - Slog.w(TAG, String.format( + Slog.w(TAG, TextUtils.formatSimple( "No hibernation state associated with package %s user %d. Maybe" + "the package was uninstalled? ", pkgName, userId)); continue; @@ -585,7 +585,7 @@ public final class AppHibernationService extends SystemService { PackageInfo pkgInfo = installedPackages.get(packageName); UserLevelState currentState = diskStates.get(i); if (pkgInfo == null) { - Slog.w(TAG, String.format( + Slog.w(TAG, TextUtils.formatSimple( "No hibernation state associated with package %s user %d. Maybe" + "the package was uninstalled? ", packageName, userId)); continue; @@ -633,7 +633,7 @@ public final class AppHibernationService extends SystemService { for (int i = 0, size = diskStates.size(); i < size; i++) { GlobalLevelState state = diskStates.get(i); if (!installedPackages.contains(state.packageName)) { - Slog.w(TAG, String.format( + Slog.w(TAG, TextUtils.formatSimple( "No hibernation state associated with package %s. Maybe the " + "package was uninstalled? ", state.packageName)); continue; @@ -742,18 +742,24 @@ public final class AppHibernationService extends SystemService { * * @param userId user to check * @param methodName method name that is calling. Used for logging purposes. + * @param shouldLog whether we should log why the user state doesn't exist * @return true if user states exist */ @GuardedBy("mLock") - private boolean checkUserStatesExist(int userId, String methodName) { + private boolean checkUserStatesExist(int userId, String methodName, boolean shouldLog) { if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - Slog.e(TAG, String.format( - "Attempt to call %s on stopped or nonexistent user %d", methodName, userId)); + if (shouldLog) { + Slog.w(TAG, TextUtils.formatSimple( + "Attempt to call %s on stopped or nonexistent user %d", + methodName, userId)); + } return false; } if (!mUserStates.contains(userId)) { - Slog.w(TAG, String.format( - "Attempt to call %s before states have been read from disk", methodName)); + if (shouldLog) { + Slog.w(TAG, TextUtils.formatSimple( + "Attempt to call %s before states have been read from disk", methodName)); + } return false; } return true; diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index d2fa386ad6ba..567d1aef3272 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -30,7 +30,7 @@ import android.annotation.Nullable; import android.app.ActivityThread; import android.attention.AttentionManagerInternal; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; -import android.attention.AttentionManagerInternal.ProximityCallbackInternal; +import android.attention.AttentionManagerInternal.ProximityUpdateCallbackInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -59,7 +59,7 @@ import android.service.attention.AttentionService.AttentionFailureCodes; import android.service.attention.AttentionService.AttentionSuccessCodes; import android.service.attention.IAttentionCallback; import android.service.attention.IAttentionService; -import android.service.attention.IProximityCallback; +import android.service.attention.IProximityUpdateCallback; import android.text.TextUtils; import android.util.Slog; @@ -336,7 +336,7 @@ public class AttentionManagerService extends SystemService { * @return {@code true} if the framework was able to dispatch the request */ @VisibleForTesting - boolean onStartProximityUpdates(ProximityCallbackInternal callbackInternal) { + boolean onStartProximityUpdates(ProximityUpdateCallbackInternal callbackInternal) { Objects.requireNonNull(callbackInternal); if (!mIsServiceEnabled) { Slog.w(LOG_TAG, "Trying to call onProximityUpdate() on an unsupported device."); @@ -385,7 +385,7 @@ public class AttentionManagerService extends SystemService { /** Cancels the specified proximity registration. */ @VisibleForTesting - void onStopProximityUpdates(ProximityCallbackInternal callbackInternal) { + void onStopProximityUpdates(ProximityUpdateCallbackInternal callbackInternal) { synchronized (mLock) { if (mCurrentProximityUpdate == null || !mCurrentProximityUpdate.mCallbackInternal.equals(callbackInternal) @@ -506,12 +506,12 @@ public class AttentionManagerService extends SystemService { @Override public boolean onStartProximityUpdates( - ProximityCallbackInternal callback) { + ProximityUpdateCallbackInternal callback) { return AttentionManagerService.this.onStartProximityUpdates(callback); } @Override - public void onStopProximityUpdates(ProximityCallbackInternal callback) { + public void onStopProximityUpdates(ProximityUpdateCallbackInternal callback) { AttentionManagerService.this.onStopProximityUpdates(callback); } } @@ -635,13 +635,13 @@ public class AttentionManagerService extends SystemService { @VisibleForTesting final class ProximityUpdate { - private final ProximityCallbackInternal mCallbackInternal; - private final IProximityCallback mIProximityCallback; + private final ProximityUpdateCallbackInternal mCallbackInternal; + private final IProximityUpdateCallback mIProximityUpdateCallback; private boolean mStartedUpdates; - ProximityUpdate(ProximityCallbackInternal callbackInternal) { + ProximityUpdate(ProximityUpdateCallbackInternal callbackInternal) { mCallbackInternal = callbackInternal; - mIProximityCallback = new IProximityCallback.Stub() { + mIProximityUpdateCallback = new IProximityUpdateCallback.Stub() { @Override public void onProximityUpdate(double distance) { synchronized (mLock) { @@ -664,7 +664,7 @@ public class AttentionManagerService extends SystemService { return false; } try { - mService.onStartProximityUpdates(mIProximityCallback); + mService.onStartProximityUpdates(mIProximityUpdateCallback); mStartedUpdates = true; } catch (RemoteException e) { Slog.e(LOG_TAG, "Cannot call into the AttentionService", e); @@ -758,7 +758,8 @@ public class AttentionManagerService extends SystemService { if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) { if (mService != null) { try { - mService.onStartProximityUpdates(mCurrentProximityUpdate.mIProximityCallback); + mService.onStartProximityUpdates( + mCurrentProximityUpdate.mIProximityUpdateCallback); } catch (RemoteException e) { Slog.e(LOG_TAG, "Cannot call into the AttentionService", e); } @@ -913,7 +914,7 @@ public class AttentionManagerService extends SystemService { } } - class TestableProximityCallbackInternal extends ProximityCallbackInternal { + class TestableProximityUpdateCallbackInternal extends ProximityUpdateCallbackInternal { private double mLastCallbackCode = PROXIMITY_UNKNOWN; @Override @@ -932,8 +933,8 @@ public class AttentionManagerService extends SystemService { final TestableAttentionCallbackInternal mTestableAttentionCallback = new TestableAttentionCallbackInternal(); - final TestableProximityCallbackInternal mTestableProximityCallback = - new TestableProximityCallbackInternal(); + final TestableProximityUpdateCallbackInternal mTestableProximityUpdateCallback = + new TestableProximityUpdateCallbackInternal(); @Override public int onCommand(@Nullable final String cmd) { @@ -964,8 +965,8 @@ public class AttentionManagerService extends SystemService { return cmdClearTestableAttentionService(); case "getLastTestCallbackCode": return cmdGetLastTestCallbackCode(); - case "getLastTestProximityCallbackCode": - return cmdGetLastTestProximityCallbackCode(); + case "getLastTestProximityUpdateCallbackCode": + return cmdGetLastTestProximityUpdateCallbackCode(); default: return handleDefaultCommands(cmd); } @@ -990,7 +991,7 @@ public class AttentionManagerService extends SystemService { private int cmdClearTestableAttentionService() { sTestAttentionServicePackage = ""; mTestableAttentionCallback.reset(); - mTestableProximityCallback.reset(); + mTestableProximityUpdateCallback.reset(); resetStates(); return 0; } @@ -1011,14 +1012,14 @@ public class AttentionManagerService extends SystemService { private int cmdCallOnStartProximityUpdates() { final PrintWriter out = getOutPrintWriter(); - boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityCallback); + boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityUpdateCallback); out.println(calledSuccessfully ? "true" : "false"); return 0; } private int cmdCallOnStopProximityUpdates() { final PrintWriter out = getOutPrintWriter(); - onStopProximityUpdates(mTestableProximityCallback); + onStopProximityUpdates(mTestableProximityUpdateCallback); out.println("true"); return 0; } @@ -1036,9 +1037,9 @@ public class AttentionManagerService extends SystemService { return 0; } - private int cmdGetLastTestProximityCallbackCode() { + private int cmdGetLastTestProximityUpdateCallbackCode() { final PrintWriter out = getOutPrintWriter(); - out.println(mTestableProximityCallback.getLastCallbackCode()); + out.println(mTestableProximityUpdateCallback.getLastCallbackCode()); return 0; } @@ -1081,7 +1082,7 @@ public class AttentionManagerService extends SystemService { out.println( " := true, if the request was successfully dispatched to the service " + "implementation." - + " (to see the result, call getLastTestProximityCallbackCode)"); + + " (to see the result, call getLastTestProximityUpdateCallbackCode)"); out.println(" := false, otherwise"); out.println(" call onStopProximityUpdates: Cancels proximity updates"); out.println(" getLastTestCallbackCode"); @@ -1089,7 +1090,7 @@ public class AttentionManagerService extends SystemService { out.println( " := An integer, representing the last callback code received from the " + "bounded implementation. If none, it will return -1"); - out.println(" getLastTestProximityCallbackCode"); + out.println(" getLastTestProximityUpdateCallbackCode"); out.println(" ---returns:"); out.println( " := A double, representing the last proximity value received from the " diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 807293fdae50..0b9fb1a72f8d 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3330,6 +3330,13 @@ public class AudioService extends IAudioService.Stub } } + private void enforceAccessUltrasoundPermission() { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Missing ACCESS_ULTRASOUND permission"); + } + } + private void enforceQueryStatePermission() { if (mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE) != PackageManager.PERMISSION_GRANTED) { @@ -3462,6 +3469,12 @@ public class AudioService extends IAudioService.Stub attributionTag, Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission()); } + /** @see AudioManager#isUltrasoundSupported() */ + public boolean isUltrasoundSupported() { + enforceAccessUltrasoundPermission(); + return AudioSystem.isUltrasoundSupported(); + } + private boolean canChangeAccessibilityVolume() { synchronized (mAccessibilityServiceUidsLock) { if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index 2c2a2bf24cfd..17215e5ae4ad 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -131,6 +131,7 @@ public class BrightnessTracker { private static final int MSG_STOP_SENSOR_LISTENER = 2; private static final int MSG_START_SENSOR_LISTENER = 3; private static final int MSG_BRIGHTNESS_CONFIG_CHANGED = 4; + private static final int MSG_SENSOR_CHANGED = 5; private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); @@ -158,6 +159,7 @@ public class BrightnessTracker { // These members should only be accessed on the mBgHandler thread. private BroadcastReceiver mBroadcastReceiver; private SensorListener mSensorListener; + private Sensor mLightSensor; private SettingsObserver mSettingsObserver; private DisplayListener mDisplayListener; private boolean mSensorRegistered; @@ -327,6 +329,14 @@ public class BrightnessTracker { m.sendToTarget(); } + /** + * Updates the light sensor to use. + */ + public void setLightSensor(Sensor lightSensor) { + mBgHandler.obtainMessage(MSG_SENSOR_CHANGED, 0 /*unused*/, 0/*unused*/, lightSensor) + .sendToTarget(); + } + private void handleBrightnessChanged(float brightness, boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness, boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId) { @@ -428,13 +438,28 @@ public class BrightnessTracker { } } + private void handleSensorChanged(Sensor lightSensor) { + if (mLightSensor != lightSensor) { + mLightSensor = lightSensor; + stopSensorListener(); + synchronized (mDataCollectionLock) { + mLastSensorReadings.clear(); + } + // Attempt to restart the sensor listener. It will check to see if it should be running + // so there is no need to also check here. + startSensorListener(); + } + } + private void startSensorListener() { if (!mSensorRegistered + && mLightSensor != null + && mAmbientBrightnessStatsTracker != null && mInjector.isInteractive(mContext) && mInjector.isBrightnessModeAutomatic(mContentResolver)) { mAmbientBrightnessStatsTracker.start(); mSensorRegistered = true; - mInjector.registerSensorListener(mContext, mSensorListener, + mInjector.registerSensorListener(mContext, mSensorListener, mLightSensor, mInjector.getBackgroundHandler()); } } @@ -736,6 +761,7 @@ public class BrightnessTracker { pw.println("BrightnessTracker state:"); synchronized (mDataCollectionLock) { pw.println(" mStarted=" + mStarted); + pw.println(" mLightSensor=" + mLightSensor); pw.println(" mLastBatteryLevel=" + mLastBatteryLevel); pw.println(" mLastBrightness=" + mLastBrightness); pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size()); @@ -1017,6 +1043,9 @@ public class BrightnessTracker { disableColorSampling(); } break; + case MSG_SENSOR_CHANGED: + handleSensorChanged((Sensor) msg.obj); + break; } } @@ -1045,9 +1074,8 @@ public class BrightnessTracker { @VisibleForTesting static class Injector { public void registerSensorListener(Context context, - SensorEventListener sensorListener, Handler handler) { + SensorEventListener sensorListener, Sensor lightSensor, Handler handler) { SensorManager sensorManager = context.getSystemService(SensorManager.class); - Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); sensorManager.registerListener(sensorListener, lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 418e91dd616e..9067f2e25152 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -826,7 +826,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) { // All properties that depend on the associated DisplayDevice and the DDC must be // updated here. - loadAmbientLightSensor(); loadBrightnessRampRates(); loadProximitySensor(); loadNitsRange(mContext.getResources()); @@ -972,6 +971,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } loadAmbientLightSensor(); + if (mBrightnessTracker != null) { + mBrightnessTracker.setLightSensor(mLightSensor); + } if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.stop(); diff --git a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java index cb93cc8fc3a7..f529c4c65a9a 100644 --- a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java +++ b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java @@ -98,6 +98,13 @@ public class ReduceBrightColorsTintController extends TintController { return ColorDisplayManager.isColorTransformAccelerated(context); } + @Override + public void setActivated(Boolean isActivated) { + super.setActivated(isActivated); + Slog.i(ColorDisplayService.TAG, (isActivated != null && isActivated) + ? "Turning on reduce bright colors" : "Turning off reduce bright colors"); + } + public int getStrength() { return mStrength; } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 39ee0f42b7d1..9b1005880e07 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -40,6 +40,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; import android.sysprop.ApexProperties; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.PrintWriterPrinter; @@ -608,18 +609,21 @@ public abstract class ApexManager { continue; } - String name = service.getName(); - for (ApexSystemServiceInfo info : mApexSystemServices) { - if (info.getName().equals(name)) { - throw new IllegalStateException(String.format( - "Duplicate apex-system-service %s from %s, %s", - name, info.mJarPath, service.getJarPath())); + if (ai.isActive) { + String name = service.getName(); + for (int j = 0; j < mApexSystemServices.size(); j++) { + ApexSystemServiceInfo info = mApexSystemServices.get(j); + if (info.getName().equals(name)) { + throw new IllegalStateException(TextUtils.formatSimple( + "Duplicate apex-system-service %s from %s, %s", name, + info.mJarPath, service.getJarPath())); + } } + ApexSystemServiceInfo info = new ApexSystemServiceInfo( + service.getName(), service.getJarPath(), + service.getInitOrder()); + mApexSystemServices.add(info); } - - ApexSystemServiceInfo info = new ApexSystemServiceInfo( - service.getName(), service.getJarPath(), service.getInitOrder()); - mApexSystemServices.add(info); } Collections.sort(mApexSystemServices); mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName); diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index 410fa975cb8a..da22b1796ac3 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -37,6 +37,8 @@ import com.android.server.utils.WatchableImpl; import com.android.server.utils.WatchedArrayMap; import com.android.server.utils.WatchedArraySet; +import java.util.Collections; +import java.util.Map; import java.util.Objects; @DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true) @@ -540,6 +542,13 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return this; } + @NonNull + @Override + public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() { + return mSharedLibraryOverlayPaths == null + ? Collections.emptyMap() : mSharedLibraryOverlayPaths; + } + @Override public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: @@ -703,11 +712,6 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated.Member - public @Nullable WatchedArrayMap<String,OverlayPaths> getSharedLibraryOverlayPaths() { - return mSharedLibraryOverlayPaths; - } - - @DataClass.Generated.Member public @Nullable String getSplashScreenTheme() { return mSplashScreenTheme; } @@ -774,10 +778,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated( - time = 1644638242940L, + time = 1645040852569L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 18c45e494c9b..4ad6ed1f45e2 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -33,6 +33,7 @@ import android.content.pm.ResolveInfo; import android.location.LocationManagerInternal; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.PackageTagsList; import android.os.Process; @@ -342,8 +343,11 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat + Intent.ACTION_ACTIVITY_RECOGNIZER + ", ignoring!"); return; } - final String tagsList = resolvedService.serviceInfo.metaData.getString( - ACTIVITY_RECOGNITION_TAGS); + final Bundle metaData = resolvedService.serviceInfo.metaData; + if (metaData == null) { + return; + } + final String tagsList = metaData.getString(ACTIVITY_RECOGNITION_TAGS); if (!TextUtils.isEmpty(tagsList)) { PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add( resolvedService.serviceInfo.packageName, diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 8ec055645f2d..1b153513595a 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -197,6 +197,8 @@ import com.android.role.RoleManagerLocal; import com.android.server.BinderCallsStatsService; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; +import com.android.server.PinnerService; +import com.android.server.PinnerService.PinnedFileStats; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.am.MemoryStatUtil.MemoryStat; @@ -727,6 +729,8 @@ public class StatsPullAtomService extends SystemService { return pullAccessibilityFloatingMenuStatsLocked(atomTag, data); case FrameworkStatsLog.MEDIA_CAPABILITIES: return pullMediaCapabilitiesStats(atomTag, data); + case FrameworkStatsLog.PINNED_FILE_SIZES_PER_PACKAGE: + return pullSystemServerPinnerStats(atomTag, data); case FrameworkStatsLog.PENDING_INTENTS_PER_PACKAGE: return pullPendingIntentsPerPackage(atomTag, data); default: @@ -926,6 +930,7 @@ public class StatsPullAtomService extends SystemService { registerAccessibilityFloatingMenuStats(); registerMediaCapabilitiesStats(); registerPendingIntentsPerPackagePuller(); + registerPinnerServiceStats(); } private void initAndRegisterNetworkStatsPullers() { @@ -4607,6 +4612,26 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + private void registerPinnerServiceStats() { + int tagId = FrameworkStatsLog.PINNED_FILE_SIZES_PER_PACKAGE; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } + + int pullSystemServerPinnerStats(int atomTag, List<StatsEvent> pulledData) { + PinnerService pinnerService = LocalServices.getService(PinnerService.class); + List<PinnedFileStats> pinnedFileStats = pinnerService.dumpDataForStatsd(); + for (PinnedFileStats pfstats : pinnedFileStats) { + pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, + pfstats.uid, pfstats.filename, pfstats.sizeKb)); + } + return StatsManager.PULL_SUCCESS; + } + private byte[] toBytes(List<Integer> audioEncodings) { ProtoOutputStream protoOutputStream = new ProtoOutputStream(); for (int audioEncoding : audioEncodings) { diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 8b80b4a0b21e..597f7f284730 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -78,6 +78,7 @@ import android.os.PowerManager.WakeLock; import android.os.Process; import android.os.SystemClock; import android.provider.Settings; +import android.telephony.TelephonyManager; import android.util.ArraySet; import android.util.Slog; @@ -163,6 +164,14 @@ import java.util.function.Consumer; public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); + // Matches DataConnection.NETWORK_TYPE private constant, and magic string from + // ConnectivityManager#getNetworkTypeName() + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String NETWORK_INFO_NETWORK_TYPE_STRING = "MOBILE"; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String NETWORK_INFO_EXTRA_INFO = "VCN"; + @VisibleForTesting(visibility = Visibility.PRIVATE) static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); @@ -1631,6 +1640,12 @@ public class VcnGatewayConnection extends StateMachine { final NetworkAgentConfig nac = new NetworkAgentConfig.Builder() .setLegacyType(ConnectivityManager.TYPE_MOBILE) + .setLegacyTypeName(NETWORK_INFO_NETWORK_TYPE_STRING) + .setLegacySubType(TelephonyManager.NETWORK_TYPE_UNKNOWN) + .setLegacySubTypeName( + TelephonyManager.getNetworkTypeName( + TelephonyManager.NETWORK_TYPE_UNKNOWN)) + .setLegacyExtraInfo(NETWORK_INFO_EXTRA_INFO) .build(); final VcnNetworkAgent agent = diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index fdd991358154..1f1f40b8121d 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -70,32 +70,42 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>(); - /** Callbacks for playing a {@link Vibration}. */ - interface VibrationCallbacks { + /** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */ + interface VibratorManagerHooks { /** - * Callback triggered before starting a synchronized vibration step. This will be called - * with {@code requiredCapabilities = 0} if no synchronization is required. + * Request the manager to prepare for triggering a synchronized vibration step. * * @param requiredCapabilities The required syncing capabilities for this preparation step. - * Expects a combination of values from + * Expect CAP_SYNC and a combination of values from * IVibratorManager.CAP_PREPARE_* and * IVibratorManager.CAP_MIXED_TRIGGER_*. * @param vibratorIds The id of the vibrators to be prepared. */ boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds); - /** Callback triggered after synchronized vibrations were prepared. */ + /** + * Request the manager to trigger a synchronized vibration. The vibration must already + * have been prepared with {@link #prepareSyncedVibration}. + */ boolean triggerSyncedVibration(long vibrationId); - /** Callback triggered to cancel a prepared synced vibration. */ + /** Tell the manager to cancel a synced vibration. */ void cancelSyncedVibration(); - /** Callback triggered when the vibration is complete. */ + /** + * Tell the manager that the currently active vibration has completed its vibration, from + * the perspective of the Effect. However, the VibrationThread may still be continuing with + * cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased} + * is called. + */ void onVibrationCompleted(long vibrationId, Vibration.Status status); - /** Callback triggered when the vibrators are released after the thread is complete. */ - void onVibratorsReleased(); + /** + * Tells the manager that the VibrationThread is finished with the previous vibration and + * all of its cleanup tasks, and the vibrators can now be used for another vibration. + */ + void onVibrationThreadReleased(); } private final Object mLock = new Object(); @@ -105,7 +115,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private final VibrationSettings mVibrationSettings; private final DeviceVibrationEffectAdapter mDeviceEffectAdapter; private final Vibration mVibration; - private final VibrationCallbacks mCallbacks; + private final VibratorManagerHooks mVibratorManagerHooks; private final SparseArray<VibratorController> mVibrators = new SparseArray<>(); private final StepQueue mStepQueue = new StepQueue(); @@ -117,11 +127,11 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { VibrationThread(Vibration vib, VibrationSettings vibrationSettings, DeviceVibrationEffectAdapter effectAdapter, SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock, - IBatteryStats batteryStatsService, VibrationCallbacks callbacks) { + IBatteryStats batteryStatsService, VibratorManagerHooks vibratorManagerHooks) { mVibration = vib; mVibrationSettings = vibrationSettings; mDeviceEffectAdapter = effectAdapter; - mCallbacks = callbacks; + mVibratorManagerHooks = vibratorManagerHooks; mWorkSource = new WorkSource(mVibration.uid); mWakeLock = wakeLock; mBatteryStatsService = batteryStatsService; @@ -163,7 +173,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED); } } finally { - mCallbacks.onVibratorsReleased(); + mVibratorManagerHooks.onVibrationThreadReleased(); } } @@ -263,7 +273,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) { if (!mCalledVibrationCompleteCallback) { mCalledVibrationCompleteCallback = true; - mCallbacks.onVibrationCompleted(mVibration.id, completedStatus); + mVibratorManagerHooks.onVibrationCompleted(mVibration.id, completedStatus); } } @@ -272,25 +282,29 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { try { CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect()); final int sequentialEffectSize = sequentialEffect.getEffects().size(); - mStepQueue.offer(new StartVibrateStep(sequentialEffect)); + mStepQueue.initializeForEffect(sequentialEffect); - while (!mStepQueue.isEmpty()) { - long waitTime; + while (!mStepQueue.isFinished()) { + long waitMillisBeforeNextStep; synchronized (mLock) { - waitTime = mStepQueue.calculateWaitTime(); - if (waitTime > 0) { + waitMillisBeforeNextStep = mStepQueue.getWaitMillisBeforeNextStep(); + if (waitMillisBeforeNextStep > 0) { try { - mLock.wait(waitTime); + mLock.wait(waitMillisBeforeNextStep); } catch (InterruptedException e) { } } } - // If we waited, the queue may have changed, so let the loop run again. - if (waitTime <= 0) { + // Only run the next vibration step if we didn't have to wait in this loop. + // If we waited then the queue may have changed, so loop again to re-evaluate + // the scheduling of the queue top element. + if (waitMillisBeforeNextStep <= 0) { if (DEBUG) { Slog.d(TAG, "Play vibration consuming next step..."); } - mStepQueue.consumeNext(); + // Run the step without holding the main lock, to avoid HAL interactions from + // blocking the thread. + mStepQueue.runNextStep(); } Vibration.Status status = mStop ? Vibration.Status.CANCELLED : mStepQueue.calculateVibrationStatus(sequentialEffectSize); @@ -350,7 +364,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } if (segmentIndex < 0) { // No more segments to play, last step is to complete the vibration on this vibrator. - return new CompleteStep(startTime, /* cancelled= */ false, controller, + return new EffectCompleteStep(startTime, /* cancelled= */ false, controller, vibratorOffTimeout); } @@ -385,7 +399,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @GuardedBy("mLock") private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>(); @GuardedBy("mLock") - private final Queue<Integer> mNotifiedVibrators = new LinkedList<>(); + private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>(); @GuardedBy("mLock") private int mPendingVibrateSteps; @@ -394,18 +408,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @GuardedBy("mLock") private int mSuccessfulVibratorOnSteps; @GuardedBy("mLock") - private boolean mWaitToProcessVibratorCallbacks; + private boolean mWaitToProcessVibratorCompleteCallbacks; - public void offer(@NonNull Step step) { + public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) { synchronized (mLock) { - if (!step.isCleanUp()) { - mPendingVibrateSteps++; - } - mNextSteps.offer(step); + mPendingVibrateSteps++; + mNextSteps.offer(new StartVibrateStep(vibration)); } } - public boolean isEmpty() { + public boolean isFinished() { synchronized (mLock) { return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty(); } @@ -429,11 +441,11 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } - /** Returns the time in millis to wait before calling {@link #consumeNext()}. */ - @GuardedBy("mLock") - public long calculateWaitTime() { + /** Returns the time in millis to wait before calling {@link #runNextStep()}. */ + @GuardedBy("VibrationThread.this.mLock") + public long getWaitMillisBeforeNextStep() { if (!mPendingOnVibratorCompleteSteps.isEmpty()) { - // Steps anticipated by vibrator complete callback should be played right away. + // Steps resumed by vibrator complete callback should be played right away. return 0; } Step nextStep = mNextSteps.peek(); @@ -444,7 +456,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * Play and remove the step at the top of this queue, and also adds the next steps generated * to be played next. */ - public void consumeNext() { + public void runNextStep() { // Vibrator callbacks should wait until the polled step is played and the next steps are // added back to the queue, so they can handle the callback. markWaitToProcessVibratorCallbacks(); @@ -472,7 +484,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } finally { synchronized (mLock) { - processVibratorCallbacks(); + processVibratorCompleteCallbacks(); } } } @@ -485,10 +497,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { */ @GuardedBy("mLock") public void notifyVibratorComplete(int vibratorId) { - mNotifiedVibrators.offer(vibratorId); - if (!mWaitToProcessVibratorCallbacks) { + mCompletionNotifiedVibrators.offer(vibratorId); + if (!mWaitToProcessVibratorCompleteCallbacks) { // No step is being played or cancelled now, process the callback right away. - processVibratorCallbacks(); + processVibratorCompleteCallbacks(); } } @@ -515,7 +527,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } finally { synchronized (mLock) { - processVibratorCallbacks(); + processVibratorCompleteCallbacks(); } } } @@ -540,7 +552,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } finally { synchronized (mLock) { - processVibratorCallbacks(); + processVibratorCompleteCallbacks(); } } } @@ -548,7 +560,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @Nullable private Step pollNext() { synchronized (mLock) { - // Prioritize the steps anticipated by a vibrator complete callback. + // Prioritize the steps resumed by a vibrator complete callback. if (!mPendingOnVibratorCompleteSteps.isEmpty()) { return mPendingOnVibratorCompleteSteps.poll(); } @@ -558,29 +570,29 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private void markWaitToProcessVibratorCallbacks() { synchronized (mLock) { - mWaitToProcessVibratorCallbacks = true; + mWaitToProcessVibratorCompleteCallbacks = true; } } /** - * Notify the step in this queue that should be anticipated by the vibrator completion - * callback and keep it separate to be consumed by {@link #consumeNext()}. + * Notify the step in this queue that should be resumed by the vibrator completion + * callback and keep it separate to be consumed by {@link #runNextStep()}. * * <p>This is a lightweight method that do not trigger any operation from {@link * VibratorController}, so it can be called directly from a native callback. * * <p>This assumes only one of the next steps is waiting on this given vibrator, so the - * first step found will be anticipated by this method, in no particular order. + * first step found will be resumed by this method, in no particular order. */ @GuardedBy("mLock") - private void processVibratorCallbacks() { - mWaitToProcessVibratorCallbacks = false; - while (!mNotifiedVibrators.isEmpty()) { - int vibratorId = mNotifiedVibrators.poll(); + private void processVibratorCompleteCallbacks() { + mWaitToProcessVibratorCompleteCallbacks = false; + while (!mCompletionNotifiedVibrators.isEmpty()) { + int vibratorId = mCompletionNotifiedVibrators.poll(); Iterator<Step> it = mNextSteps.iterator(); while (it.hasNext()) { Step step = it.next(); - if (step.shouldPlayWhenVibratorComplete(vibratorId)) { + if (step.acceptVibratorCompleteCallback(vibratorId)) { it.remove(); mPendingOnVibratorCompleteSteps.offer(step); break; @@ -637,10 +649,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } /** - * Return true to play this step right after a vibrator has notified vibration completed, - * used to anticipate steps waiting on vibrator callbacks with a timeout. + * Return true to run this step right after a vibrator has notified vibration completed, + * used to resume steps waiting on vibrator callbacks with a timeout. */ - public boolean shouldPlayWhenVibratorComplete(int vibratorId) { + public boolean acceptVibratorCompleteCallback(int vibratorId) { return false; } @@ -670,7 +682,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished * all their individual steps. * - * <o>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the + * <p>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the * sequential effect isn't finished yet. */ private final class StartVibrateStep extends Step { @@ -805,7 +817,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { boolean hasTriggered = false; long maxDuration = 0; try { - hasPrepared = mCallbacks.prepareSyncedVibration( + hasPrepared = mVibratorManagerHooks.prepareSyncedVibration( effectMapping.getRequiredSyncCapabilities(), effectMapping.getVibratorIds()); @@ -821,13 +833,13 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { // Check if sync was prepared and if any step was accepted by a vibrator, // otherwise there is nothing to trigger here. if (hasPrepared && maxDuration > 0) { - hasTriggered = mCallbacks.triggerSyncedVibration(mVibration.id); + hasTriggered = mVibratorManagerHooks.triggerSyncedVibration(mVibration.id); } return maxDuration; } finally { if (hasPrepared && !hasTriggered) { // Trigger has failed or all steps were ignored by the vibrators. - mCallbacks.cancelSyncedVibration(); + mVibratorManagerHooks.cancelSyncedVibration(); nextSteps.clear(); } else if (maxDuration < 0) { // Some vibrator failed without being prepared so other vibrators might be @@ -910,7 +922,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { public final long vibratorOffTimeout; long mVibratorOnResult; - boolean mVibratorCallbackReceived; + boolean mVibratorCompleteCallbackReceived; /** * @param startTime The time to schedule this step in the {@link StepQueue}. @@ -919,7 +931,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * @param index The index of the next segment to be played by this step * @param vibratorOffTimeout The time the vibrator is expected to complete any previous * vibration and turn off. This is used to allow this step to be - * anticipated when the completion callback is triggered, and can + * triggered when the completion callback is received, and can * be used play effects back-to-back. */ SingleVibratorStep(long startTime, VibratorController controller, @@ -937,17 +949,17 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } @Override - public boolean shouldPlayWhenVibratorComplete(int vibratorId) { + public boolean acceptVibratorCompleteCallback(int vibratorId) { boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId; - mVibratorCallbackReceived |= isSameVibrator; - // Only anticipate this step if a timeout was set to wait for the vibration to complete, + mVibratorCompleteCallbackReceived |= isSameVibrator; + // Only activate this step if a timeout was set to wait for the vibration to complete, // otherwise we are waiting for the correct time to play the next step. return isSameVibrator && (vibratorOffTimeout > SystemClock.uptimeMillis()); } @Override public List<Step> cancel() { - return Arrays.asList(new CompleteStep(SystemClock.uptimeMillis(), + return Arrays.asList(new EffectCompleteStep(SystemClock.uptimeMillis(), /* cancelled= */ true, controller, vibratorOffTimeout)); } @@ -1205,10 +1217,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * <p>This runs right at the time the vibration is considered to end and will update the pending * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude. */ - private final class CompleteStep extends SingleVibratorStep { + private final class EffectCompleteStep extends SingleVibratorStep { private final boolean mCancelled; - CompleteStep(long startTime, boolean cancelled, VibratorController controller, + EffectCompleteStep(long startTime, boolean cancelled, VibratorController controller, long vibratorOffTimeout) { super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout); mCancelled = cancelled; @@ -1232,13 +1244,13 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @Override public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteStep"); + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "EffectCompleteStep"); try { if (DEBUG) { Slog.d(TAG, "Running " + (mCancelled ? "cancel" : "complete") + " vibration" + " step on vibrator " + controller.getVibratorInfo().getId()); } - if (mVibratorCallbackReceived) { + if (mVibratorCompleteCallbackReceived) { // Vibration completion callback was received by this step, just turn if off // and skip any clean-up. stopVibrating(); @@ -1310,7 +1322,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { Slog.d(TAG, "Ramp down the vibrator amplitude, step with " + latency + "ms latency."); } - if (mVibratorCallbackReceived) { + if (mVibratorCompleteCallbackReceived) { // Vibration completion callback was received by this step, just turn if off // and skip the rest of the steps to ramp down the vibrator amplitude. stopVibrating(); @@ -1337,7 +1349,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * Represents a step to turn the vibrator off. * * <p>This runs after a timeout on the expected time the vibrator should have finished playing, - * and can anticipated by vibrator complete callbacks. + * and can be brought forward by vibrator complete callbacks. */ private final class OffStep extends SingleVibratorStep { @@ -1389,13 +1401,14 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } @Override - public boolean shouldPlayWhenVibratorComplete(int vibratorId) { + public boolean acceptVibratorCompleteCallback(int vibratorId) { if (controller.getVibratorInfo().getId() == vibratorId) { - mVibratorCallbackReceived = true; + mVibratorCompleteCallbackReceived = true; mNextOffTime = SystemClock.uptimeMillis(); } - // Timings are tightly controlled here, so only anticipate if the vibrator was supposed - // to be ON but has completed prematurely, to turn it back on as soon as possible. + // Timings are tightly controlled here, so only trigger this step if the vibrator was + // supposed to be ON but has completed prematurely, to turn it back on as soon as + // possible. return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0; } @@ -1409,8 +1422,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { Slog.d(TAG, "Running amplitude step with " + latency + "ms latency."); } - if (mVibratorCallbackReceived && latency < 0) { - // This step was anticipated because the vibrator turned off prematurely. + if (mVibratorCompleteCallbackReceived && latency < 0) { + // This step was run early because the vibrator turned off prematurely. // Turn it back on and return this same step to run at the exact right time. mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency); return Arrays.asList(new AmplitudeStep(startTime, controller, effect, diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index b2e34da6545a..63f3af3f3095 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -125,7 +125,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private final long mCapabilities; private final int[] mVibratorIds; private final SparseArray<VibratorController> mVibrators; - private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks(); + private final VibrationThreadCallbacks mVibrationThreadCallbacks = + new VibrationThreadCallbacks(); @GuardedBy("mLock") private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>(); @GuardedBy("mLock") @@ -634,7 +635,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings, mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService, - mVibrationCallbacks); + mVibrationThreadCallbacks); if (mCurrentVibration == null) { return startVibrationThreadLocked(vibThread); @@ -1115,10 +1116,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } /** - * Implementation of {@link VibrationThread.VibrationCallbacks} that controls synced vibrations - * and reports them when finished. + * Implementation of {@link VibrationThread.VibratorManagerHooks} that controls synced + * vibrations and reports them when finished. */ - private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks { + private final class VibrationThreadCallbacks implements VibrationThread.VibratorManagerHooks { @Override public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) { @@ -1153,7 +1154,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override - public void onVibratorsReleased() { + public void onVibrationThreadReleased() { if (DEBUG) { Slog.d(TAG, "Vibrators released after finished vibration"); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 87ba859c3d94..b0efa5b283bb 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -628,6 +628,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // it references to gets removed. This should also be cleared when we move out of pip. private Task mLastParentBeforePip; + // Only set if this instance is a launch-into-pip Activity, points to the + // host Activity the launch-into-pip Activity is originated from. + private ActivityRecord mLaunchIntoPipHostActivity; + boolean firstWindowDrawn; /** Whether the visible window(s) of this activity is drawn. */ private boolean mReportedDrawn; @@ -1225,6 +1229,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mLastParentBeforePip != null) { pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId); } + if (mLaunchIntoPipHostActivity != null) { + pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity); + } mLetterboxUiController.dump(pw, prefix); @@ -1559,10 +1566,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure * {@link #getTask()} is set before this is called. + * + * @param launchIntoPipHostActivity {@link ActivityRecord} as the host Activity for the + * launch-int-pip Activity see also {@link #mLaunchIntoPipHostActivity}. */ - void setLastParentBeforePip() { - mLastParentBeforePip = getTask(); + void setLastParentBeforePip(@Nullable ActivityRecord launchIntoPipHostActivity) { + mLastParentBeforePip = (launchIntoPipHostActivity == null) + ? getTask() + : launchIntoPipHostActivity.getTask(); mLastParentBeforePip.mChildPipActivity = this; + mLaunchIntoPipHostActivity = launchIntoPipHostActivity; } private void clearLastParentBeforePip() { @@ -1570,12 +1583,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLastParentBeforePip.mChildPipActivity = null; mLastParentBeforePip = null; } + mLaunchIntoPipHostActivity = null; } @Nullable Task getLastParentBeforePip() { return mLastParentBeforePip; } + @Nullable ActivityRecord getLaunchIntoPipHostActivity() { + return mLaunchIntoPipHostActivity; + } + private void updateColorTransform() { if (mSurfaceControl != null && mLastAppSaturationInfo != null) { getPendingTransaction().setColorTransform(mSurfaceControl, @@ -1856,6 +1874,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mRotationAnimationHint = rotationAnimation; } + if (options.getLaunchIntoPipParams() != null) { + pictureInPictureArgs = options.getLaunchIntoPipParams(); + } + mOverrideTaskTransition = options.getOverrideTaskTransition(); } @@ -2515,7 +2537,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } removeStartingWindowAnimation(true /* prepareAnimation */); - // TODO(b/215316431): Add tests final Task task = getTask(); if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation() && task != null) { @@ -7695,7 +7716,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * once the starting window is removed in {@link #removeStartingWindow}). * </ul> */ - // TODO(b/215316431): Add tests boolean isEligibleForLetterboxEducation() { return mWmService.mLetterboxConfiguration.getIsEducationEnabled() && mIsEligibleForFixedOrientationLetterbox diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 5ffe2146f71f..ef0ee1206208 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1896,6 +1896,14 @@ class ActivityStarter { mSupervisor.handleNonResizableTaskIfNeeded(startedTask, mPreferredWindowingMode, mPreferredTaskDisplayArea, mTargetRootTask); + // If Activity's launching into PiP, move the mStartActivity immediately to pinned mode. + // Note that mStartActivity and source should be in the same Task at this point. + if (mOptions != null && mOptions.isLaunchIntoPip() + && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()) { + mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity, + sourceRecord, "launch-into-pip"); + } + return START_SUCCESS; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 1741d0f88fd3..049747728ade 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3538,8 +3538,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final float aspectRatio = r.pictureInPictureArgs.getAspectRatio(); final float expandedAspectRatio = r.pictureInPictureArgs.getExpandedAspectRatio(); final List<RemoteAction> actions = r.pictureInPictureArgs.getActions(); - mRootWindowContainer.moveActivityToPinnedRootTask( - r, "enterPictureInPictureMode"); + mRootWindowContainer.moveActivityToPinnedRootTask(r, + null /* launchIntoPipHostActivity */, "enterPictureInPictureMode"); final Task task = r.getTask(); task.setPictureInPictureAspectRatio(aspectRatio, expandedAspectRatio); task.setPictureInPictureActions(actions); @@ -3922,11 +3922,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void onPictureInPictureStateChanged(PictureInPictureUiState pipState) { enforceTaskPermission("onPictureInPictureStateChanged"); - final Task rootPinnedStask = mRootWindowContainer.getDefaultTaskDisplayArea() + final Task rootPinnedTask = mRootWindowContainer.getDefaultTaskDisplayArea() .getRootPinnedTask(); - if (rootPinnedStask != null && rootPinnedStask.getTopMostActivity() != null) { + if (rootPinnedTask != null && rootPinnedTask.getTopMostActivity() != null) { mWindowManager.mAtmService.mActivityClientController.onPictureInPictureStateChanged( - rootPinnedStask.getTopMostActivity(), pipState); + rootPinnedTask.getTopMostActivity(), pipState); } } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 33b807b99719..9893f68c535b 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -106,12 +106,6 @@ class BackNavigationController { synchronized (task.mWmService.mGlobalLock) { activityRecord = task.topRunningActivity(); - if(!activityRecord.info.applicationInfo.isOnBackInvokedCallbackEnabled()) { - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Activity %s: enableOnBackInvokedCallback=false." - + " Returning null BackNavigationInfo.", activityRecord.getName()); - return null; - } - removedWindowContainer = activityRecord; taskWindowConfiguration = task.getTaskInfo().configuration.windowConfiguration; WindowState window = task.getWindow(WindowState::isFocused); diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 132396bbf441..08681119a306 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -652,7 +652,9 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { void prepareSurfaces() { mDimmer.resetDimStates(); super.prepareSurfaces(); + // Bounds need to be relative, as the dim layer is a child. getBounds(mTmpDimBoundsRect); + mTmpDimBoundsRect.offsetTo(0 /* newLeft */, 0 /* newTop */); // If SystemUI is dragging for recents, we want to reset the dim state so any dim layer // on the display level fades out. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2a06d8b8653d..ddfdddc4f65b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -457,8 +457,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private boolean mLastWallpaperVisible = false; - private Rect mBaseDisplayRect = new Rect(); - // Accessed directly by all users. private boolean mLayoutNeeded; int pendingLayoutChanges; @@ -488,9 +486,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private final Rect mTmpRect2 = new Rect(); private final Region mTmpRegion = new Region(); - /** Used for handing back size of display */ - private final Rect mTmpBounds = new Rect(); - private final Configuration mTmpConfiguration = new Configuration(); /** Remove this display when animation on it has completed. */ @@ -1362,8 +1357,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayRotation; } - void setInsetProvider(@InternalInsetsType int type, WindowState win, - @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider){ + void setInsetProvider(@InternalInsetsType int type, WindowContainer win, + @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider) { setInsetProvider(type, win, frameProvider, null /* imeFrameProvider */); } @@ -1377,10 +1372,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * @param imeFrameProvider Function to compute the frame when dispatching insets to the IME, or * {@code null} if the normal frame should be taken. */ - void setInsetProvider(@InternalInsetsType int type, WindowState win, - @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider, - @Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) { - mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider, + void setInsetProvider(@InternalInsetsType int type, WindowContainer win, + @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider, + @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider) { + mInsetsStateController.getSourceProvider(type).setWindowContainer(win, frameProvider, imeFrameProvider); } @@ -2080,8 +2075,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId, mDisplayInfo); - mBaseDisplayRect.set(0, 0, dw, dh); - if (isDefaultDisplay) { mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics, mCompatDisplayMetrics); @@ -2213,14 +2206,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ void computeScreenConfiguration(Configuration config) { final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config); - calculateBounds(displayInfo, mTmpBounds); - config.windowConfiguration.setBounds(mTmpBounds); - config.windowConfiguration.setMaxBounds(mTmpBounds); + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; + mTmpRect.set(0, 0, dw, dh); + config.windowConfiguration.setBounds(mTmpRect); + config.windowConfiguration.setMaxBounds(mTmpRect); config.windowConfiguration.setWindowingMode(getWindowingMode()); config.windowConfiguration.setDisplayWindowingMode(getWindowingMode()); - final int dw = displayInfo.logicalWidth; - final int dh = displayInfo.logicalHeight; computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, config.uiMode, displayInfo.displayCutout); @@ -2844,10 +2837,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Update base (override) display metrics. */ void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) { - final int originalWidth = mBaseDisplayWidth; - final int originalHeight = mBaseDisplayHeight; - final int originalDensity = mBaseDisplayDensity; - mBaseDisplayWidth = baseWidth; mBaseDisplayHeight = baseHeight; mBaseDisplayDensity = baseDensity; @@ -2867,12 +2856,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp + mBaseDisplayHeight + " on display:" + getDisplayId()); } } - - if (mBaseDisplayWidth != originalWidth || mBaseDisplayHeight != originalHeight - || mBaseDisplayDensity != originalDensity) { - mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight); - updateBounds(); - } } /** @@ -3021,7 +3004,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (focusedTask == null) { mTouchExcludeRegion.setEmpty(); } else { - mTouchExcludeRegion.set(mBaseDisplayRect); + mTouchExcludeRegion.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); mTmpRect.setEmpty(); mTmpRect2.setEmpty(); @@ -3822,7 +3805,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final int imePid = mInputMethodWindow.mSession.mPid; mAtmService.onImeWindowSetOnDisplayArea(imePid, mImeWindowsContainer); } - mInsetsStateController.getSourceProvider(ITYPE_IME).setWindow(win, + mInsetsStateController.getSourceProvider(ITYPE_IME).setWindowContainer(win, mDisplayPolicy.getImeSourceFrameProvider(), null /* imeFrameProvider */); computeImeTarget(true /* updateImeTarget */); updateImeControlTarget(); @@ -4587,25 +4570,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - private void updateBounds() { - calculateBounds(mDisplayInfo, mTmpBounds); - setBounds(mTmpBounds); - } - - // Determines the current display bounds based on the current state - private void calculateBounds(DisplayInfo displayInfo, Rect out) { - // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked. - final int rotation = displayInfo.rotation; - boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; - final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight; - int width = displayInfo.logicalWidth; - int left = (physWidth - width) / 2; - int height = displayInfo.logicalHeight; - int top = (physHeight - height) / 2; - out.set(left, top, left + width, top + height); - } - private void getBounds(Rect out, @Rotation int rotation) { getBounds(out); @@ -5561,8 +5525,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void onDisplayChanged() { - mDisplay.getRealSize(mTmpDisplaySize); - setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); final int lastDisplayState = mDisplayInfo.state; updateDisplayInfo(); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 30fffd32a258..4148d8b7d853 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1106,8 +1106,8 @@ public class DisplayPolicy { break; case TYPE_STATUS_BAR: mStatusBar = win; - final TriConsumer<DisplayFrames, WindowState, Rect> gestureFrameProvider = - (displayFrames, windowState, rect) -> { + final TriConsumer<DisplayFrames, WindowContainer, Rect> gestureFrameProvider = + (displayFrames, windowContainer, rect) -> { rect.bottom = rect.top + getStatusBarHeight(displayFrames); final DisplayCutout cutout = displayFrames.mInsetsState.getDisplayCutout(); @@ -1128,24 +1128,25 @@ public class DisplayPolicy { case TYPE_NAVIGATION_BAR: mNavigationBar = win; mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win, - (displayFrames, windowState, inOutFrame) -> { + (displayFrames, windowContainer, inOutFrame) -> { if (!mNavButtonForcedVisible) { - inOutFrame.inset(windowState.getLayoutingAttrs( + inOutFrame.inset(win.getLayoutingAttrs( displayFrames.mRotation).providedInternalInsets); inOutFrame.inset(win.mGivenContentInsets); } }, // For IME we use regular frame. - (displayFrames, windowState, inOutFrame) -> - inOutFrame.set(windowState.getFrame())); + (displayFrames, windowContainer, inOutFrame) -> { + inOutFrame.set(win.getFrame()); + }); mDisplayContent.setInsetProvider(ITYPE_BOTTOM_MANDATORY_GESTURES, win, - (displayFrames, windowState, inOutFrame) -> { + (displayFrames, windowContainer, inOutFrame) -> { inOutFrame.top -= mBottomGestureAdditionalInset; }); mDisplayContent.setInsetProvider(ITYPE_LEFT_GESTURES, win, - (displayFrames, windowState, inOutFrame) -> { + (displayFrames, windowContainer, inOutFrame) -> { final int leftSafeInset = Math.max(displayFrames.mDisplayCutoutSafe.left, 0); inOutFrame.left = 0; @@ -1154,7 +1155,7 @@ public class DisplayPolicy { inOutFrame.right = leftSafeInset + mLeftGestureInset; }); mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win, - (displayFrames, windowState, inOutFrame) -> { + (displayFrames, windowContainer, inOutFrame) -> { final int rightSafeInset = Math.min(displayFrames.mDisplayCutoutSafe.right, displayFrames.mUnrestricted.right); @@ -1164,8 +1165,8 @@ public class DisplayPolicy { inOutFrame.right = displayFrames.mDisplayWidth; }); mDisplayContent.setInsetProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT, win, - (displayFrames, windowState, inOutFrame) -> { - if ((windowState.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0 + (displayFrames, windowContainer, inOutFrame) -> { + if ((win.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0 || mNavigationBarLetsThroughTaps) { inOutFrame.setEmpty(); } @@ -1176,11 +1177,13 @@ public class DisplayPolicy { default: if (attrs.providesInsetsTypes != null) { for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) { - final TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider = + final TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider = !attrs.providedInternalImeInsets.equals(Insets.NONE) - ? (displayFrames, windowState, inOutFrame) -> - inOutFrame.inset(windowState.getLayoutingAttrs( - displayFrames.mRotation).providedInternalImeInsets) + ? (displayFrames, windowContainer, inOutFrame) -> { + inOutFrame.inset(win.getLayoutingAttrs( + displayFrames.mRotation) + .providedInternalImeInsets); + } : null; switch (insetsType) { case ITYPE_STATUS_BAR: @@ -1201,10 +1204,9 @@ public class DisplayPolicy { break; } mDisplayContent.setInsetProvider(insetsType, win, (displayFrames, - windowState, inOutFrame) -> { - inOutFrame.inset( - windowState.getLayoutingAttrs(displayFrames.mRotation) - .providedInternalInsets); + windowContainer, inOutFrame) -> { + inOutFrame.inset(win.getLayoutingAttrs( + displayFrames.mRotation).providedInternalInsets); inOutFrame.inset(win.mGivenContentInsets); }, imeFrameProvider); mInsetsSourceWindowsExceptIme.add(win); @@ -1230,8 +1232,13 @@ public class DisplayPolicy { } } - TriConsumer<DisplayFrames, WindowState, Rect> getImeSourceFrameProvider() { - return (displayFrames, windowState, inOutFrame) -> { + TriConsumer<DisplayFrames, WindowContainer, Rect> getImeSourceFrameProvider() { + return (displayFrames, windowContainer, inOutFrame) -> { + WindowState windowState = windowContainer.asWindowState(); + if (windowState == null) { + throw new IllegalArgumentException("IME insets must be provided by a window."); + } + if (mNavigationBar != null && navigationBarPosition(displayFrames.mRotation) == NAV_BAR_BOTTOM) { // In gesture navigation, nav bar frame is larger than frame to calculate insets. diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index cbefe7f3ade4..8f972209d124 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -113,8 +113,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { private void reportImeDrawnForOrganizer(InsetsControlTarget caller) { if (caller.getWindow() != null && caller.getWindow().getTask() != null) { if (caller.getWindow().getTask().isOrganized()) { - mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask( - caller.getWindow().getTask()); + mWindowContainer.mWmService.mAtmService.mTaskOrganizerController + .reportImeDrawnOnTask(caller.getWindow().getTask()); } } } @@ -173,12 +173,18 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } void checkShowImePostLayout() { + if (mWindowContainer == null) { + return; + } + WindowState windowState = mWindowContainer.asWindowState(); + if (windowState == null) { + throw new IllegalArgumentException("IME insets must be provided by a window."); + } // check if IME is drawn if (mIsImeLayoutDrawn || (isReadyToShowIme() - && mWin != null - && mWin.isDrawn() - && !mWin.mGivenInsetsPending)) { + && windowState.isDrawn() + && !windowState.mGivenInsetsPending)) { mIsImeLayoutDrawn = true; // show IME if InputMethodService requested it to be shown. if (mShowImeRunner != null) { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index d28dfd54c37f..433f107133cc 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -19,24 +19,36 @@ package com.android.server.wm; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.InsetsController.ANIMATION_TYPE_HIDE; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; +import static android.view.InsetsState.ITYPE_CAPTION_BAR; +import static android.view.InsetsState.ITYPE_CLIMATE_BAR; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_IME; +import static android.view.InsetsState.ITYPE_INVALID; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.SyncRtSurfaceTransactionApplier.applyParams; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.StatusBarManager; +import android.app.WindowConfiguration; import android.graphics.Insets; import android.graphics.Rect; +import android.util.ArrayMap; import android.util.IntArray; import android.util.SparseArray; import android.view.InsetsAnimationControlCallbacks; @@ -164,8 +176,9 @@ class InsetsPolicy { } boolean isHidden(@InternalInsetsType int type) { - final InsetsSourceProvider provider = mStateController.peekSourceProvider(type); - return provider != null && provider.hasWindow() && !provider.getSource().isVisible(); + final InsetsSourceProvider provider = mStateController.peekSourceProvider(type); + return provider != null && provider.hasWindowContainer() + && !provider.getSource().isVisible(); } void showTransient(@InternalInsetsType int[] types, boolean isGestureOnSystemBar) { @@ -235,10 +248,11 @@ class InsetsPolicy { } /** - * @see InsetsStateController#getInsetsForWindow + * Adjusts the sources in {@code originalState} to account for things like transient bars, IME + * & rounded corners. */ - InsetsState getInsetsForWindow(WindowState target, boolean includesTransient) { - final InsetsState originalState = mStateController.getInsetsForWindow(target); + InsetsState adjustInsetsForWindow(WindowState target, InsetsState originalState, + boolean includesTransient) { InsetsState state; if (!includesTransient) { state = adjustVisibilityForTransientTypes(originalState); @@ -249,19 +263,133 @@ class InsetsPolicy { return adjustInsetsForRoundedCorners(target, state, state == originalState); } - InsetsState getInsetsForWindow(WindowState target) { - return getInsetsForWindow(target, false); + InsetsState adjustInsetsForWindow(WindowState target, InsetsState originalState) { + return adjustInsetsForWindow(target, originalState, false); } - /** - * @see InsetsStateController#getInsetsForWindowMetrics + * @see WindowState#getInsetsState() */ InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) { - final InsetsState originalState = mStateController.getInsetsForWindowMetrics(attrs); + final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs); + final WindowToken token = mDisplayContent.getWindowToken(attrs.token); + if (token != null) { + final InsetsState rotatedState = token.getFixedRotationTransformInsetsState(); + if (rotatedState != null) { + return rotatedState; + } + } + final boolean alwaysOnTop = token != null && token.isAlwaysOnTop(); + // Always use windowing mode fullscreen when get insets for window metrics to make sure it + // contains all insets types. + final InsetsState originalState = mDisplayContent.getInsetsPolicy() + .enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop, + mStateController.getRawInsetsState()); return adjustVisibilityForTransientTypes(originalState); } + private static @InternalInsetsType int getInsetsTypeForLayoutParams( + WindowManager.LayoutParams attrs) { + @WindowManager.LayoutParams.WindowType int type = attrs.type; + switch (type) { + case TYPE_STATUS_BAR: + return ITYPE_STATUS_BAR; + case TYPE_NAVIGATION_BAR: + return ITYPE_NAVIGATION_BAR; + case TYPE_INPUT_METHOD: + return ITYPE_IME; + } + + // If not one of the types above, check whether an internal inset mapping is specified. + if (attrs.providesInsetsTypes != null) { + for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) { + switch (insetsType) { + case ITYPE_STATUS_BAR: + case ITYPE_NAVIGATION_BAR: + case ITYPE_CLIMATE_BAR: + case ITYPE_EXTRA_NAVIGATION_BAR: + return insetsType; + } + } + } + + return ITYPE_INVALID; + } + + + /** + * Modifies the given {@code state} according to the {@code type} (Inset type) provided by + * the target. + * When performing layout of the target or dispatching insets to the target, we need to exclude + * sources which should not be visible to the target. e.g., the source which represents the + * target window itself, and the IME source when the target is above IME. We also need to + * exclude certain types of insets source for client within specific windowing modes. + * + * @param type the inset type provided by the target + * @param windowingMode the windowing mode of the target + * @param isAlwaysOnTop is the target always on top + * @param state the input inset state containing all the sources + * @return The state stripped of the necessary information. + */ + InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type, + @WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop, + InsetsState state) { + boolean stateCopied = false; + + if (type != ITYPE_INVALID) { + state = new InsetsState(state); + stateCopied = true; + state.removeSource(type); + + // Navigation bar doesn't get influenced by anything else + if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) { + state.removeSource(ITYPE_IME); + state.removeSource(ITYPE_STATUS_BAR); + state.removeSource(ITYPE_CLIMATE_BAR); + state.removeSource(ITYPE_CAPTION_BAR); + state.removeSource(ITYPE_NAVIGATION_BAR); + state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR); + } + + // Status bar doesn't get influenced by caption bar + if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) { + state.removeSource(ITYPE_CAPTION_BAR); + } + + // IME needs different frames for certain cases (e.g. navigation bar in gesture nav). + if (type == ITYPE_IME) { + ArrayMap<Integer, InsetsSourceProvider> providers = mStateController + .getSourceProviders(); + for (int i = providers.size() - 1; i >= 0; i--) { + InsetsSourceProvider otherProvider = providers.valueAt(i); + if (otherProvider.overridesImeFrame()) { + InsetsSource override = + new InsetsSource( + state.getSource(otherProvider.getSource().getType())); + override.setFrame(otherProvider.getImeOverrideFrame()); + state.addSource(override); + } + } + } + } + + if (WindowConfiguration.isFloating(windowingMode) + || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) { + if (!stateCopied) { + state = new InsetsState(state); + stateCopied = true; + } + state.removeSource(ITYPE_STATUS_BAR); + state.removeSource(ITYPE_NAVIGATION_BAR); + state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR); + if (windowingMode == WINDOWING_MODE_PINNED) { + state.removeSource(ITYPE_IME); + } + } + + return state; + } + private InsetsState adjustVisibilityForTransientTypes(InsetsState originalState) { InsetsState state = originalState; for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) { @@ -644,7 +772,7 @@ class InsetsPolicy { } mAnimatingShown = show; - final InsetsState state = getInsetsForWindow(mFocusedWin); + final InsetsState state = mFocusedWin.getInsetsState(); // We are about to playing the default animation. Passing a null frame indicates // the controlled types should be animated regardless of the frame. diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 21eea940f135..4c7a29754234 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -69,7 +69,7 @@ class InsetsSourceProvider { protected final DisplayContent mDisplayContent; protected final @NonNull InsetsSource mSource; - protected WindowState mWin; + protected WindowContainer mWindowContainer; private final Rect mTmpRect = new Rect(); private final InsetsStateController mStateController; @@ -80,8 +80,8 @@ class InsetsSourceProvider { private @Nullable InsetsControlTarget mFakeControlTarget; private @Nullable ControlAdapter mAdapter; - private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider; - private TriConsumer<DisplayFrames, WindowState, Rect> mImeFrameProvider; + private TriConsumer<DisplayFrames, WindowContainer, Rect> mFrameProvider; + private TriConsumer<DisplayFrames, WindowContainer, Rect> mImeFrameProvider; private final Rect mImeOverrideFrame = new Rect(); private boolean mIsLeashReadyForDispatching; private final Rect mLastSourceFrame = new Rect(); @@ -100,7 +100,8 @@ class InsetsSourceProvider { private boolean mClientVisible; /** - * Whether the window is available and considered visible as in {@link WindowState#isVisible}. + * Whether the window container is available and considered visible as in + * {@link WindowContainer#isVisible}. */ private boolean mServerVisible; @@ -109,8 +110,8 @@ class InsetsSourceProvider { private final boolean mControllable; /** - * Whether to forced the dimensions of the source window to the inset frame and crop out any - * overflow. + * Whether to forced the dimensions of the source window container to the inset frame and crop + * out any overflow. * Used to crop the taskbar inset source when a task animation is occurring to hide the taskbar * rounded corners overlays. * @@ -152,42 +153,42 @@ class InsetsSourceProvider { } /** - * Updates the window that currently backs this source. + * Updates the window container that currently backs this source. * - * @param win The window that links to this source. + * @param windowContainer The window container that links to this source. * @param frameProvider Based on display frame state and the window, calculates the resulting * frame that should be reported to clients. * @param imeFrameProvider Based on display frame state and the window, calculates the resulting * frame that should be reported to IME. */ - void setWindow(@Nullable WindowState win, - @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider, - @Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) { - if (mWin != null) { + void setWindowContainer(@Nullable WindowContainer windowContainer, + @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider, + @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider) { + if (mWindowContainer != null) { if (mControllable) { - mWin.setControllableInsetProvider(null); + mWindowContainer.setControllableInsetProvider(null); } - // The window may be animating such that we can hand out the leash to the control - // target. Revoke the leash by cancelling the animation to correct the state. + // The window container may be animating such that we can hand out the leash to the + // control target. Revoke the leash by cancelling the animation to correct the state. // TODO: Ideally, we should wait for the animation to finish so previous window can // animate-out as new one animates-in. - mWin.cancelAnimation(); - mWin.mProvidedInsetsSources.remove(mSource.getType()); + mWindowContainer.cancelAnimation(); + mWindowContainer.getProvidedInsetsSources().remove(mSource.getType()); mSeamlessRotating = false; } - ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s", win, - InsetsState.typeToString(mSource.getType())); - mWin = win; + ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s", + windowContainer, InsetsState.typeToString(mSource.getType())); + mWindowContainer = windowContainer; mFrameProvider = frameProvider; mImeFrameProvider = imeFrameProvider; - if (win == null) { + if (windowContainer == null) { setServerVisible(false); mSource.setFrame(new Rect()); mSource.setVisibleFrame(null); } else { - mWin.mProvidedInsetsSources.put(mSource.getType(), mSource); + mWindowContainer.getProvidedInsetsSources().put(mSource.getType(), mSource); if (mControllable) { - mWin.setControllableInsetProvider(this); + mWindowContainer.setControllableInsetProvider(this); if (mPendingControlTarget != null) { updateControlForTarget(mPendingControlTarget, true /* force */); mPendingControlTarget = null; @@ -197,18 +198,39 @@ class InsetsSourceProvider { } /** - * @return Whether there is a window which backs this source. + * @return Whether there is a window container which backs this source. */ - boolean hasWindow() { - return mWin != null; + boolean hasWindowContainer() { + return mWindowContainer != null; } /** * The source frame can affect the layout of other windows, so this should be called once the - * window gets laid out. + * window container gets laid out. */ void updateSourceFrame() { - if (mWin == null || mWin.mGivenInsetsPending) { + if (mWindowContainer == null) { + return; + } + WindowState win = mWindowContainer.asWindowState(); + + if (win == null) { + // For all the non window WindowContainers. + if (mServerVisible) { + mTmpRect.set(mWindowContainer.getBounds()); + if (mFrameProvider != null) { + mFrameProvider.accept(mWindowContainer.getDisplayContent().mDisplayFrames, + mWindowContainer, mTmpRect); + } + } else { + mTmpRect.setEmpty(); + } + mSource.setFrame(mTmpRect); + mSource.setVisibleFrame(null); + return; + } + + if (win.mGivenInsetsPending) { // If the given insets are pending, they are not reliable for now. The source frame // should be updated after the new given insets are sent to window manager. return; @@ -218,11 +240,12 @@ class InsetsSourceProvider { // frame may not yet determined that server side doesn't think the window is ready to // visible. (i.e. No surface, pending insets that were given during layout, etc..) if (mServerVisible) { - mTmpRect.set(mWin.getFrame()); + mTmpRect.set(win.getFrame()); if (mFrameProvider != null) { - mFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin, mTmpRect); + mFrameProvider.accept(mWindowContainer.getDisplayContent().mDisplayFrames, + mWindowContainer, mTmpRect); } else { - mTmpRect.inset(mWin.mGivenContentInsets); + mTmpRect.inset(win.mGivenContentInsets); } } else { mTmpRect.setEmpty(); @@ -230,15 +253,17 @@ class InsetsSourceProvider { mSource.setFrame(mTmpRect); if (mImeFrameProvider != null) { - mImeOverrideFrame.set(mWin.getFrame()); - mImeFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin, + mImeOverrideFrame.set(win.getFrame()); + mImeFrameProvider.accept(mWindowContainer.getDisplayContent().mDisplayFrames, + mWindowContainer, mImeOverrideFrame); } - if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0 - || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) { - mTmpRect.set(mWin.getFrame()); - mTmpRect.inset(mWin.mGivenVisibleInsets); + if (win.mGivenVisibleInsets.left != 0 || win.mGivenVisibleInsets.top != 0 + || win.mGivenVisibleInsets.right != 0 + || win.mGivenVisibleInsets.bottom != 0) { + mTmpRect.set(win.getFrame()); + mTmpRect.inset(win.mGivenVisibleInsets); mSource.setVisibleFrame(mTmpRect); } else { mSource.setVisibleFrame(null); @@ -253,7 +278,7 @@ class InsetsSourceProvider { source.setVisible(mSource.isVisible()); mTmpRect.set(winFrame); if (mFrameProvider != null) { - mFrameProvider.accept(displayFrames, mWin, mTmpRect); + mFrameProvider.accept(displayFrames, mWindowContainer, mTmpRect); } source.setFrame(mTmpRect); return source; @@ -263,27 +288,30 @@ class InsetsSourceProvider { * Called when a layout pass has occurred. */ void onPostLayout() { - if (mWin == null) { + if (mWindowContainer == null) { return; } - - setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy()); + WindowState windowState = mWindowContainer.asWindowState(); + boolean isServerVisible = windowState != null + ? windowState.wouldBeVisibleIfPolicyIgnored() && windowState.isVisibleByPolicy() + : mWindowContainer.isVisibleRequested(); + setServerVisible(isServerVisible); updateSourceFrame(); if (mControl != null) { boolean changed = false; final Point position = getWindowFrameSurfacePosition(); if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) { changed = true; - if (mWin.getWindowFrames().didFrameSizeChange() && mWin.mWinAnimator.getShown() - && mWin.okToDisplay()) { - mWin.applyWithNextDraw(mSetLeashPositionConsumer); + if (windowState != null && windowState.getWindowFrames().didFrameSizeChange() + && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { + windowState.applyWithNextDraw(mSetLeashPositionConsumer); } else { - mSetLeashPositionConsumer.accept(mWin.getSyncTransaction()); + mSetLeashPositionConsumer.accept(mWindowContainer.getSyncTransaction()); } } if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) { final Insets insetsHint = mSource.calculateInsets( - mWin.getBounds(), true /* ignoreVisibility */); + mWindowContainer.getBounds(), true /* ignoreVisibility */); if (!insetsHint.equals(mControl.getInsetsHint())) { changed = true; mControl.setInsetsHint(insetsHint); @@ -297,17 +325,19 @@ class InsetsSourceProvider { } private Point getWindowFrameSurfacePosition() { + WindowState win = mWindowContainer.asWindowState(); if (mControl != null) { final AsyncRotationController controller = - mWin.mDisplayContent.getAsyncRotationController(); - if (controller != null && controller.shouldFreezeInsetsPosition(mWin)) { + win.mDisplayContent.getAsyncRotationController(); + if (controller != null && controller.shouldFreezeInsetsPosition(win)) { // Use previous position because the fade-out animation runs in old rotation. return mControl.getSurfacePosition(); } } - final Rect frame = mWin.getFrame(); + final Rect frame = mWindowContainer.asWindowState() != null + ? mWindowContainer.asWindowState().getFrame() : mWindowContainer.getBounds(); final Point position = new Point(); - mWin.transformFrameToSurfacePosition(frame.left, frame.top, position); + mWindowContainer.transformFrameToSurfacePosition(frame.left, frame.top, position); return position; } @@ -322,8 +352,8 @@ class InsetsSourceProvider { } /** - * Ensures that the inset source window is cropped so that anything that doesn't fit within the - * inset frame is cropped out until removeCropToProvidingInsetsBounds is called. + * Ensures that the inset source window container is cropped so that anything that doesn't fit + * within the inset frame is cropped out until removeCropToProvidingInsetsBounds is called. * * The inset source surface will get cropped to the be of the size of the insets it's providing. * @@ -342,9 +372,10 @@ class InsetsSourceProvider { void setCropToProvidingInsetsBounds(Transaction t) { mCropToProvidingInsets = true; - if (mWin != null && mWin.mSurfaceAnimator.hasLeash()) { + if (mWindowContainer != null && mWindowContainer.mSurfaceAnimator.hasLeash()) { // apply to existing leash - t.setWindowCrop(mWin.mSurfaceAnimator.mLeash, getProvidingInsetsBoundsCropRect()); + t.setWindowCrop(mWindowContainer.mSurfaceAnimator.mLeash, + getProvidingInsetsBoundsCropRect()); } } @@ -359,13 +390,15 @@ class InsetsSourceProvider { mCropToProvidingInsets = false; // apply to existing leash - if (mWin != null && mWin.mSurfaceAnimator.hasLeash()) { - t.setWindowCrop(mWin.mSurfaceAnimator.mLeash, null); + if (mWindowContainer != null && mWindowContainer.mSurfaceAnimator.hasLeash()) { + t.setWindowCrop(mWindowContainer.mSurfaceAnimator.mLeash, null); } } private Rect getProvidingInsetsBoundsCropRect() { - Rect sourceWindowFrame = mWin.getFrame(); + Rect sourceWindowFrame = mWindowContainer.asWindowState() != null + ? mWindowContainer.asWindowState().getFrame() + : mWindowContainer.getBounds(); Rect insetFrame = getSource().getFrame(); // The rectangle in buffer space we want to crop to @@ -384,11 +417,11 @@ class InsetsSourceProvider { return; } - if (mWin != null && mWin.getSurfaceControl() == null) { + if (mWindowContainer != null && mWindowContainer.getSurfaceControl() == null) { // if window doesn't have a surface, set it null and return. - setWindow(null, null, null); + setWindowContainer(null, null, null); } - if (mWin == null) { + if (mWindowContainer == null) { mPendingControlTarget = target; return; } @@ -397,7 +430,7 @@ class InsetsSourceProvider { } if (target == null) { // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields. - mWin.cancelAnimation(); + mWindowContainer.cancelAnimation(); setClientVisible(InsetsState.getDefaultVisibility(mSource.getType())); return; } @@ -407,7 +440,7 @@ class InsetsSourceProvider { setClientVisible(target.getRequestedVisibility(mSource.getType())); } final Transaction t = mDisplayContent.getSyncTransaction(); - mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */, + mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */, ANIMATION_TYPE_INSETS_CONTROL); // The leash was just created. We cannot dispatch it until its surface transaction is @@ -418,16 +451,16 @@ class InsetsSourceProvider { mControlTarget = target; updateVisibility(); mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition, - mSource.calculateInsets(mWin.getBounds(), true /* ignoreVisibility */)); + mSource.calculateInsets(mWindowContainer.getBounds(), true /* ignoreVisibility */)); ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource Control %s for target %s", mControl, mControlTarget); } void startSeamlessRotation() { - if (!mSeamlessRotating) { - mSeamlessRotating = true; - mWin.cancelAnimation(); - } + if (!mSeamlessRotating) { + mSeamlessRotating = true; + mWindowContainer.cancelAnimation(); + } } void finishSeamlessRotation() { @@ -475,10 +508,13 @@ class InsetsSourceProvider { } private boolean isMirroredSource() { - if (mWin == null) { + if (mWindowContainer == null) { + return false; + } + if (mWindowContainer.asWindowState() == null) { return false; } - final int[] provides = mWin.mAttrs.providesInsetsTypes; + final int[] provides = ((WindowState) mWindowContainer).mAttrs.providesInsetsTypes; if (provides == null) { return false; } @@ -542,9 +578,9 @@ class InsetsSourceProvider { pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching); pw.print(" mImeOverrideFrame="); pw.print(mImeOverrideFrame.toShortString()); pw.println(); - if (mWin != null) { - pw.print(prefix + "mWin="); - pw.println(mWin); + if (mWindowContainer != null) { + pw.print(prefix + "mWindowContainer="); + pw.println(mWindowContainer); } if (mAdapter != null) { pw.print(prefix + "mAdapter="); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index a1468cc60682..32e70d926f2d 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -16,30 +16,20 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_CLIMATE_BAR; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_IME; -import static android.view.InsetsState.ITYPE_INVALID; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.mandatorySystemGestures; import static android.view.WindowInsets.Type.systemGestures; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.WindowConfiguration; -import android.app.WindowConfiguration.WindowingMode; import android.graphics.Rect; import android.os.Trace; import android.util.ArrayMap; @@ -49,8 +39,6 @@ import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams.WindowType; import com.android.internal.protolog.common.ProtoLog; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -103,134 +91,6 @@ class InsetsStateController { mDisplayContent = displayContent; } - /** - * Gets the insets state from the perspective of the target. When performing layout of the - * target or dispatching insets to the target, we need to exclude sources which should not be - * visible to the target. e.g., the source which represents the target window itself, and the - * IME source when the target is above IME. We also need to exclude certain types of insets - * source for client within specific windowing modes. - * This is to get the insets for a window layout on the screen. If the window is not there, use - * the {@link #getInsetsForWindowMetrics} to get insets instead. - * - * @param target The window associate with the perspective. - * @return The state stripped of the necessary information. - */ - InsetsState getInsetsForWindow(@NonNull WindowState target) { - final InsetsState rotatedState = target.mToken.getFixedRotationTransformInsetsState(); - if (rotatedState != null) { - return rotatedState; - } - final InsetsSourceProvider provider = target.getControllableInsetProvider(); - final @InternalInsetsType int type = provider != null - ? provider.getSource().getType() : ITYPE_INVALID; - return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(), - target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : - (target.mAttrs.receiveInsetsIgnoringZOrder ? mState : - target.mAboveInsetsState)); - } - - InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) { - final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs); - final WindowToken token = mDisplayContent.getWindowToken(attrs.token); - if (token != null) { - final InsetsState rotatedState = token.getFixedRotationTransformInsetsState(); - if (rotatedState != null) { - return rotatedState; - } - } - final boolean alwaysOnTop = token != null && token.isAlwaysOnTop(); - // Always use windowing mode fullscreen when get insets for window metrics to make sure it - // contains all insets types. - return getInsetsForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop, mState); - } - - private static @InternalInsetsType - int getInsetsTypeForLayoutParams(WindowManager.LayoutParams attrs) { - @WindowType int type = attrs.type; - switch (type) { - case TYPE_STATUS_BAR: - return ITYPE_STATUS_BAR; - case TYPE_NAVIGATION_BAR: - return ITYPE_NAVIGATION_BAR; - case TYPE_INPUT_METHOD: - return ITYPE_IME; - } - - // If not one of the types above, check whether an internal inset mapping is specified. - if (attrs.providesInsetsTypes != null) { - for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) { - switch (insetsType) { - case ITYPE_STATUS_BAR: - case ITYPE_NAVIGATION_BAR: - case ITYPE_CLIMATE_BAR: - case ITYPE_EXTRA_NAVIGATION_BAR: - return insetsType; - } - } - } - - return ITYPE_INVALID; - } - - /** - * @see #getInsetsForWindow - * @see #getInsetsForWindowMetrics - */ - private InsetsState getInsetsForTarget(@InternalInsetsType int type, - @WindowingMode int windowingMode, boolean isAlwaysOnTop, InsetsState state) { - boolean stateCopied = false; - - if (type != ITYPE_INVALID) { - state = new InsetsState(state); - stateCopied = true; - state.removeSource(type); - - // Navigation bar doesn't get influenced by anything else - if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) { - state.removeSource(ITYPE_STATUS_BAR); - state.removeSource(ITYPE_CLIMATE_BAR); - state.removeSource(ITYPE_CAPTION_BAR); - state.removeSource(ITYPE_NAVIGATION_BAR); - state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR); - } - - // Status bar doesn't get influenced by caption bar - if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) { - state.removeSource(ITYPE_CAPTION_BAR); - } - - // IME needs different frames for certain cases (e.g. navigation bar in gesture nav). - if (type == ITYPE_IME) { - for (int i = mProviders.size() - 1; i >= 0; i--) { - InsetsSourceProvider otherProvider = mProviders.valueAt(i); - if (otherProvider.overridesImeFrame()) { - InsetsSource override = - new InsetsSource( - state.getSource(otherProvider.getSource().getType())); - override.setFrame(otherProvider.getImeOverrideFrame()); - state.addSource(override); - } - } - } - } - - if (WindowConfiguration.isFloating(windowingMode) - || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) { - if (!stateCopied) { - state = new InsetsState(state); - stateCopied = true; - } - state.removeSource(ITYPE_STATUS_BAR); - state.removeSource(ITYPE_NAVIGATION_BAR); - state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR); - if (windowingMode == WINDOWING_MODE_PINNED) { - state.removeSource(ITYPE_IME); - } - } - - return state; - } - InsetsState getRawInsetsState() { return mState; } @@ -248,6 +108,10 @@ class InsetsStateController { return result; } + ArrayMap<Integer, InsetsSourceProvider> getSourceProviders() { + return mProviders; + } + /** * @return The provider of a specific type. */ @@ -303,7 +167,7 @@ class InsetsStateController { final InsetsState aboveInsetsState = new InsetsState(); aboveInsetsState.set(mState, displayCutout() | systemGestures() | mandatorySystemGestures()); - final SparseArray<InsetsSource> winProvidedSources = win.mProvidedInsetsSources; + final SparseArray<InsetsSource> winProvidedSources = win.getProvidedInsetsSources(); final ArrayList<WindowState> insetsChangedWindows = new ArrayList<>(); mDisplayContent.forAllWindows(w -> { if (aboveWin[0]) { @@ -315,7 +179,7 @@ class InsetsStateController { } return winProvidedSources.size() == 0; } else { - final SparseArray<InsetsSource> providedSources = w.mProvidedInsetsSources; + final SparseArray<InsetsSource> providedSources = w.getProvidedInsetsSources(); for (int i = providedSources.size() - 1; i >= 0; i--) { aboveInsetsState.addSource(providedSources.valueAt(i)); } @@ -382,7 +246,7 @@ class InsetsStateController { final InsetsState state = displayFrames.mInsetsState; for (int i = mProviders.size() - 1; i >= 0; i--) { final InsetsSourceProvider provider = mProviders.valueAt(i); - if (provider.mWin == win) { + if (provider.mWindowContainer == win) { state.addSource(provider.createSimulatedSource(displayFrames, winFrame)); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d5350183cdcb..8ab2ee03a1e3 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1979,7 +1979,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> onTop); } - void moveActivityToPinnedRootTask(ActivityRecord r, String reason) { + void moveActivityToPinnedRootTask(@NonNull ActivityRecord r, + @Nullable ActivityRecord launchIntoPipHostActivity, String reason) { mService.deferWindowLayout(); final TaskDisplayArea taskDisplayArea = r.getDisplayArea(); @@ -2030,7 +2031,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> .setHasBeenVisible(true) .build(); // Establish bi-directional link between the original and pinned task. - r.setLastParentBeforePip(); + r.setLastParentBeforePip(launchIntoPipHostActivity); // It's possible the task entering PIP is in freeform, so save the last // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore // to its previous freeform bounds. @@ -2091,6 +2092,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> r.setWindowingMode(intermediateWindowingMode); r.mWaitForEnteringPinnedMode = true; rootTask.setWindowingMode(WINDOWING_MODE_PINNED); + // Set the launch bounds for launch-into-pip Activity on the root task. + if (r.getOptions() != null && r.getOptions().isLaunchIntoPip()) { + rootTask.setBounds(r.getOptions().getLaunchBounds()); + } rootTask.setDeferTaskAppear(false); // Reset the state that indicates it can enter PiP while pausing after we've moved it diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index d1460f41cad6..6bb63d933353 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -108,15 +108,12 @@ class ScreenRotationAnimation { */ private SurfaceControl mBackColorSurface; private BlackFrame mEnteringBlackFrame; - private int mWidth, mHeight; private final int mOriginalRotation; private final int mOriginalWidth; private final int mOriginalHeight; private int mCurRotation; - private Rect mOriginalDisplayRect = new Rect(); - private Rect mCurrentDisplayRect = new Rect(); // The current active animation to move from the old to the new rotated // state. Which animation is run here will depend on the old and new // rotations. @@ -127,7 +124,6 @@ class ScreenRotationAnimation { private boolean mAnimRunning; private boolean mFinishAnimReady; private long mFinishAnimStartTime; - private boolean mForceDefaultOrientation; private SurfaceRotationAnimationController mSurfaceRotationAnimationController; /** Intensity of light/whiteness of the layout before rotation occurs. */ private float mStartLuma; @@ -138,25 +134,13 @@ class ScreenRotationAnimation { mService = displayContent.mWmService; mContext = mService.mContext; mDisplayContent = displayContent; - displayContent.getBounds(mOriginalDisplayRect); + final Rect currentBounds = displayContent.getBounds(); + final int width = currentBounds.width(); + final int height = currentBounds.height(); // Screenshot does NOT include rotation! final DisplayInfo displayInfo = displayContent.getDisplayInfo(); final int realOriginalRotation = displayInfo.rotation; - final int originalWidth; - final int originalHeight; - if (displayContent.getDisplayRotation().isFixedToUserRotation()) { - // Emulated orientation. - mForceDefaultOrientation = true; - originalWidth = displayContent.mBaseDisplayWidth; - originalHeight = displayContent.mBaseDisplayHeight; - } else { - // Normal situation - originalWidth = displayInfo.logicalWidth; - originalHeight = displayInfo.logicalHeight; - } - mWidth = originalWidth; - mHeight = originalHeight; mOriginalRotation = originalRotation; // If the delta is not zero, the rotation of display may not change, but we still want to @@ -165,8 +149,8 @@ class ScreenRotationAnimation { // when restoring the rotated app to the same rotation as current display. final int delta = deltaRotation(originalRotation, realOriginalRotation); final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270; - mOriginalWidth = flipped ? originalHeight : originalWidth; - mOriginalHeight = flipped ? originalWidth : originalHeight; + mOriginalWidth = flipped ? height : width; + mOriginalHeight = flipped ? width : height; mSurfaceRotationAnimationController = new SurfaceRotationAnimationController(); // Check whether the current screen contains any secure content. @@ -179,7 +163,7 @@ class ScreenRotationAnimation { new SurfaceControl.LayerCaptureArgs.Builder(displayContent.getSurfaceControl()) .setCaptureSecureLayers(true) .setAllowProtected(true) - .setSourceCrop(new Rect(0, 0, mWidth, mHeight)) + .setSourceCrop(new Rect(0, 0, width, height)) .build(); SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = SurfaceControl.captureLayers(args); @@ -244,6 +228,19 @@ class ScreenRotationAnimation { Slog.w(TAG, "Unable to allocate freeze surface", e); } + // If display size is changed with the same orientation, map the bounds of screenshot to + // the new logical display size. Currently pending transaction and RWC#mDisplayTransaction + // are merged to global transaction, so it can be synced with display change when calling + // DisplayManagerInternal#performTraversal(transaction). + final int logicalWidth = displayInfo.logicalWidth; + final int logicalHeight = displayInfo.logicalHeight; + if (logicalWidth > mOriginalWidth == logicalHeight > mOriginalHeight + && (logicalWidth != mOriginalWidth || logicalHeight != mOriginalHeight)) { + displayContent.getPendingTransaction().setGeometry(mScreenshotLayer, + new Rect(0, 0, mOriginalWidth, mOriginalHeight), + new Rect(0, 0, logicalWidth, logicalHeight), Surface.ROTATION_0); + } + ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: CREATE", mScreenshotLayer); if (originalRotation == realOriginalRotation) { @@ -277,11 +274,6 @@ class ScreenRotationAnimation { matrix.getValues(mTmpFloats); float x = mTmpFloats[Matrix.MTRANS_X]; float y = mTmpFloats[Matrix.MTRANS_Y]; - if (mForceDefaultOrientation) { - mDisplayContent.getBounds(mCurrentDisplayRect); - x -= mCurrentDisplayRect.left; - y -= mCurrentDisplayRect.top; - } t.setPosition(mScreenshotLayer, x, y); t.setMatrix(mScreenshotLayer, mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], @@ -293,8 +285,6 @@ class ScreenRotationAnimation { public void printTo(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer); - pw.print(" mWidth="); pw.print(mWidth); - pw.print(" mHeight="); pw.println(mHeight); pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame); @@ -315,11 +305,6 @@ class ScreenRotationAnimation { pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mSnapshotInitialMatrix="); mSnapshotInitialMatrix.dump(pw); pw.println(); - pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation); - if (mForceDefaultOrientation) { - pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString()); - pw.print(" mCurrentDisplayRect="); pw.println(mCurrentDisplayRect.toShortString()); - } } public void setRotation(SurfaceControl.Transaction t, int rotation) { @@ -329,7 +314,8 @@ class ScreenRotationAnimation { // to the snapshot to make it stay in the same original position // with the current screen rotation. int delta = deltaRotation(rotation, mOriginalRotation); - RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); + RotationAnimationUtils.createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, + mSnapshotInitialMatrix); setRotationTransform(t, mSnapshotInitialMatrix); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 58cf4bb62f04..358a61591c2e 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3398,6 +3398,12 @@ class Task extends TaskFragment { info.pictureInPictureParams = getPictureInPictureParams(top); info.preferDockBigOverlays = getPreferDockBigOverlays(); + if (info.pictureInPictureParams != null + && info.pictureInPictureParams.isLaunchIntoPip() + && top.getTopMostActivity().getLastParentBeforePip() != null) { + info.launchIntoPipHostTaskId = + top.getTopMostActivity().getLastParentBeforePip().mTaskId; + } info.displayCutoutInsets = top != null ? top.getDisplayCutoutInsets() : null; info.topActivityInfo = mReuseActivitiesReport.top != null ? mReuseActivitiesReport.top.info @@ -6576,8 +6582,7 @@ class Task extends TaskFragment { return; } if (mOverlayHost != null) { - final InsetsState s = getDisplayContent().getInsetsPolicy() - .getInsetsForWindow(originalChange, true); + final InsetsState s = originalChange.getInsetsState(true); getBounds(mTmpRect); mOverlayHost.dispatchInsetsChanged(s, mTmpRect); } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 1bba103c3ba7..f0cca18eca99 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -18,7 +18,6 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -111,9 +110,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { private Task mRootHomeTask; private Task mRootPinnedTask; - // TODO(b/159029784): Remove when getStack() behavior is cleaned-up - private Task mRootRecentsTask; - private final ArrayList<WindowContainer> mTmpAlwaysOnTopChildren = new ArrayList<>(); private final ArrayList<WindowContainer> mTmpNormalChildren = new ArrayList<>(); private final ArrayList<WindowContainer> mTmpHomeChildren = new ArrayList<>(); @@ -222,8 +218,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { Task getRootTask(int windowingMode, int activityType) { if (activityType == ACTIVITY_TYPE_HOME) { return mRootHomeTask; - } else if (activityType == ACTIVITY_TYPE_RECENTS) { - return mRootRecentsTask; } if (windowingMode == WINDOWING_MODE_PINNED) { return mRootPinnedTask; @@ -249,11 +243,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { return mRootHomeTask; } - @Nullable - Task getRootRecentsTask() { - return mRootRecentsTask; - } - Task getRootPinnedTask() { return mRootPinnedTask; } @@ -288,16 +277,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } else { mRootHomeTask = rootTask; } - } else if (rootTask.isActivityTypeRecents()) { - if (mRootRecentsTask != null) { - if (!rootTask.isDescendantOf(mRootRecentsTask)) { - throw new IllegalArgumentException("addRootTaskReferenceIfNeeded: root recents" - + " task=" + mRootRecentsTask + " already exist on display=" + this - + " rootTask=" + rootTask); - } - } else { - mRootRecentsTask = rootTask; - } } if (!rootTask.isRootTask()) { @@ -317,8 +296,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { void removeRootTaskReferenceIfNeeded(Task rootTask) { if (rootTask == mRootHomeTask) { mRootHomeTask = null; - } else if (rootTask == mRootRecentsTask) { - mRootRecentsTask = null; } else if (rootTask == mRootPinnedTask) { mRootPinnedTask = null; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index dd1f29e3bf5f..7fab94cab413 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -31,6 +31,7 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Process.INVALID_UID; +import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.USER_NULL; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.TRANSIT_CLOSE; @@ -82,6 +83,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.DisplayMetrics; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -530,6 +532,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { * certificate.</li> */ private boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) { + if (UserHandle.getAppId(mTaskFragmentOrganizerUid) == SYSTEM_UID) { + // The system is trusted to embed other apps securely and for all users. + return true; + } + if (mTaskFragmentOrganizerUid == a.getUid()) { // Activities from the same UID can be embedded freely by the host. return true; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index cbef60c459a6..0b965c37a2ba 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1251,6 +1251,17 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ActivityRecord topMostActivity = task.getTopMostActivity(); change.setAllowEnterPip(topMostActivity != null && topMostActivity.checkEnterPictureInPictureAppOpsState()); + final ActivityRecord topRunningActivity = task.topRunningActivity(); + if (topRunningActivity != null && task.mDisplayContent != null) { + // If Activity is in fixed rotation, its will be applied with the next rotation, + // when the Task is still in the previous rotation. + final int taskRotation = task.getWindowConfiguration().getDisplayRotation(); + final int activityRotation = topRunningActivity.getWindowConfiguration() + .getDisplayRotation(); + if (taskRotation != activityRotation) { + change.setEndFixedRotation(activityRotation); + } + } } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) { change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index e1746cca455a..45fdc04671c3 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -81,8 +81,10 @@ import android.util.Pair; import android.util.Pools; import android.util.RotationUtils; import android.util.Slog; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; +import android.view.InsetsSource; import android.view.MagnificationSpec; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; @@ -127,7 +129,8 @@ import java.util.function.Predicate; * changes are made to this class. */ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E> - implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable { + implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable, + InsetsControlTarget { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; @@ -145,6 +148,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // onParentChanged() notification. boolean mReparenting; + protected @Nullable InsetsSourceProvider mControllableInsetProvider; + + /** + * The insets sources provided by this windowContainer. + */ + private SparseArray<InsetsSource> mProvidedInsetsSources = null; + // List of children for this window container. List is in z-order as the children appear on // screen with the top-most window container at the tail of the list. protected final WindowList<E> mChildren = new WindowList<E>(); @@ -329,6 +339,25 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceFreezer = new SurfaceFreezer(this, wms); } + /** + * Set's an {@link InsetsSourceProvider} to be associated with this window, but only if the + * provider itself is controllable, as one window can be the provider of more than one inset + * type (i.e. gesture insets). If this window is controllable, all its animations must be + * controlled by its control target, and the visibility of this window should be taken account + * into the state of the control target. + * + * @param insetProvider the provider which should not be visible to the client. + * @see #getInsetsState() + */ + void setControllableInsetProvider(InsetsSourceProvider insetProvider) { + mControllableInsetProvider = insetProvider; + } + + InsetsSourceProvider getControllableInsetProvider() { + return mControllableInsetProvider; + } + + @Override final protected WindowContainer getParent() { return mParent; @@ -858,6 +887,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + public SparseArray<InsetsSource> getProvidedInsetsSources() { + if (mProvidedInsetsSources == null) { + mProvidedInsetsSources = new SparseArray<>(); + } + return mProvidedInsetsSources; + } + DisplayContent getDisplayContent() { return mDisplayContent; } @@ -2962,6 +2998,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< scheduleAnimation(); } + void transformFrameToSurfacePosition(int left, int top, Point outPoint) { + outPoint.set(left, top); + final WindowContainer parentWindowContainer = getParent(); + if (parentWindowContainer == null) { + return; + } + final Rect parentBounds = parentWindowContainer.getBounds(); + outPoint.offset(-parentBounds.left, -parentBounds.top); + } + void reassignLayer(Transaction t) { final WindowContainer parent = getParent(); if (parent != null) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 49f742cd15dd..1ab8cbf31dad 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -30,6 +30,7 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.os.PowerManager.DRAW_WAKE_LOCK; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.InsetsState.ITYPE_IME; +import static android.view.InsetsState.ITYPE_INVALID; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.SurfaceControl.Transaction; import static android.view.SurfaceControl.getGlobalTransaction; @@ -216,7 +217,6 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.MergedConfiguration; import android.util.Slog; -import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.Display; @@ -680,11 +680,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final InsetsState mAboveInsetsState = new InsetsState(); /** - * The insets sources provided by this window. - */ - final SparseArray<InsetsSource> mProvidedInsetsSources = new SparseArray<>(); - - /** * Surface insets from the previous call to relayout(), used to track * if we are changing the Surface insets. */ @@ -738,7 +733,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private boolean mIsDimming = false; - private @Nullable InsetsSourceProvider mControllableInsetProvider; private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); /** @@ -1647,11 +1641,39 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } /** + * See {@link WindowState#getInsetsState(boolean)} + */ + InsetsState getInsetsState() { + return getInsetsState(false); + } + + /** * Returns the insets state for the window. Its sources may be the copies with visibility * modification according to the state of transient bars. + * This is to get the insets for a window layout on the screen. If the window is not there, use + * the {@link InsetsPolicy#getInsetsForWindowMetrics} to get insets instead. + * @param includeTransient whether or not the transient types should be included in the + * insets state. */ - InsetsState getInsetsState() { - return getDisplayContent().getInsetsPolicy().getInsetsForWindow(this); + InsetsState getInsetsState(boolean includeTransient) { + final InsetsState rotatedState = mToken.getFixedRotationTransformInsetsState(); + final InsetsPolicy insetsPolicy = getDisplayContent().getInsetsPolicy(); + if (rotatedState != null) { + return insetsPolicy.adjustInsetsForWindow(this, rotatedState); + } + final InsetsSourceProvider provider = getControllableInsetProvider(); + final InsetsStateController insetsStateController = getDisplayContent() + .getInsetsStateController(); + final @InternalInsetsType int insetTypeProvidedByWindow = provider != null + ? provider.getSource().getType() : ITYPE_INVALID; + final InsetsState rawInsetsState = getFrozenInsetsState() != null + ? getFrozenInsetsState() : (mAttrs.receiveInsetsIgnoringZOrder + ? insetsStateController.getRawInsetsState() : mAboveInsetsState); + final InsetsState insetsStateForWindow = insetsPolicy + .enforceInsetsPolicyForTarget(insetTypeProvidedByWindow, + getWindowingMode(), isAlwaysOnTop(), rawInsetsState); + return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow, + includeTransient); } /** @@ -5658,24 +5680,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWindowFrames.setContentChanged(false); } - /** - * Set's an {@link InsetsSourceProvider} to be associated with this window, but only if the - * provider itself is controllable, as one window can be the provider of more than one inset - * type (i.e. gesture insets). If this window is controllable, all its animations must be - * controlled by its control target, and the visibility of this window should be taken account - * into the state of the control target. - * - * @param insetProvider the provider which should not be visible to the client. - * @see InsetsStateController#getInsetsForWindow(WindowState) - */ - void setControllableInsetProvider(InsetsSourceProvider insetProvider) { - mControllableInsetProvider = insetProvider; - } - - InsetsSourceProvider getControllableInsetProvider() { - return mControllableInsetProvider; - } - private final class MoveAnimationSpec implements AnimationSpec { private final long mDuration; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index 936940fcde6e..e6bb0ce318fe 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -2648,6 +2648,11 @@ public final class BackgroundRestrictionTest { AppPermissionTracker getAppPermissionTracker() { return mAppPermissionTracker; } + + @Override + void scheduleInitTrackers(Handler handler, Runnable initializers) { + initializers.run(); + } } private class TestBaseTrackerInjector<T extends BaseAppStatePolicy> diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java index 3890d4d006e2..897b91e65c85 100644 --- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; -import android.attention.AttentionManagerInternal.ProximityCallbackInternal; +import android.attention.AttentionManagerInternal.ProximityUpdateCallbackInternal; import android.content.ComponentName; import android.content.Context; import android.os.IBinder; @@ -48,7 +48,7 @@ import android.os.RemoteException; import android.provider.DeviceConfig; import android.service.attention.IAttentionCallback; import android.service.attention.IAttentionService; -import android.service.attention.IProximityCallback; +import android.service.attention.IProximityUpdateCallback; import androidx.test.filters.SmallTest; @@ -85,7 +85,7 @@ public class AttentionManagerServiceTest { @Mock Context mContext; @Mock - private ProximityCallbackInternal mMockProximityCallbackInternal; + private ProximityUpdateCallbackInternal mMockProximityUpdateCallbackInternal; @Before public void setUp() throws RemoteException { @@ -119,7 +119,8 @@ public class AttentionManagerServiceTest { public void testRegisterProximityUpdates_returnFalseWhenServiceDisabled() { mSpyAttentionManager.mIsServiceEnabled = false; - assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + assertThat(mSpyAttentionManager.onStartProximityUpdates( + mMockProximityUpdateCallbackInternal)) .isFalse(); } @@ -128,7 +129,8 @@ public class AttentionManagerServiceTest { mSpyAttentionManager.mIsServiceEnabled = true; doReturn(false).when(mSpyAttentionManager).isServiceAvailable(); - assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + assertThat(mSpyAttentionManager.onStartProximityUpdates( + mMockProximityUpdateCallbackInternal)) .isFalse(); } @@ -139,7 +141,8 @@ public class AttentionManagerServiceTest { doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); doReturn(false).when(mMockIPowerManager).isInteractive(); - assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + assertThat(mSpyAttentionManager.onStartProximityUpdates( + mMockProximityUpdateCallbackInternal)) .isFalse(); } @@ -149,9 +152,10 @@ public class AttentionManagerServiceTest { doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); doReturn(true).when(mMockIPowerManager).isInteractive(); - assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + assertThat(mSpyAttentionManager.onStartProximityUpdates( + mMockProximityUpdateCallbackInternal)) .isTrue(); - verify(mMockProximityCallbackInternal, times(1)) + verify(mMockProximityUpdateCallbackInternal, times(1)) .onProximityUpdate(PROXIMITY_SUCCESS_STATE); } @@ -161,21 +165,23 @@ public class AttentionManagerServiceTest { doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); doReturn(true).when(mMockIPowerManager).isInteractive(); - assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + assertThat(mSpyAttentionManager.onStartProximityUpdates( + mMockProximityUpdateCallbackInternal)) .isTrue(); ProximityUpdate prevProximityUpdate = mSpyAttentionManager.mCurrentProximityUpdate; - assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + assertThat(mSpyAttentionManager.onStartProximityUpdates( + mMockProximityUpdateCallbackInternal)) .isTrue(); assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isEqualTo(prevProximityUpdate); - verify(mMockProximityCallbackInternal, times(1)) + verify(mMockProximityUpdateCallbackInternal, times(1)) .onProximityUpdate(PROXIMITY_SUCCESS_STATE); } @Test public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() { - mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); - verifyZeroInteractions(mMockProximityCallbackInternal); + mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal); + verifyZeroInteractions(mMockProximityUpdateCallbackInternal); } @Test @@ -184,11 +190,11 @@ public class AttentionManagerServiceTest { mSpyAttentionManager.mIsServiceEnabled = true; doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); doReturn(true).when(mMockIPowerManager).isInteractive(); - mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); - verify(mMockProximityCallbackInternal, times(1)) + mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal); + verify(mMockProximityUpdateCallbackInternal, times(1)) .onProximityUpdate(PROXIMITY_SUCCESS_STATE); - ProximityCallbackInternal mismatchedCallback = new ProximityCallbackInternal() { + ProximityUpdateCallbackInternal mismatchedCallback = new ProximityUpdateCallbackInternal() { @Override public void onProximityUpdate(double distance) { fail("Callback shouldn't have responded."); @@ -196,7 +202,7 @@ public class AttentionManagerServiceTest { }; mSpyAttentionManager.onStopProximityUpdates(mismatchedCallback); - verifyNoMoreInteractions(mMockProximityCallbackInternal); + verifyNoMoreInteractions(mMockProximityUpdateCallbackInternal); } @Test @@ -205,8 +211,8 @@ public class AttentionManagerServiceTest { mSpyAttentionManager.mIsServiceEnabled = true; doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); doReturn(true).when(mMockIPowerManager).isInteractive(); - mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); - mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal); + mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal); assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isNull(); } @@ -217,14 +223,14 @@ public class AttentionManagerServiceTest { mSpyAttentionManager.mIsServiceEnabled = true; doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); doReturn(true).when(mMockIPowerManager).isInteractive(); - mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); - verify(mMockProximityCallbackInternal, times(1)) + mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal); + verify(mMockProximityUpdateCallbackInternal, times(1)) .onProximityUpdate(PROXIMITY_SUCCESS_STATE); // Attention Service unregisters the proximity update twice in a row. - mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); - mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); - verifyNoMoreInteractions(mMockProximityCallbackInternal); + mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal); + mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal); + verifyNoMoreInteractions(mMockProximityUpdateCallbackInternal); } @Test @@ -337,7 +343,8 @@ public class AttentionManagerServiceTest { public void cancelAttentionCheck(IAttentionCallback callback) { } - public void onStartProximityUpdates(IProximityCallback callback) throws RemoteException { + public void onStartProximityUpdates(IProximityUpdateCallback callback) + throws RemoteException { callback.onProximityUpdate(PROXIMITY_SUCCESS_STATE); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 2ae285409b73..9aac81c34edb 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -361,6 +361,16 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void close_cleanVirtualAudioController() { + mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID); + mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback); + + mDeviceImpl.close(); + + assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull(); + } + + @Test public void sendKeyEvent_noFd() { assertThrows( IllegalArgumentException.class, diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index bdf94f3a2882..356600d84099 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ParceledListSlice; import android.database.ContentObserver; +import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.display.AmbientBrightnessDayStats; @@ -42,6 +43,7 @@ import android.hardware.display.ColorDisplayManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; +import android.hardware.input.InputSensorInfo; import android.os.BatteryManager; import android.os.Handler; import android.os.HandlerThread; @@ -63,6 +65,8 @@ import com.android.internal.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -84,8 +88,11 @@ public class BrightnessTrackerTest { private static final String DEFAULT_DISPLAY_ID = "123"; private static final float FLOAT_DELTA = 0.01f; + @Mock private InputSensorInfo mInputSensorInfoMock; + private BrightnessTracker mTracker; private TestInjector mInjector; + private Sensor mLightSensorFake; private static Object sHandlerLock = new Object(); private static Handler sHandler; @@ -108,9 +115,12 @@ public class BrightnessTrackerTest { @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); mInjector = new TestInjector(ensureHandler()); + mLightSensorFake = new Sensor(mInputSensorInfoMock); mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector); + mTracker.setLightSensor(mLightSensorFake); mDefaultNightModeColorTemperature = InstrumentationRegistry.getContext().getResources().getInteger( R.integer.config_nightDisplayColorTemperatureDefault); @@ -834,6 +844,47 @@ public class BrightnessTrackerTest { mTracker.stop(); } + @Test + public void testLightSensorChange() { + // verify the tracker started correctly and a listener registered + startTracker(mTracker); + assertNotNull(mInjector.mSensorListener); + assertEquals(mInjector.mLightSensor, mLightSensorFake); + + // Setting the sensor to null should stop the registered listener. + mTracker.setLightSensor(null); + mInjector.waitForHandler(); + assertNull(mInjector.mSensorListener); + assertNull(mInjector.mLightSensor); + + // Resetting sensor should start listener again + mTracker.setLightSensor(mLightSensorFake); + mInjector.waitForHandler(); + assertNotNull(mInjector.mSensorListener); + assertEquals(mInjector.mLightSensor, mLightSensorFake); + + Sensor secondSensor = new Sensor(mInputSensorInfoMock); + // Setting a different listener should keep things working + mTracker.setLightSensor(secondSensor); + mInjector.waitForHandler(); + assertNotNull(mInjector.mSensorListener); + assertEquals(mInjector.mLightSensor, secondSensor); + } + + @Test + public void testSetLightSensorDoesntStartListener() { + mTracker.setLightSensor(mLightSensorFake); + assertNull(mInjector.mSensorListener); + } + + @Test + public void testNullLightSensorWontRegister() { + mTracker.setLightSensor(null); + startTracker(mTracker); + assertNull(mInjector.mSensorListener); + assertNull(mInjector.mLightSensor); + } + private InputStream getInputStream(String data) { return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); } @@ -924,6 +975,7 @@ public class BrightnessTrackerTest { private class TestInjector extends BrightnessTracker.Injector { SensorEventListener mSensorListener; + Sensor mLightSensor; BroadcastReceiver mBroadcastReceiver; DisplayManager.DisplayListener mDisplayListener; Map<String, Integer> mSecureIntSettings = new HashMap<>(); @@ -974,14 +1026,16 @@ public class BrightnessTrackerTest { @Override public void registerSensorListener(Context context, - SensorEventListener sensorListener, Handler handler) { + SensorEventListener sensorListener, Sensor lightSensor, Handler handler) { mSensorListener = sensorListener; + mLightSensor = lightSensor; } @Override public void unregisterSensorListener(Context context, SensorEventListener sensorListener) { mSensorListener = null; + mLightSensor = null; } @Override diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java index 2f5993d1d989..5d3da43c5327 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java @@ -132,7 +132,12 @@ public class ApexManagerTest { @Test public void testGetApexSystemServices() throws RemoteException { - when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, false)); + when(mApexService.getAllPackages()).thenReturn(new ApexInfo[] { + createApexInfoForTestPkg(false, true, 1), + // only active apex reports apex-system-service + createApexInfoForTestPkg(true, false, 2), + }); + mApexManager.scanApexPackagesTraced(mPackageParser2, ParallelPackageParser.makeExecutorService()); @@ -484,17 +489,20 @@ public class ApexManagerTest { assertThat(e).hasMessageThat().contains("Failed to collect certificates from "); } - private ApexInfo[] createApexInfoForTestPkg(boolean isActive, boolean isFactory) { + private ApexInfo createApexInfoForTestPkg(boolean isActive, boolean isFactory, int version) { File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME); ApexInfo apexInfo = new ApexInfo(); apexInfo.isActive = isActive; apexInfo.isFactory = isFactory; apexInfo.moduleName = TEST_APEX_PKG; apexInfo.modulePath = apexFile.getPath(); - apexInfo.versionCode = 191000070; + apexInfo.versionCode = version; apexInfo.preinstalledModulePath = apexFile.getPath(); + return apexInfo; + } - return new ApexInfo[]{apexInfo}; + private ApexInfo[] createApexInfoForTestPkg(boolean isActive, boolean isFactory) { + return new ApexInfo[]{createApexInfoForTestPkg(isActive, isFactory, 191000070)}; } private ApexInfo createApexInfo(String moduleName, int versionCode, boolean isActive, diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index 020d9f8361d2..e1a4989e5a05 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -100,7 +100,7 @@ public class VibrationThreadTest { public MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock - private VibrationThread.VibrationCallbacks mThreadCallbacks; + private VibrationThread.VibratorManagerHooks mManagerHooks; @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks; @Mock @@ -648,9 +648,9 @@ public class VibrationThreadTest { VibrationEffect.createOneShot(10, 100))); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); - verify(mThreadCallbacks, never()).prepareSyncedVibration(anyLong(), any()); - verify(mThreadCallbacks, never()).triggerSyncedVibration(anyLong()); - verify(mThreadCallbacks, never()).cancelSyncedVibration(); + verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any()); + verify(mManagerHooks, never()).triggerSyncedVibration(anyLong()); + verify(mManagerHooks, never()).cancelSyncedVibration(); } @Test @@ -807,8 +807,8 @@ public class VibrationThreadTest { mockVibrators(vibratorIds); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - when(mThreadCallbacks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true); - when(mThreadCallbacks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); + when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true); + when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100) @@ -823,9 +823,9 @@ public class VibrationThreadTest { waitForCompletion(thread); long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_COMPOSE; - verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); - verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId)); - verify(mThreadCallbacks, never()).cancelSyncedVibration(); + verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); + verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId)); + verify(mManagerHooks, never()).cancelSyncedVibration(); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); VibrationEffectSegment expected = expectedPrimitive( @@ -840,8 +840,8 @@ public class VibrationThreadTest { mockVibrators(vibratorIds); mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK); mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); - when(mThreadCallbacks.triggerSyncedVibration(anyLong())).thenReturn(true); + when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); + when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true); long vibrationId = 1; VibrationEffect composed = VibrationEffect.startComposition() @@ -863,9 +863,9 @@ public class VibrationThreadTest { | IVibratorManager.CAP_MIXED_TRIGGER_ON | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM | IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE; - verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); - verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId)); - verify(mThreadCallbacks, never()).cancelSyncedVibration(); + verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); + verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId)); + verify(mManagerHooks, never()).cancelSyncedVibration(); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); } @@ -875,7 +875,7 @@ public class VibrationThreadTest { mockVibrators(vibratorIds); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(false); + when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(false); long vibrationId = 1; CombinedVibration effect = CombinedVibration.startParallel() @@ -886,9 +886,9 @@ public class VibrationThreadTest { waitForCompletion(thread); long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON; - verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); - verify(mThreadCallbacks, never()).triggerSyncedVibration(eq(vibrationId)); - verify(mThreadCallbacks, never()).cancelSyncedVibration(); + verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); + verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibrationId)); + verify(mManagerHooks, never()).cancelSyncedVibration(); assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffectSegments()); @@ -903,8 +903,8 @@ public class VibrationThreadTest { int[] vibratorIds = new int[]{1, 2}; mockVibrators(vibratorIds); mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK); - when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); - when(mThreadCallbacks.triggerSyncedVibration(anyLong())).thenReturn(false); + when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); + when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false); long vibrationId = 1; CombinedVibration effect = CombinedVibration.startParallel() @@ -919,9 +919,9 @@ public class VibrationThreadTest { | IVibratorManager.CAP_PREPARE_PERFORM | IVibratorManager.CAP_MIXED_TRIGGER_ON | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM; - verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); - verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId)); - verify(mThreadCallbacks).cancelSyncedVibration(); + verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); + verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId)); + verify(mManagerHooks).cancelSyncedVibration(); assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty()); } @@ -1161,9 +1161,9 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); // Vibration completed but vibrator not yet released. - verify(mThreadCallbacks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId), + verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId), eq(Vibration.Status.FINISHED)); - verify(mThreadCallbacks, never()).onVibratorsReleased(); + verify(mManagerHooks, never()).onVibrationThreadReleased(); // Thread still running ramp down. assertTrue(thread.isAlive()); @@ -1177,9 +1177,9 @@ public class VibrationThreadTest { waitForCompletion(thread); // Does not cancel already finished vibration, but releases vibrator. - verify(mThreadCallbacks, never()).onVibrationCompleted(eq(vibrationId), + verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId), eq(Vibration.Status.CANCELLED)); - verify(mThreadCallbacks).onVibratorsReleased(); + verify(mManagerHooks).onVibrationThreadReleased(); } @Test @@ -1300,7 +1300,7 @@ public class VibrationThreadTest { private VibrationThread startThreadAndDispatcher(Vibration vib) { VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter, - createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks); + createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mManagerHooks); doAnswer(answer -> { thread.vibratorComplete(answer.getArgument(0)); return null; @@ -1380,8 +1380,8 @@ public class VibrationThreadTest { } private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) { - verify(mThreadCallbacks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus)); - verify(mThreadCallbacks).onVibratorsReleased(); + verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus)); + verify(mManagerHooks).onVibrationThreadReleased(); } private final class TestLooperAutoDispatcher extends Thread { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java index 184ea521e828..fe1ea0d99eeb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java @@ -33,6 +33,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; import android.app.Instrumentation; import android.app.Instrumentation.ActivityMonitor; +import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -40,6 +41,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.util.Rational; import android.view.SurfaceControl; import android.window.TaskOrganizer; @@ -91,6 +93,20 @@ public class ActivityOptionsTest { } @Test + public void testMakeLaunchIntoPip() { + // Construct some params with set values + PictureInPictureParams params = new PictureInPictureParams.Builder() + .setAspectRatio(new Rational(1, 1)) + .build(); + // Construct ActivityOptions via makeLaunchIntoPip + ActivityOptions opts = ActivityOptions.makeLaunchIntoPip(params); + + // Verify the params in ActivityOptions has the right flag being turned on + assertNotNull(opts.getLaunchIntoPipParams()); + assertTrue(opts.isLaunchIntoPip()); + } + + @Test public void testTransferLaunchCookie() { final Binder cookie = new Binder(); final ActivityOptions options = ActivityOptions.makeBasic(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 043bc0700ab4..ffc10d791ef9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -109,6 +109,7 @@ import static org.mockito.Mockito.never; import android.app.ActivityOptions; import android.app.ICompatCameraControlCallback; +import android.app.PictureInPictureParams; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.DestroyActivityItem; @@ -2200,6 +2201,20 @@ public class ActivityRecordTests extends WindowTestsBase { assertFalse(activity.supportsPictureInPicture()); } + @Test + public void testLaunchIntoPip() { + final PictureInPictureParams params = new PictureInPictureParams.Builder() + .build(); + final ActivityOptions opts = ActivityOptions.makeLaunchIntoPip(params); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setLaunchIntoPipActivityOptions(opts) + .build(); + + // Verify the pictureInPictureArgs is set on the new Activity + assertNotNull(activity.pictureInPictureArgs); + assertTrue(activity.pictureInPictureArgs.isLaunchIntoPip()); + } + private void verifyProcessInfoUpdate(ActivityRecord activity, State state, boolean shouldUpdate, boolean activityChange) { reset(activity.app); @@ -3044,7 +3059,7 @@ public class ActivityRecordTests extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(app); mDisplayContent.updateImeInputAndControlTarget(app); - InsetsState state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app); + InsetsState state = app.getInsetsState(); assertFalse(state.getSource(ITYPE_IME).isVisible()); assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty()); @@ -3064,7 +3079,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Verify when IME is visible and the app can receive the right IME insets from policy. makeWindowVisibleAndDrawn(app, mImeWindow); - state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app); + state = app.getInsetsState(); assertTrue(state.getSource(ITYPE_IME).isVisible()); assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame()); } @@ -3076,7 +3091,7 @@ public class ActivityRecordTests extends WindowTestsBase { final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); - mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow( + mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer( mImeWindow, null, null); mImeWindow.getControllableInsetProvider().setServerVisible(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index c8e48a48d3fb..87abc53bfc5a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -39,6 +39,7 @@ import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; +import static android.os.Process.SYSTEM_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; @@ -68,6 +69,7 @@ import static org.mockito.ArgumentMatchers.notNull; import android.app.ActivityOptions; import android.app.IApplicationThread; +import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -1141,6 +1143,34 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test + public void testStartActivityInner_inTaskFragment_allowedForSystemUid() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); + final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token, + true /* createdByOrganizer */); + sourceRecord.getTask().addChild(taskFragment, POSITION_TOP); + + taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class), SYSTEM_UID, + "system_uid"); + + starter.startActivityInner( + /* r */targetRecord, + /* sourceRecord */ sourceRecord, + /* voiceSession */null, + /* voiceInteractor */ null, + /* startFlags */ 0, + /* doResume */true, + /* options */null, + /* inTask */null, + /* inTaskFragment */ taskFragment, + /* restrictedBgActivity */false, + /* intentGrants */null); + + assertTrue(taskFragment.hasChild()); + } + + @Test public void testStartActivityInner_inTaskFragment_allowedForSameUid() { final ActivityStarter starter = prepareStarter(0, false); final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); @@ -1264,4 +1294,36 @@ public class ActivityStarterTests extends WindowTestsBase { // Verify the cookie is updated assertTrue(mRootWindowContainer.topRunningActivity().mLaunchCookie == newCookie); } + + @Test + public void testStartLaunchIntoPipActivity() { + final ActivityStarter starter = prepareStarter(0, false); + + // Create an activity from ActivityOptions#makeLaunchIntoPip + final PictureInPictureParams params = new PictureInPictureParams.Builder() + .build(); + final ActivityOptions opts = ActivityOptions.makeLaunchIntoPip(params); + ActivityRecord targetRecord = new ActivityBuilder(mAtm) + .setLaunchIntoPipActivityOptions(opts) + .build(); + + // Start the target launch-into-pip activity from a source + final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build(); + starter.startActivityInner( + /* r */ targetRecord, + /* sourceRecord */ sourceRecord, + /* voiceSession */ null, + /* voiceInteractor */ null, + /* startFlags */ 0, + /* doResume */ true, + /* options */ opts, + /* inTask */ null, + /* inTaskFragment */ null, + /* restrictedBgActivity */ false, + /* intentGrants */ null); + + // Verify the ActivityRecord#getLaunchIntoPipHostActivity points to sourceRecord. + assertThat(targetRecord.getLaunchIntoPipHostActivity()).isNotNull(); + assertEquals(targetRecord.getLaunchIntoPipHostActivity(), sourceRecord); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 497ae1defb61..db22757cc4fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -258,11 +258,9 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord)); mWindow.mAboveInsetsState.set( mDisplayContent.getInsetsStateController().getRawInsetsState()); - final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow) - .getSource(ITYPE_STATUS_BAR).getFrame(); + final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame(); mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord); - final Rect rotatedFrame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow) - .getSource(ITYPE_STATUS_BAR).getFrame(); + final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame(); assertEquals(DISPLAY_WIDTH, frame.width()); assertEquals(DISPLAY_HEIGHT, rotatedFrame.width()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java index 5f9626711896..ca8481a8c50b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java @@ -71,7 +71,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { public void testIsImeShowing() { WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime"); makeWindowVisibleAndDrawn(ime); - mImeProvider.setWindow(ime, null, null); + mImeProvider.setWindowContainer(ime, null, null); WindowState target = createWindow(null, TYPE_APPLICATION, "app"); mDisplayContent.setImeLayeringTarget(target); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 2987f943f1c5..c61b88b3d3a2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -63,7 +63,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); statusBar.getFrame().set(0, 0, 500, 100); statusBar.mHasSurface = true; - mProvider.setWindow(statusBar, null, null); + mProvider.setWindowContainer(statusBar, null, null); mProvider.onPostLayout(); assertEquals(new Rect(0, 0, 500, 100), mProvider.getSource().getFrame()); assertEquals(Insets.of(0, 100, 0, 0), @@ -80,7 +80,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { ime.mGivenContentInsets.set(0, 0, 0, 60); ime.mGivenVisibleInsets.set(0, 0, 0, 75); ime.mHasSurface = true; - mProvider.setWindow(ime, null, null); + mProvider.setWindowContainer(ime, null, null); mProvider.onPostLayout(); assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame()); assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame()); @@ -95,10 +95,10 @@ public class InsetsSourceProviderTest extends WindowTestsBase { public void testPostLayout_invisible() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); statusBar.getFrame().set(0, 0, 500, 100); - mProvider.setWindow(statusBar, null, null); + mProvider.setWindowContainer(statusBar, null, null); mProvider.onPostLayout(); assertEquals(Insets.NONE, mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), - false /* ignoreVisibility */)); + false /* ignoreVisibility */)); } @Test @@ -106,7 +106,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); statusBar.getFrame().set(0, 0, 500, 100); statusBar.mHasSurface = true; - mProvider.setWindow(statusBar, + mProvider.setWindowContainer(statusBar, (displayFrames, windowState, rect) -> { rect.set(10, 10, 20, 20); }, null); @@ -126,7 +126,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { assertNull(mProvider.getControlTarget()); // We can have the control or the control target after we have the insets source window. - mProvider.setWindow(statusBar, null, null); + mProvider.setWindowContainer(statusBar, null, null); mProvider.updateControlForTarget(target, false /* force */); assertNotNull(mProvider.getControl(target)); assertNotNull(mProvider.getControlTarget()); @@ -164,7 +164,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); statusBar.getFrame().set(0, 0, 500, 100); - mProvider.setWindow(statusBar, null, null); + mProvider.setWindowContainer(statusBar, null, null); mProvider.updateControlForFakeTarget(target); assertNotNull(mProvider.getControl(target)); assertNull(mProvider.getControl(target).getLeash()); @@ -178,7 +178,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { inputMethod.getFrame().set(new Rect(0, 400, 500, 500)); - mImeProvider.setWindow(inputMethod, null, null); + mImeProvider.setWindowContainer(inputMethod, null, null); mImeProvider.setServerVisible(false); mImeSource.setVisible(true); mImeProvider.updateSourceFrame(); @@ -201,7 +201,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); statusBar.getFrame().set(0, 0, 500, 100); - mProvider.setWindow(statusBar, null, null); + mProvider.setWindowContainer(statusBar, null, null); mProvider.updateControlForTarget(target, false /* force */); final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); @@ -215,7 +215,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); statusBar.getFrame().set(0, 0, 500, 100); - mProvider.setWindow(statusBar, null, null); + mProvider.setWindowContainer(statusBar, null, null); final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false); target.setRequestedVisibilities(requestedVisibilities); @@ -228,7 +228,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); statusBar.getFrame().set(0, 0, 500, 100); statusBar.mHasSurface = true; - mProvider.setWindow(statusBar, null, null); + mProvider.setWindowContainer(statusBar, null, null); mProvider.onPostLayout(); assertEquals(new Rect(0, 0, 500, 100), mProvider.getSource().getFrame()); // Still apply top insets if window overlaps even if it's top doesn't exactly match diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 2eece4c2ca4d..c7a1b07fd439 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -68,11 +68,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { // IME cannot be the IME target. ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); - getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); - assertNull(getController().getInsetsForWindow(navBar).peekSource(ITYPE_IME)); - assertNull(getController().getInsetsForWindow(navBar).peekSource(ITYPE_STATUS_BAR)); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, + null); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null); + + assertNull(navBar.getInsetsState().peekSource(ITYPE_IME)); + assertNull(navBar.getInsetsState().peekSource(ITYPE_STATUS_BAR)); } @Test @@ -81,13 +84,15 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, + null); app.setWindowingMode(WINDOWING_MODE_PINNED); - assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR)); - assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR)); - assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_IME)); + assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR)); + assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.getInsetsState().peekSource(ITYPE_IME)); } @Test @@ -96,12 +101,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, + null); app.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR)); - assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR)); + assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); } @Test @@ -110,19 +117,21 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, + null); app.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); app.setAlwaysOnTop(true); - assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR)); - assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR)); + assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); } @UseTestDisplay(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_independentSources() { - getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null); final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); @@ -130,34 +139,34 @@ public class InsetsStateControllerTest extends WindowTestsBase { app1.mAboveInsetsState.addSource(getController().getRawInsetsState().getSource(ITYPE_IME)); getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); - assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME) + assertFalse(app2.getInsetsState().getSource(ITYPE_IME) .isVisible()); - assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME) + assertTrue(app1.getInsetsState().getSource(ITYPE_IME) .isVisible()); } @UseTestDisplay(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_belowIme() { - getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true); app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame()); getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); - assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); + assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible()); } @UseTestDisplay(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_aboveIme() { - getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); - assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME) + assertFalse(app.getInsetsState().getSource(ITYPE_IME) .isVisible()); } @@ -172,7 +181,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { // Make IME and stay visible during the test. mImeWindow.setHasSurface(true); - getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null); getController().onImeControlTargetChanged(mDisplayContent.getImeTarget(IME_TARGET_INPUT)); final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); requestedVisibilities.setVisibility(ITYPE_IME, true); @@ -195,7 +204,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { // app won't get visible IME insets while above IME even when IME is visible. assertTrue(getController().getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); - assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME) + assertFalse(app.getInsetsState().getSource(ITYPE_IME) .isVisible()); // Reset invocation counter. @@ -212,13 +221,13 @@ public class InsetsStateControllerTest extends WindowTestsBase { verify(app, atLeastOnce()).notifyInsetsChanged(); // app will get visible IME insets while below IME. - assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); + assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible()); } @UseTestDisplay(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_childWindow_altFocusable() { - getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); @@ -231,15 +240,15 @@ public class InsetsStateControllerTest extends WindowTestsBase { mDisplayContent.applySurfaceChangesTransaction(); getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); - assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); - assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME) + assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible()); + assertFalse(child.getInsetsState().getSource(ITYPE_IME) .isVisible()); } @UseTestDisplay(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_childWindow_splitScreen() { - getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); @@ -252,8 +261,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { mDisplayContent.applySurfaceChangesTransaction(); getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); - assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); - assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME) + assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible()); + assertFalse(child.getInsetsState().getSource(ITYPE_IME) .isVisible()); } @@ -267,14 +276,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { InsetsSourceProvider statusBarProvider = getController().getSourceProvider(ITYPE_STATUS_BAR); - statusBarProvider.setWindow(statusBar, null, ((displayFrames, windowState, rect) -> - rect.set(0, 1, 2, 3))); - getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); + statusBarProvider.setWindowContainer(statusBar, null, ((displayFrames, windowState, rect) -> + rect.set(0, 1, 2, 3))); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null); statusBar.setControllableInsetProvider(statusBarProvider); statusBarProvider.onPostLayout(); - final InsetsState state = getController().getInsetsForWindow(ime); + final InsetsState state = ime.getInsetsState(); assertEquals(new Rect(0, 1, 2, 3), state.getSource(ITYPE_STATUS_BAR).getFrame()); } @@ -285,10 +294,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar"); final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); - getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindow(climateBar, null, null); - getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindow(extraNavBar, null, + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, + null); + getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindowContainer(climateBar, null, + null); + getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindowContainer( + extraNavBar, null, null); getController().onBarControlTargetChanged(app, null, app, null); InsetsSourceControl[] controls = getController().getControlsForDispatch(app); @@ -299,7 +312,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { public void testControlRevoked() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); getController().onBarControlTargetChanged(app, null, null, null); assertNotNull(getController().getControlsForDispatch(app)); getController().onBarControlTargetChanged(null, null, null, null); @@ -310,7 +324,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { public void testControlRevoked_animation() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); getController().onBarControlTargetChanged(app, null, null, null); assertNotNull(getController().getControlsForDispatch(app)); statusBar.cancelAnimation(); @@ -322,7 +337,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final InsetsSourceProvider provider = getController().getSourceProvider(ITYPE_STATUS_BAR); - provider.setWindow(statusBar, null, null); + provider.setWindowContainer(statusBar, null, null); final InsetsState rotatedState = new InsetsState(app.getInsetsState(), true /* copySources */); @@ -344,7 +359,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState statusBar = createTestWindow("statusBar"); final WindowState navBar = createTestWindow("navBar"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); @@ -365,8 +381,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState statusBar = createTestWindow("statusBar"); final WindowState navBar = createTestWindow("navBar"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, + null); assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); assertNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR)); @@ -386,10 +404,12 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState statusBar = createTestWindow("statusBar"); final WindowState navBar = createTestWindow("navBar"); - getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); + getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null); getController().getSourceProvider(ITYPE_IME).setClientVisible(true); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, + null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, + null); getController().updateAboveInsetsState(ime, false /* notifyInsetsChange */); getController().updateAboveInsetsState(statusBar, false /* notifyInsetsChange */); getController().updateAboveInsetsState(navBar, false /* notifyInsetsChange */); @@ -420,11 +440,12 @@ public class InsetsStateControllerTest extends WindowTestsBase { @Test public void testDispatchGlobalInsets() { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, + null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); app.mAttrs.receiveInsetsIgnoringZOrder = true; - assertNotNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR)); + assertNotNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); } private WindowState createTestWindow(String name) { diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index ee17f52c0bcb..ba6510440747 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -251,7 +251,8 @@ public class RootWindowContainerTests extends WindowTestsBase { ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity); // Move first activity to pinned root task. - mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "initialMove"); + mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, + null /* launchIntoPipHostActivity */, "initialMove"); final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea(); Task pinnedRootTask = taskDisplayArea.getRootPinnedTask(); @@ -260,7 +261,8 @@ public class RootWindowContainerTests extends WindowTestsBase { ensureTaskPlacement(fullscreenTask, secondActivity); // Move second activity to pinned root task. - mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "secondMove"); + mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, + null /* launchIntoPipHostActivity */, "secondMove"); // Need to get root tasks again as a new instance might have been created. pinnedRootTask = taskDisplayArea.getRootPinnedTask(); @@ -291,7 +293,8 @@ public class RootWindowContainerTests extends WindowTestsBase { // Move first activity to pinned root task. - mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove"); + mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, + null /* launchIntoPipHostActivity */, "initialMove"); assertTrue(firstActivity.mRequestForceTransition); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index b815c38b7a8a..7b38a955f822 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -31,6 +31,7 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; @@ -2128,6 +2129,136 @@ public class SizeCompatTests extends WindowTestsBase { APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE); } + @Test + public void testIsEligibleForLetterboxEducation_educationNotEnabled_returnsFalse() { + setUpDisplaySizeWithApp(2500, 1000); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(false); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + assertFalse(mActivity.isEligibleForLetterboxEducation()); + } + + @Test + public void testIsEligibleForLetterboxEducation_notEligibleForFixedOrientation_returnsFalse() { + setUpDisplaySizeWithApp(1000, 2500); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + assertFalse(mActivity.isEligibleForLetterboxEducation()); + } + + @Test + public void testIsEligibleForLetterboxEducation_windowingModeMultiWindow_returnsFalse() { + // Support non resizable in multi window + mAtm.mDevEnableNonResizableMultiWindow = true; + setUpDisplaySizeWithApp(1000, 1200); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); + + // Non-resizable landscape activity + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + final Rect originalBounds = new Rect(mActivity.getBounds()); + + // Move activity to split screen which takes half of the screen. + mTask.reparent(organizer.mPrimary, POSITION_TOP, + false /*moveParents*/, "test"); + organizer.mPrimary.setBounds(0, 0, 1000, 600); + + assertFalse(mActivity.isEligibleForLetterboxEducation()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); + } + + @Test + public void testIsEligibleForLetterboxEducation_fixedOrientationLandscape_returnsFalse() { + setUpDisplaySizeWithApp(1000, 2500); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + assertFalse(mActivity.isEligibleForLetterboxEducation()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + } + + @Test + public void testIsEligibleForLetterboxEducation_hasStartingWindow_returnsFalseUntilRemoved() { + setUpDisplaySizeWithApp(2500, 1000); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mActivity.mStartingData = mock(StartingData.class); + mActivity.attachStartingWindow( + createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), + mActivity)); + + assertFalse(mActivity.isEligibleForLetterboxEducation()); + + // Verify that after removing the starting window isEligibleForLetterboxEducation returns + // true and mTask.dispatchTaskInfoChangedIfNeeded is called. + spyOn(mTask); + mActivity.removeStartingWindow(); + + assertTrue(mActivity.isEligibleForLetterboxEducation()); + verify(mTask).dispatchTaskInfoChangedIfNeeded(true); + } + + @Test + public void testIsEligibleForLetterboxEducation_hasStartingWindowAndEducationNotEnabled() { + setUpDisplaySizeWithApp(2500, 1000); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(false); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mActivity.mStartingData = mock(StartingData.class); + mActivity.attachStartingWindow( + createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), + mActivity)); + + assertFalse(mActivity.isEligibleForLetterboxEducation()); + + // Verify that after removing the starting window isEligibleForLetterboxEducation still + // returns false and mTask.dispatchTaskInfoChangedIfNeeded isn't called. + spyOn(mTask); + mActivity.removeStartingWindow(); + + assertFalse(mActivity.isEligibleForLetterboxEducation()); + verify(mTask, never()).dispatchTaskInfoChangedIfNeeded(true); + } + + @Test + public void testIsEligibleForLetterboxEducation_letterboxedForFixedOrientation_returnsTrue() { + setUpDisplaySizeWithApp(2500, 1000); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + assertTrue(mActivity.isEligibleForLetterboxEducation()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + } + + @Test + public void testIsEligibleForLetterboxEducation_sizeCompatAndEligibleForFixedOrientation() { + setUpDisplaySizeWithApp(1000, 2500); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + assertTrue(mActivity.isEligibleForLetterboxEducation()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertTrue(mActivity.inSizeCompatMode()); + } + /** * Tests that all three paths in which aspect ratio logic can be applied yield the same * result, which is that aspect ratio is respected on app bounds. The three paths are diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 80192f7b5d60..459e3a5131cc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -105,7 +105,7 @@ import java.util.List; * Tests for the {@link WindowState} class. * * Build/Install/Run: - * atest WmTests:WindowStateTests + * atest WmTests:WindowStateTests */ @SmallTest @Presubmit @@ -411,7 +411,7 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(app.canAffectSystemUiFlags()); } - @UseTestDisplay(addWindows = { W_ACTIVITY, W_STATUS_BAR }) + @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR}) @Test public void testVisibleWithInsetsProvider() { final WindowState statusBar = mStatusBarWindow; @@ -419,7 +419,8 @@ public class WindowStateTests extends WindowTestsBase { statusBar.mHasSurface = true; assertTrue(statusBar.isVisible()); mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR) - .setWindow(statusBar, null /* frameProvider */, null /* imeFrameProvider */); + .setWindowContainer(statusBar, null /* frameProvider */, + null /* imeFrameProvider */); mDisplayContent.getInsetsStateController().onBarControlTargetChanged( app, null /* fakeTopControlling */, app, null /* fakeNavControlling */); final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); @@ -623,7 +624,7 @@ public class WindowStateTests extends WindowTestsBase { assertEquals(w.getWindowConfiguration().getBounds(), unscaledClientBounds); } - @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE }) + @UseTestDisplay(addWindows = {W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE}) @Test public void testRequestDrawIfNeeded() { final WindowState startingApp = createWindow(null /* parent */, @@ -831,7 +832,7 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); } - @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD }) + @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD}) @Test public void testNeedsRelativeLayeringToIme_startingWindow() { WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING, @@ -864,7 +865,7 @@ public class WindowStateTests extends WindowTestsBase { verify(app).notifyInsetsChanged(); } - @UseTestDisplay(addWindows = { W_INPUT_METHOD, W_ACTIVITY }) + @UseTestDisplay(addWindows = {W_INPUT_METHOD, W_ACTIVITY}) @Test public void testImeAlwaysReceivesVisibleNavigationBarInsets() { final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR); @@ -890,7 +891,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.mInputMethodWindow = imeWindow; final InsetsStateController controller = mDisplayContent.getInsetsStateController(); - controller.getImeSourceProvider().setWindow(imeWindow, null, null); + controller.getImeSourceProvider().setWindowContainer(imeWindow, null, null); // Simulate app requests IME with updating all windows Insets State when IME is above app. mDisplayContent.setImeLayeringTarget(app); @@ -914,7 +915,7 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible()); } - @UseTestDisplay(addWindows = { W_ACTIVITY }) + @UseTestDisplay(addWindows = {W_ACTIVITY}) @Test public void testUpdateImeControlTargetWhenLeavingMultiWindow() { WindowState app = createWindow(null, TYPE_BASE_APPLICATION, @@ -940,7 +941,7 @@ public class WindowStateTests extends WindowTestsBase { assertEquals(mAppWindow, mDisplayContent.getImeTarget(IME_TARGET_CONTROL).getWindow()); } - @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE }) + @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE}) @Test public void testNotificationShadeHasImeInsetsWhenMultiWindow() { WindowState app = createWindow(null, TYPE_BASE_APPLICATION, @@ -954,7 +955,7 @@ public class WindowStateTests extends WindowTestsBase { mNotificationShadeWindow.setHasSurface(true); mNotificationShadeWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE; assertTrue(mNotificationShadeWindow.canBeImeTarget()); - mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow( + mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer( mImeWindow, null, null); mDisplayContent.computeImeTarget(true); @@ -963,8 +964,7 @@ public class WindowStateTests extends WindowTestsBase { .setSourceVisible(ITYPE_IME, true); // Verify notificationShade can still get IME insets even windowing mode is multi-window. - InsetsState state = mDisplayContent.getInsetsStateController().getInsetsForWindow( - mNotificationShadeWindow); + InsetsState state = mNotificationShadeWindow.getInsetsState(); assertNotNull(state.peekSource(ITYPE_IME)); assertTrue(state.getSource(ITYPE_IME).isVisible()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 5dbb4c59b10a..bd1f9d536c28 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -941,6 +941,7 @@ class WindowTestsBase extends SystemServiceTestsBase { private boolean mOnTop = false; private ActivityInfo.WindowLayout mWindowLayout; private boolean mVisible = true; + private ActivityOptions mLaunchIntoPipOpts; ActivityBuilder(ActivityTaskManagerService service) { mService = service; @@ -1076,6 +1077,11 @@ class WindowTestsBase extends SystemServiceTestsBase { return this; } + ActivityBuilder setLaunchIntoPipActivityOptions(ActivityOptions opts) { + mLaunchIntoPipOpts = opts; + return this; + } + ActivityRecord build() { SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock); try { @@ -1132,7 +1138,9 @@ class WindowTestsBase extends SystemServiceTestsBase { } ActivityOptions options = null; - if (mLaunchTaskBehind) { + if (mLaunchIntoPipOpts != null) { + options = mLaunchIntoPipOpts; + } else if (mLaunchTaskBehind) { options = ActivityOptions.makeTaskLaunchBehind(); } final ActivityRecord activity = new ActivityRecord.Builder(mService) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index 74cff10f994f..4b5f330a2ddc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -262,7 +262,7 @@ public class WindowTokenTests extends WindowTestsBase { @Test public void testSetInsetsFrozen_notAffectImeWindowState() { // Pre-condition: make the IME window be controlled by IME insets provider. - mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow( + mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer( mDisplayContent.mInputMethodWindow, null, null); // Simulate an app window to be the IME layering target, assume the app window has no diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java index 27e8d69aa762..ab8f69b121a0 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java @@ -22,6 +22,7 @@ import static com.android.server.usage.UsageStatsService.DEBUG_RESPONSE_STATS; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -39,6 +40,8 @@ import com.android.internal.util.IndentingPrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; class BroadcastResponseStatsTracker { private static final String TAG = "ResponseStatsTracker"; @@ -172,28 +175,27 @@ class BroadcastResponseStatsTracker { } } - @NonNull BroadcastResponseStats queryBroadcastResponseStats(int callingUid, - @NonNull String packageName, long id, @UserIdInt int userId) { - final BroadcastResponseStats aggregatedResponseStats = - new BroadcastResponseStats(packageName); + @NonNull List<BroadcastResponseStats> queryBroadcastResponseStats(int callingUid, + @Nullable String packageName, @IntRange(from = 0) long id, @UserIdInt int userId) { + final List<BroadcastResponseStats> broadcastResponseStatsList = new ArrayList<>(); synchronized (mLock) { final SparseArray<UserBroadcastResponseStats> responseStatsForCaller = mUserResponseStats.get(callingUid); if (responseStatsForCaller == null) { - return aggregatedResponseStats; + return broadcastResponseStatsList; } final UserBroadcastResponseStats responseStatsForUser = responseStatsForCaller.get(userId); if (responseStatsForUser == null) { - return aggregatedResponseStats; + return broadcastResponseStatsList; } - responseStatsForUser.aggregateBroadcastResponseStats(aggregatedResponseStats, - packageName, id); + responseStatsForUser.populateAllBroadcastResponseStats( + broadcastResponseStatsList, packageName, id); } - return aggregatedResponseStats; + return broadcastResponseStatsList; } - void clearBroadcastResponseStats(int callingUid, @NonNull String packageName, long id, + void clearBroadcastResponseStats(int callingUid, @Nullable String packageName, long id, @UserIdInt int userId) { synchronized (mLock) { final SparseArray<UserBroadcastResponseStats> responseStatsForCaller = @@ -210,6 +212,16 @@ class BroadcastResponseStatsTracker { } } + void clearBroadcastEvents(int callingUid, @UserIdInt int userId) { + synchronized (mLock) { + final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(userId); + if (userBroadcastEvents == null) { + return; + } + userBroadcastEvents.clear(callingUid); + } + } + void onUserRemoved(@UserIdInt int userId) { synchronized (mLock) { mUserBroadcastEvents.remove(userId); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 4a761a7a47be..06aa8f05179c 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -49,7 +49,7 @@ import android.app.PendingIntent; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.AppLaunchEstimateInfo; import android.app.usage.AppStandbyInfo; -import android.app.usage.BroadcastResponseStats; +import android.app.usage.BroadcastResponseStatsList; import android.app.usage.ConfigurationStats; import android.app.usage.EventStats; import android.app.usage.IUsageStatsManager; @@ -2686,16 +2686,15 @@ public class UsageStatsService extends SystemService implements @Override @NonNull - public BroadcastResponseStats queryBroadcastResponseStats( - @NonNull String packageName, - @IntRange(from = 1) long id, + public BroadcastResponseStatsList queryBroadcastResponseStats( + @Nullable String packageName, + @IntRange(from = 0) long id, @NonNull String callingPackage, @UserIdInt int userId) { - Objects.requireNonNull(packageName); Objects.requireNonNull(callingPackage); // TODO: Move to Preconditions utility class - if (id <= 0) { - throw new IllegalArgumentException("id needs to be >0"); + if (id < 0) { + throw new IllegalArgumentException("id needs to be >=0"); } final int callingUid = Binder.getCallingUid(); @@ -2708,8 +2707,9 @@ public class UsageStatsService extends SystemService implements userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, false /* allowAll */, false /* requireFull */, "queryBroadcastResponseStats" /* name */, callingPackage); - return mResponseStatsTracker.queryBroadcastResponseStats( - callingUid, packageName, id, userId); + return new BroadcastResponseStatsList( + mResponseStatsTracker.queryBroadcastResponseStats( + callingUid, packageName, id, userId)); } @Override @@ -2718,10 +2718,9 @@ public class UsageStatsService extends SystemService implements @IntRange(from = 1) long id, @NonNull String callingPackage, @UserIdInt int userId) { - Objects.requireNonNull(packageName); Objects.requireNonNull(callingPackage); - if (id <= 0) { - throw new IllegalArgumentException("id needs to be >0"); + if (id < 0) { + throw new IllegalArgumentException("id needs to be >=0"); } final int callingUid = Binder.getCallingUid(); @@ -2737,6 +2736,23 @@ public class UsageStatsService extends SystemService implements mResponseStatsTracker.clearBroadcastResponseStats(callingUid, packageName, id, userId); } + + @Override + public void clearBroadcastEvents(@NonNull String callingPackage, @UserIdInt int userId) { + Objects.requireNonNull(callingPackage); + + final int callingUid = Binder.getCallingUid(); + if (!hasPermission(callingPackage)) { + throw new SecurityException( + "Caller does not have the permission needed to call this API; " + + "callingPackage=" + callingPackage + + ", callingUid=" + callingUid); + } + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, + userId, false /* allowAll */, false /* requireFull */, + "clearBroadcastResponseStats" /* name */, callingPackage); + mResponseStatsTracker.clearBroadcastEvents(callingUid, userId); + } } void registerAppUsageObserver(int callingUid, int observerId, String[] packages, diff --git a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java index 81964484696a..0ec59c3e022f 100644 --- a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java +++ b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java @@ -51,6 +51,10 @@ class UserBroadcastEvents { } void onUidRemoved(int uid) { + clear(uid); + } + + void clear(int uid) { for (int i = mBroadcastEvents.size() - 1; i >= 0; --i) { final LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i); for (int j = broadcastEvents.size() - 1; j >= 0; --j) { diff --git a/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java b/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java index ac2a320e4995..1828a7109c2f 100644 --- a/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java +++ b/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java @@ -16,6 +16,7 @@ package com.android.server.usage; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.BroadcastResponseStats; @@ -23,6 +24,8 @@ import android.util.ArrayMap; import com.android.internal.util.IndentingPrintWriter; +import java.util.List; + class UserBroadcastResponseStats { /** * Contains the mapping of a BroadcastEvent type to it's aggregated stats. @@ -39,31 +42,38 @@ class UserBroadcastResponseStats { BroadcastEvent broadcastEvent) { BroadcastResponseStats responseStats = mResponseStats.get(broadcastEvent); if (responseStats == null) { - responseStats = new BroadcastResponseStats(broadcastEvent.getTargetPackage()); + responseStats = new BroadcastResponseStats(broadcastEvent.getTargetPackage(), + broadcastEvent.getIdForResponseEvent()); mResponseStats.put(broadcastEvent, responseStats); } return responseStats; } - void aggregateBroadcastResponseStats( - @NonNull BroadcastResponseStats responseStats, - @NonNull String packageName, long id) { + void populateAllBroadcastResponseStats( + @NonNull List<BroadcastResponseStats> broadcastResponseStatsList, + @Nullable String packageName, @IntRange(from = 0) long id) { for (int i = mResponseStats.size() - 1; i >= 0; --i) { final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i); - if (broadcastEvent.getIdForResponseEvent() == id - && broadcastEvent.getTargetPackage().equals(packageName)) { - responseStats.addCounts(mResponseStats.valueAt(i)); + if (id != 0 && id != broadcastEvent.getIdForResponseEvent()) { + continue; + } + if (packageName != null && !packageName.equals(broadcastEvent.getTargetPackage())) { + continue; } + broadcastResponseStatsList.add(mResponseStats.valueAt(i)); } } - void clearBroadcastResponseStats(@NonNull String packageName, long id) { + void clearBroadcastResponseStats(@Nullable String packageName, @IntRange(from = 0) long id) { for (int i = mResponseStats.size() - 1; i >= 0; --i) { final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i); - if (broadcastEvent.getIdForResponseEvent() == id - && broadcastEvent.getTargetPackage().equals(packageName)) { - mResponseStats.removeAt(i); + if (id != 0 && id != broadcastEvent.getIdForResponseEvent()) { + continue; + } + if (packageName != null && !packageName.equals(broadcastEvent.getTargetPackage())) { + continue; } + mResponseStats.removeAt(i); } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 3d00474f487f..eb3df1c1c4e4 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8226,7 +8226,9 @@ public class CarrierConfigManager { "store_sim_pin_for_unattended_reboot_bool"; /** - * Determine whether "Enable 2G" toggle can be shown. + * Allow whether the user can use the "Allow 2G" toggle in Settings. + * + * If {@code true} then the toggle is disabled (i.e. grayed out). * * Used to trade privacy/security against potentially reduced carrier coverage for some * carriers. diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index a49a61b592ba..b6ae53017f64 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -25,15 +25,20 @@ import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Activity; import android.app.PendingIntent; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.os.RemoteException; +import android.telephony.SubscriptionInfo; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccCardManager.ResetOption; +import android.util.Log; import com.android.internal.telephony.euicc.IEuiccController; @@ -57,6 +62,7 @@ import java.util.stream.Collectors; */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_EUICC) public class EuiccManager { + private static final String TAG = "EuiccManager"; /** * Intent action to launch the embedded SIM (eUICC) management settings screen. @@ -811,6 +817,14 @@ public class EuiccManager { */ public static final int ERROR_INVALID_PORT = 10017; + /** + * Apps targeting on Android T and beyond will get exception whenever switchToSubscription + * without portIndex is called for disable subscription. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + public static final long SWITCH_WITHOUT_PORT_INDEX_EXCEPTION_ON_DISABLE = 218393363L; private final Context mContext; private int mCardId; @@ -1127,7 +1141,7 @@ public class EuiccManager { * intent to prompt the user to accept the download. The caller should also be authorized to * manage the subscription to be enabled. * - * <p> From Android T, devices might support MEP(Multiple Enabled Profile), the subscription + * <p> From Android T, devices might support MEP(Multiple Enabled Profiles), the subscription * can be installed on different port from the eUICC. Calling apps with carrier privilege * (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently active subscriptions * can use {@link #switchToSubscription(int, int, PendingIntent)} to specify which port to @@ -1138,10 +1152,12 @@ public class EuiccManager { * * @param subscriptionId the ID of the subscription to enable. May be * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the - * current profile without activating another profile to replace it. If it's a disable - * operation, requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} - * permission, or the calling app must be authorized to manage the active subscription on - * the target eUICC. + * current profile without activating another profile to replace it. Calling apps targeting + * on android T must use {@link #switchToSubscription(int, int, PendingIntent)} API for + * disable profile, port index can be found from {@link SubscriptionInfo#getPortIndex()}. + * If it's a disable operation, requires the + * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or the + * calling app must be authorized to manage the active subscription on the target eUICC. * @param callbackIntent a PendingIntent to launch when the operation completes. */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) @@ -1151,6 +1167,18 @@ public class EuiccManager { return; } try { + // TODO: Uncomment below compat change code once callers are ported to use + // switchToSubscription with portIndex for disable operation. + // if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID + // && getIEuiccController().isCompatChangeEnabled(mContext.getOpPackageName(), + // SWITCH_WITHOUT_PORT_INDEX_EXCEPTION_ON_DISABLE)) { + // // Apps targeting on Android T and beyond will get exception whenever + // // switchToSubscription without portIndex is called with INVALID_SUBSCRIPTION_ID. + // Log.e(TAG, "switchToSubscription without portIndex is not allowed for" + // + " disable operation"); + // throw new IllegalArgumentException("Must use switchToSubscription with portIndex" + // + " API for disable operation"); + // } getIEuiccController().switchToSubscription(mCardId, subscriptionId, mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { @@ -1180,7 +1208,10 @@ public class EuiccManager { * current profile without activating another profile to replace it. If it's a disable * operation, requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} * permission, or the calling app must be authorized to manage the active subscription on - * the target eUICC. + * the target eUICC. From Android T, multiple enabled profiles is supported. Calling apps + * targeting on android T must use {@link #switchToSubscription(int, int, PendingIntent)} + * API for disable profile, port index can be found from + * {@link SubscriptionInfo#getPortIndex()}. * @param portIndex the index of the port to target for the enabled subscription * @param callbackIntent a PendingIntent to launch when the operation completes. */ @@ -1192,6 +1223,17 @@ public class EuiccManager { return; } try { + boolean canWriteEmbeddedSubscriptions = mContext.checkCallingOrSelfPermission( + Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) + == PackageManager.PERMISSION_GRANTED; + // If the caller is not privileged caller and does not have the carrier privilege over + // any active subscription, do not continue. + if (!canWriteEmbeddedSubscriptions && !getIEuiccController() + .hasCarrierPrivilegesForPackageOnAnyPhone(mContext.getOpPackageName())) { + Log.e(TAG, "Not permitted to use switchToSubscription with portIndex"); + throw new SecurityException( + "Must have carrier privileges to use switchToSubscription with portIndex"); + } getIEuiccController().switchToSubscriptionWithPort(mCardId, subscriptionId, portIndex, mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index dda95b159552..19f1a5b832fd 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -55,4 +55,6 @@ interface IEuiccController { List<String> getSupportedCountries(boolean isSupported); boolean isSupportedCountry(String countryIso); boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage); + boolean hasCarrierPrivilegesForPackageOnAnyPhone(String callingPackage); + boolean isCompatChangeEnabled(String callingPackage, long changeId); } diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index 98d13e81551d..566c725a3414 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -26,16 +26,8 @@ <option name="shell-timeout" value="6600s" /> <option name="test-timeout" value="6600s" /> <option name="hidden-api-checks" value="false" /> - <option name="device-listeners" - value="com.android.server.wm.flicker.TraceFileReadyListener" /> </test> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="pull-pattern-keys" value="(\w)+\.winscope" /> - <option name="pull-pattern-keys" value="(\w)+\.mp4" /> - <option name="collect-on-run-ended-only" value="false" /> - <option name="clean-up" value="true" /> - </metrics_collector> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> <option name="directory-keys" value="/sdcard/flicker" /> <option name="collect-on-run-ended-only" value="true" /> <option name="clean-up" value="true" /> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index 3b3a3035f296..f8348203599f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest_ShellTransit.kt index 849e3ae91eaa..0d2869cbd178 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest_ShellTransit.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 18d017d95d03..00fee82adf76 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -16,10 +16,10 @@ package com.android.server.wm.flicker.launch +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import android.view.Display -import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -31,7 +31,6 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.traces.common.WindowManagerConditionsFactory import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test @@ -140,37 +139,6 @@ open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart() - /** {@inheritDoc} */ - @Presubmit - @Test - override fun appWindowReplacesLauncherAsTopWindow() { - assumeFalse(isShellTransitionsEnabled) - super.appWindowReplacesLauncherAsTopWindow() - } - - @FlakyTest(bugId = 216266712) - @Test - fun appWindowReplacesLauncherAsTopWindow_shellTransit() { - assumeTrue(isShellTransitionsEnabled) - super.appWindowReplacesLauncherAsTopWindow() - } - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - assumeFalse(isShellTransitionsEnabled) - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - - /** {@inheritDoc} */ - @FlakyTest(bugId = 218470989) - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry_shellTransit() { - assumeTrue(isShellTransitionsEnabled) - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt index 24716ffb902a..1c06495cf473 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -25,6 +25,7 @@ import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -60,4 +61,16 @@ class OpenAppFromOverviewTest_ShellTransit(testSpec: FlickerTestParameter) override fun before() { assumeTrue(isShellTransitionsEnabled) } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 216266712) + @Test + override fun appWindowReplacesLauncherAsTopWindow() = + super.appWindowReplacesLauncherAsTopWindow() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 218470989) + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 0ba5369917b8..b0e53e9f85bf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt index f8afcd9233f4..8a08d07e654e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt index d2f6c7f144dd..53560cc14c44 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.launch import android.app.Instrumentation -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.Presubmit import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest_ShellTransit.kt index 0ce73f4e2509..3958dd2c89b4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest_ShellTransit.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt index 07fe274e5ea2..cffed81f6cb9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.quickswitch -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index f603f6e7ed9d..2b944c666782 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -112,10 +112,7 @@ class ChangeAppRotationTest( * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, * doesn't flicker, and disappears before the transition is complete */ - @Presubmit - @Test - fun rotationLayerAppearsAndVanishes() { - Assume.assumeFalse(isShellTransitionsEnabled) + fun rotationLayerAppearsAndVanishesAssertion() { testSpec.assertLayers { this.isVisible(testApp.component) .then() @@ -126,11 +123,26 @@ class ChangeAppRotationTest( } } + /** + * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, + * doesn't flicker, and disappears before the transition is complete + */ + @Presubmit + @Test + fun rotationLayerAppearsAndVanishes() { + Assume.assumeFalse(isShellTransitionsEnabled) + rotationLayerAppearsAndVanishesAssertion() + } + + /** + * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, + * doesn't flicker, and disappears before the transition is complete + */ @FlakyTest(bugId = 218484127) @Test fun rotationLayerAppearsAndVanishes_shellTransit() { Assume.assumeTrue(isShellTransitionsEnabled) - rotationLayerAppearsAndVanishes() + rotationLayerAppearsAndVanishesAssertion() } /** diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index d1bdeed81b78..0becadf630e1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -153,4 +153,4 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt index be55751bf060..d397d5979803 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.rotation -import android.platform.test.annotations.FlakyTest +import androidx.test.filters.FlakyTest import android.platform.test.annotations.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter diff --git a/tests/Internal/src/com/android/internal/util/UserIconsTest.java b/tests/Internal/src/com/android/internal/util/UserIconsTest.java new file mode 100644 index 000000000000..cc7b20b28a97 --- /dev/null +++ b/tests/Internal/src/com/android/internal/util/UserIconsTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.R; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UserIconsTest { + + @Test + public void convertToBitmapAtUserIconSize_sizeIsCorrect() { + Resources res = InstrumentationRegistry.getTargetContext().getResources(); + Drawable icon = UserIcons.getDefaultUserIcon(res, 0, true); + Bitmap bitmap = UserIcons.convertToBitmapAtUserIconSize(res, icon); + int expectedSize = res.getDimensionPixelSize(R.dimen.user_icon_size); + + assertThat(bitmap.getWidth()).isEqualTo(expectedSize); + assertThat(bitmap.getHeight()).isEqualTo(expectedSize); + } + +} diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp index c59a41ee0cd3..9a9e42bfc300 100644 --- a/tests/UpdatableSystemFontTest/Android.bp +++ b/tests/UpdatableSystemFontTest/Android.bp @@ -37,8 +37,6 @@ android_test { "vts", ], data: [ - ":NotoSerif-Regular.ttf", - ":NotoSerif-Bold.ttf", ":UpdatableSystemFontTestCertDer", ":UpdatableSystemFontTest_NotoColorEmoji.ttf", ":UpdatableSystemFontTest_NotoColorEmoji.sig", @@ -48,7 +46,9 @@ android_test { ":UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig", ":UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf", ":UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig", + ":UpdatableSystemFontTest_NotoSerif-Regular.ttf", ":UpdatableSystemFontTest_NotoSerif-Regular.sig", + ":UpdatableSystemFontTest_NotoSerif-Bold.ttf", ":UpdatableSystemFontTest_NotoSerif-Bold.sig", ], sdk_version: "test_current", diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml index 6effa7bd0a50..9e2a4b643bf6 100644 --- a/tests/UpdatableSystemFontTest/AndroidTest.xml +++ b/tests/UpdatableSystemFontTest/AndroidTest.xml @@ -28,11 +28,7 @@ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <option name="cleanup" value="true" /> <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" /> - <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" /> - <option name="push" value="NotoSerif-Regular.ttf->/data/local/tmp/NotoSerif-Regular.ttf" /> - <option name="push" value="NotoSerif-Bold.ttf->/data/local/tmp/NotoSerif-Bold.ttf" /> - <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig" /> - <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmoji.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.ttf" /> <option name="push" value="UpdatableSystemFontTest_NotoColorEmoji.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.sig" /> <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiV0.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.ttf" /> <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiV0.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.sig" /> @@ -40,6 +36,10 @@ <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig" /> <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf" /> <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.ttf" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index 87fda0d220e5..cbe13d9aa149 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -84,7 +84,7 @@ public class UpdatableSystemFontTest { private static final String NOTO_COLOR_EMOJI_POSTSCRIPT_NAME = "NotoColorEmoji"; private static final String NOTO_COLOR_EMOJI_TTF = - "/data/local/tmp/NotoColorEmoji.ttf"; + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.ttf"; private static final String NOTO_COLOR_EMOJI_SIG = "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.sig"; // A font with revision == 0. @@ -105,13 +105,13 @@ public class UpdatableSystemFontTest { private static final String NOTO_SERIF_REGULAR_POSTSCRIPT_NAME = "NotoSerif"; private static final String NOTO_SERIF_REGULAR_TTF = - "/data/local/tmp/NotoSerif-Regular.ttf"; + "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.ttf"; private static final String NOTO_SERIF_REGULAR_SIG = "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig"; private static final String NOTO_SERIF_BOLD_POSTSCRIPT_NAME = "NotoSerif-Bold"; private static final String NOTO_SERIF_BOLD_TTF = - "/data/local/tmp/NotoSerif-Bold.ttf"; + "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.ttf"; private static final String NOTO_SERIF_BOLD_SIG = "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig"; diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp index 64b698dd0db0..0bdb3a8c6b14 100644 --- a/tests/UpdatableSystemFontTest/testdata/Android.bp +++ b/tests/UpdatableSystemFontTest/testdata/Android.bp @@ -21,11 +21,19 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -// An existing module name is reused to avoid merge conflicts. -// TODO: fix the font file name. filegroup { name: "UpdatableSystemFontTest_NotoColorEmoji.ttf", - srcs: ["NotoColorEmoji.ttf"], + srcs: ["UpdatableSystemFontTest_NotoColorEmoji.ttf"], +} + +filegroup { + name: "UpdatableSystemFontTest_NotoSerif-Regular.ttf", + srcs: ["UpdatableSystemFontTest_NotoSerif-Regular.ttf"], +} + +filegroup { + name: "UpdatableSystemFontTest_NotoSerif-Bold.ttf", + srcs: ["UpdatableSystemFontTest_NotoSerif-Bold.ttf"], } filegroup { @@ -43,10 +51,6 @@ filegroup { srcs: ["UpdatableSystemFontTestCert.der"], } -genrule_defaults { - name: "updatable_system_font_increment_font_revision_default", -} - genrule { name: "UpdatableSystemFontTest_NotoColorEmojiV0.ttf", srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], @@ -124,13 +128,13 @@ genrule { genrule { name: "UpdatableSystemFontTest_NotoSerif-Regular.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":NotoSerif-Regular.ttf"], + srcs: ["UpdatableSystemFontTest_NotoSerif-Regular.ttf"], out: ["UpdatableSystemFontTest_NotoSerif-Regular.sig"], } genrule { name: "UpdatableSystemFontTest_NotoSerif-Bold.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":NotoSerif-Bold.ttf"], + srcs: ["UpdatableSystemFontTest_NotoSerif-Bold.ttf"], out: ["UpdatableSystemFontTest_NotoSerif-Bold.sig"], } diff --git a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttf Binary files differindex f71f52cbaa2b..f71f52cbaa2b 100644 --- a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttf +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttf diff --git a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttx index 6540c5898ec5..6540c5898ec5 100644 --- a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttx +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttx diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf Binary files differnew file mode 100644 index 000000000000..66c1bd2a09b1 --- /dev/null +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx new file mode 100644 index 000000000000..8c4215eafc60 --- /dev/null +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx @@ -0,0 +1,196 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2022 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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> + + <GlyphOrder> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="a"/> + </GlyphOrder> + + <head> + <tableVersion value="1.0"/> + <!-- Currently NotoSerif-Bold.ttf's fontRevision is 1.xx. + 100.0 will be sufficiently larger than that. --> + <fontRevision value="100.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Wed Feb 16 12:00:00 2022"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="0x00010000"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + </hhea> + + <maxp> + <tableVersion value="0x10000"/> + <maxZones value="0"/> + <maxTwilightPoints value="0"/> + <maxStorage value="0"/> + <maxFunctionDefs value="0"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="0"/> + <maxSizeOfInstructions value="0"/> + <maxComponentElements value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="32"/> + <usLastCharIndex value="122"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="a" width="3000" lsb="93"/> <!-- 3em --> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <!-- length will be calculated by the compiler. --> + <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="0" language="0" nGroups="1"> + <!-- The font must support at least one of the characters used + in OtfFontFileParser to validate the font. --> + <map code="0x61" name="a" /> + </cmap_format_12> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" /> + <TTGlyph name="a" xMin="0" yMin="0" xMax="300" yMax="300"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="0" y="300" on="1" /> + <pt x="300" y="300" on="1" /> + <pt x="300" y="0" on="1" /> + </contour> + <instructions /> + </TTGlyph> + </glyf> + + <name> + <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409"> + Copyright (C) 2022 The Android Open Source Project + </namerecord> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Bold + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + <!-- Android identifies the target font to be updated by PostScript name. + To test updating NotoSerif-Bold.ttf, the PostScript needs to be + the same as NotoSerif-Bold.ttf here. --> + NotoSerif-Bold + </namerecord> + <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + 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. + </namerecord> + <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> + http://www.apache.org/licenses/LICENSE-2.0 + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + +</ttFont> diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf Binary files differnew file mode 100644 index 000000000000..707ae281d990 --- /dev/null +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx new file mode 100644 index 000000000000..754eae31c7c1 --- /dev/null +++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx @@ -0,0 +1,196 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2022 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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> + + <GlyphOrder> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="a"/> + </GlyphOrder> + + <head> + <tableVersion value="1.0"/> + <!-- Currently NotoSerif-Regular.ttf's fontRevision is 1.xx. + 100.0 will be sufficiently larger than that. --> + <fontRevision value="100.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Wed Feb 16 12:00:00 2022"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="0x00010000"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + </hhea> + + <maxp> + <tableVersion value="0x10000"/> + <maxZones value="0"/> + <maxTwilightPoints value="0"/> + <maxStorage value="0"/> + <maxFunctionDefs value="0"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="0"/> + <maxSizeOfInstructions value="0"/> + <maxComponentElements value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="32"/> + <usLastCharIndex value="122"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="a" width="3000" lsb="93"/> <!-- 3em --> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <!-- length will be calculated by the compiler. --> + <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="0" language="0" nGroups="1"> + <!-- The font must support at least one of the characters used + in OtfFontFileParser to validate the font. --> + <map code="0x61" name="a" /> + </cmap_format_12> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" /> + <TTGlyph name="a" xMin="0" yMin="0" xMax="300" yMax="300"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="0" y="300" on="1" /> + <pt x="300" y="300" on="1" /> + <pt x="300" y="0" on="1" /> + </contour> + <instructions /> + </TTGlyph> + </glyf> + + <name> + <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409"> + Copyright (C) 2022 The Android Open Source Project + </namerecord> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + <!-- Android identifies the target font to be updated by PostScript name. + To test updating NotoSerif-Regular.ttf, the PostScript needs to be + the same as NotoSerif-Regular.ttf here. --> + NotoSerif + </namerecord> + <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + 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. + </namerecord> + <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> + http://www.apache.org/licenses/LICENSE-2.0 + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + +</ttFont> diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index e547400fff73..4cfa93b4ecf9 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -307,7 +307,10 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection ncCaptor.capture(), lpCaptor.capture(), any(), - argThat(nac -> nac.getLegacyType() == ConnectivityManager.TYPE_MOBILE), + // Subtype integer/name and extras do not have getters; cannot be tested. + argThat(nac -> nac.getLegacyType() == ConnectivityManager.TYPE_MOBILE + && nac.getLegacyTypeName().equals( + VcnGatewayConnection.NETWORK_INFO_NETWORK_TYPE_STRING)), any(), any(), any()); |