Merge "Fix ComponentResolver Computer consistency"
diff --git a/Android.bp b/Android.bp
index 1c4f10e..f805947 100644
--- a/Android.bp
+++ b/Android.bp
@@ -191,7 +191,6 @@
"sax/java",
"telecomm/java",
- "apex/media/aidl/stable",
// TODO(b/147699819): remove this
"telephony/java",
],
@@ -289,6 +288,7 @@
// 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 @@
"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 {
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 996cdc9..ba31161 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -145,11 +145,9 @@
"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 96e88dd..0000000
--- 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 2c5965c..0000000
--- 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 4ba0d9b..0000000
--- 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 64edafc..0000000
--- 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 e1c89e9..0000000
--- 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 e347ebf..0000000
--- 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 42c6e70..0000000
--- 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 26e717b..0000000
--- 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 10ac1be..0000000
--- 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 43a7b12..0000000
--- 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 92d673f..0000000
--- 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/aidl/stable/android/media/Session2Token.aidl b/apex/media/aidl/stable/android/media/Session2Token.aidl
deleted file mode 100644
index c5980e9..0000000
--- a/apex/media/aidl/stable/android/media/Session2Token.aidl
+++ /dev/null
@@ -1,19 +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;
-
-parcelable Session2Token;
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
deleted file mode 100644
index e38488d..0000000
--- 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 3d21914..0000000
--- 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 b7d7ed8..0000000
--- 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 7317f14..0000000
--- 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 d802177..0000000
--- 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 d802177..0000000
--- 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 6eea769..0000000
--- 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 d802177..0000000
--- 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 91489dc..0000000
--- 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 97fa0ec..0000000
--- 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 915f3f6a3..0000000
--- 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 04af028..0000000
--- 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 8eefec7..0000000
--- 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 c297ecd..0000000
--- 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 ef5552e..0000000
--- 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 ce10889..0000000
--- 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 159e8e5..0000000
--- 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 8d1b159..0000000
--- 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 d7ad97d..0000000
--- 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 fb36e80..0000000
--- 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 b6f85c7..0000000
--- 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 7d07eb3..0000000
--- 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 9f80c43..0000000
--- 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 aff3204..0000000
--- 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 14d3ce8..0000000
--- 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 2359813..0000000
--- 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 7e71591..0000000
--- 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 af8184a..0000000
--- 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 6e550e8..0000000
--- 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 aae2e1b..0000000
--- 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 c81152c..0000000
--- 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 95eea45..0000000
--- 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 4e7d842..0000000
--- 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 0e300bb..0000000
--- 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 d802177..0000000
--- 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 d802177..0000000
--- 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 d802177..0000000
--- 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 d802177..0000000
--- 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 7e37c2b..0000000
--- 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 4223fa6..0000000
--- 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 8145861..0000000
--- 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 def6baf..0000000
--- 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 bf49838..139ff3e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4550,6 +4550,7 @@
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,6 +6636,7 @@
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);
@@ -16995,6 +16997,7 @@
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;
@@ -17006,7 +17009,6 @@
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 {
@@ -17124,6 +17126,9 @@
public final class SensorPrivacyManager {
method public boolean supportsSensorToggle(int);
+ method public boolean supportsSensorToggle(int, int);
+ field public static final int TOGGLE_TYPE_HARDWARE = 2; // 0x2
+ field public static final int TOGGLE_TYPE_SOFTWARE = 1; // 0x1
}
public static class SensorPrivacyManager.Sensors {
@@ -39511,7 +39516,7 @@
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 5d76b08..785ad13 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2254,13 +2254,20 @@
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 @@
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 @@
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 @@
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 @@
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);
@@ -3731,13 +3759,25 @@
public final class SensorPrivacyManager {
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
- method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int, int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean);
}
public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener {
- method public void onSensorPrivacyChanged(int, boolean);
+ method public default void onSensorPrivacyChanged(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams);
+ method @Deprecated public void onSensorPrivacyChanged(int, boolean);
+ }
+
+ public static class SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams {
+ method public int getSensor();
+ method public int getToggleType();
+ method public boolean isEnabled();
}
}
@@ -5850,8 +5890,8 @@
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();
@@ -5878,8 +5918,8 @@
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);
}
@@ -10848,7 +10888,7 @@
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
@@ -10866,7 +10906,7 @@
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/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 5012121..5ddaa80 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -350,6 +350,10 @@
/** 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 @@
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 @@
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 @@
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 @@
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 @@
mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
mSpecsFuture = otherOptions.mSpecsFuture;
mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
+ mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams;
}
/**
@@ -2039,6 +2081,9 @@
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 0d1bc05..7c7c7ef 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2687,7 +2687,7 @@
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 @@
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 @@
* @hide
*/
public static @Mode int opToDefaultMode(int op) {
+ if (op == OP_SYSTEM_ALERT_WINDOW) {
+ return getSystemAlertWindowDefault();
+ }
return sOpDefaultMode[op];
}
@@ -10113,6 +10118,11 @@
}
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 @@
// 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 5ecddfd..2d2788c 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -64,6 +64,27 @@
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.
@@ -221,6 +242,20 @@
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
@@ -232,7 +267,8 @@
public PictureInPictureParams build() {
PictureInPictureParams params = new PictureInPictureParams(mAspectRatio,
mExpandedAspectRatio, mUserActions, mCloseAction, mSourceRectHint,
- mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle);
+ mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle,
+ mIsLaunchIntoPip);
return params;
}
}
@@ -294,6 +330,13 @@
@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() {
}
@@ -322,13 +365,16 @@
if (in.readInt() != 0) {
mSubtitle = in.readCharSequence();
}
+ if (in.readInt() != 0) {
+ mIsLaunchIntoPip = in.readBoolean();
+ }
}
/** {@hide} */
PictureInPictureParams(Rational aspectRatio, Rational expandedAspectRatio,
List<RemoteAction> actions, RemoteAction closeAction, Rect sourceRectHint,
Boolean autoEnterEnabled, Boolean seamlessResizeEnabled, CharSequence title,
- CharSequence subtitle) {
+ CharSequence subtitle, Boolean isLaunchIntoPip) {
mAspectRatio = aspectRatio;
mExpandedAspectRatio = expandedAspectRatio;
mUserActions = actions;
@@ -338,6 +384,7 @@
mSeamlessResizeEnabled = seamlessResizeEnabled;
mTitle = title;
mSubtitle = subtitle;
+ mIsLaunchIntoPip = isLaunchIntoPip;
}
/**
@@ -347,8 +394,8 @@
public PictureInPictureParams(PictureInPictureParams other) {
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);
}
/**
@@ -384,6 +431,9 @@
if (otherArgs.hasSetSubtitle()) {
mSubtitle = otherArgs.mSubtitle;
}
+ if (otherArgs.mIsLaunchIntoPip != null) {
+ mIsLaunchIntoPip = otherArgs.mIsLaunchIntoPip;
+ }
}
/**
@@ -548,14 +598,22 @@
}
/**
+ * @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() && !hasSetCloseAction()
- && !hasSetAspectRatio() && !hasSetExpandedAspectRatio() && mAutoEnterEnabled != null
- && mSeamlessResizeEnabled != null && !hasSetTitle()
- && !hasSetSubtitle();
+ && !hasSetAspectRatio() && !hasSetExpandedAspectRatio() && mAutoEnterEnabled == null
+ && mSeamlessResizeEnabled == null && !hasSetTitle()
+ && !hasSetSubtitle() && mIsLaunchIntoPip == null;
}
@Override
@@ -571,13 +629,15 @@
&& 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, mCloseAction,
- mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle);
+ mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle,
+ mIsLaunchIntoPip);
}
@Override
@@ -628,6 +688,12 @@
} else {
out.writeInt(0);
}
+ if (mIsLaunchIntoPip != null) {
+ out.writeInt(1);
+ out.writeBoolean(mIsLaunchIntoPip);
+ } else {
+ out.writeInt(0);
+ }
}
private void writeRationalToParcel(Rational rational, Parcel out) {
@@ -659,6 +725,7 @@
+ " 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 eca4170..5c7c73c 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -191,6 +191,13 @@
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 @@
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 @@
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 @@
+ " 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 a07af68..584b176 100644
--- a/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java
@@ -33,6 +33,9 @@
* <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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
&& 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 @@
+ ", 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 @@
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 @@
/** 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 @@
/** 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 @@
}
/**
+ * 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 @@
/**
* 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 feb1c34..fbdb7be 100644
--- a/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java
@@ -59,17 +59,29 @@
@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 @@
}
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 13091e2..1d13066 100644
--- a/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java
@@ -54,16 +54,27 @@
@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 @@
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 eb56e93..19177df 100644
--- a/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java
@@ -69,21 +69,33 @@
@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 @@
@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 2b1f420..6bdc926 100644
--- a/core/java/android/app/smartspace/uitemplatedata/Icon.java
+++ b/core/java/android/app/smartspace/uitemplatedata/Icon.java
@@ -69,7 +69,10 @@
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 @@
}
/**
- * 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 9c8330d..48af9c1 100644
--- a/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java
@@ -62,18 +62,30 @@
@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 @@
@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 7df5238..38692cd 100644
--- a/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java
@@ -63,18 +63,30 @@
@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 @@
@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 6f6034d..b1535f1 100644
--- a/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java
@@ -62,18 +62,30 @@
@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 @@
@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 83ff6ab..b8e1afb 100644
--- a/core/java/android/app/smartspace/uitemplatedata/TapAction.java
+++ b/core/java/android/app/smartspace/uitemplatedata/TapAction.java
@@ -58,7 +58,13 @@
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 @@
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 @@
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 @@
out.writeTypedObject(mPendingIntent, flags);
out.writeTypedObject(mUserHandle, flags);
out.writeBundle(mExtras);
+ out.writeBoolean(mShouldShowOnLockscreen);
}
@Override
@@ -158,6 +175,7 @@
+ ", mPendingIntent=" + mPendingIntent
+ ", mUserHandle=" + mUserHandle
+ ", mExtras=" + mExtras
+ + ", mShouldShowOnLockscreen=" + mShouldShowOnLockscreen
+ '}';
}
@@ -174,14 +192,16 @@
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 @@
}
/**
+ * 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 @@
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 b733394..e1afce7 100644
--- a/core/java/android/app/smartspace/uitemplatedata/Text.java
+++ b/core/java/android/app/smartspace/uitemplatedata/Text.java
@@ -131,16 +131,6 @@
}
/**
- * 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/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 4e00da1..47bec61 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -49,21 +49,19 @@
/**
* 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 @@
}
/** 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 d50a6ba..99ce147 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -148,6 +148,8 @@
}
}
};
+ @Nullable
+ private VirtualAudioDevice mVirtualAudioDevice;
private VirtualDevice(
IVirtualDeviceManager service,
@@ -255,8 +257,8 @@
}
/**
- * 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 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+ if (mVirtualAudioDevice != null) {
+ mVirtualAudioDevice.close();
+ mVirtualAudioDevice = null;
+ }
}
/**
@@ -351,8 +357,10 @@
* 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 @@
@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 5c246d3..c816da7 100644
--- a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java
+++ b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java
@@ -60,7 +60,6 @@
/** 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 @@
/** 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 38e37ec..3f7299f 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.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 @@
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 @@
/**
* 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 @@
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 bc71bd6..c6a1045 100644
--- a/core/java/android/companion/virtual/audio/VirtualAudioSession.java
+++ b/core/java/android/companion/virtual/audio/VirtualAudioSession.java
@@ -120,7 +120,7 @@
@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 @@
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 45d4c09..6113932 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 @@
* 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 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 7b0092d..bac212a 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 @@
* 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/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 4399af9..a3cc01c 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -158,36 +158,30 @@
}
+
+ /**
+ * Constant for software toggle.
+ */
+ public static final int TOGGLE_TYPE_SOFTWARE =
+ SensorPrivacyIndividualEnabledSensorProto.SOFTWARE;
+
+ /**
+ * Constant for hardware toggle.
+ */
+ public static final int TOGGLE_TYPE_HARDWARE =
+ SensorPrivacyIndividualEnabledSensorProto.HARDWARE;
+
/**
* Types of toggles which can exist for sensor privacy
+ *
* @hide
*/
- public static class ToggleTypes {
- private ToggleTypes() {}
-
- /**
- * Constant for software toggle.
- */
- public static final int SOFTWARE = SensorPrivacyIndividualEnabledSensorProto.SOFTWARE;
-
- /**
- * Constant for hardware toggle.
- */
- public static final int HARDWARE = SensorPrivacyIndividualEnabledSensorProto.HARDWARE;
-
- /**
- * Types of toggles which can exist for sensor privacy
- *
- * @hide
- */
- @IntDef(value = {
- SOFTWARE,
- HARDWARE
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ToggleType {}
-
- }
+ @IntDef(value = {
+ TOGGLE_TYPE_SOFTWARE,
+ TOGGLE_TYPE_HARDWARE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ToggleType {}
/**
* Types of state which can exist for the sensor privacy toggle
@@ -232,20 +226,23 @@
/**
* Callback invoked when the sensor privacy state changes.
*
+ * @param params Parameters describing the new state
+ */
+ default void onSensorPrivacyChanged(@NonNull SensorPrivacyChangedParams params) {
+ onSensorPrivacyChanged(params.mSensor, params.mEnabled);
+ }
+
+ /**
+ * Callback invoked when the sensor privacy state changes.
+ *
* @param sensor the sensor whose state is changing
* @param enabled true if sensor privacy is enabled, false otherwise.
+ *
+ * @deprecated Please use
+ * {@link #onSensorPrivacyChanged(SensorPrivacyChangedParams)}
*/
+ @Deprecated
void onSensorPrivacyChanged(int sensor, boolean enabled);
- }
-
- /**
- * A class implementing this interface can register with the {@link
- * android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy
- * state changes.
- *
- * @hide
- */
- public interface OnToggleSensorPrivacyChangedListener {
/**
* A class containing information about what the sensor privacy state has changed to.
@@ -262,7 +259,7 @@
mEnabled = enabled;
}
- public @ToggleTypes.ToggleType int getToggleType() {
+ public @ToggleType int getToggleType() {
return mToggleType;
}
@@ -274,13 +271,6 @@
return mEnabled;
}
}
-
- /**
- * Callback invoked when the sensor privacy state changes.
- *
- * @param params Parameters describing the new state
- */
- void onSensorPrivacyChanged(@NonNull SensorPrivacyChangedParams params);
}
private static final Object sInstanceLock = new Object();
@@ -305,15 +295,15 @@
/** Registered listeners */
@GuardedBy("mLock")
@NonNull
- private final ArrayMap<OnToggleSensorPrivacyChangedListener, Executor> mToggleListeners =
+ private final ArrayMap<OnSensorPrivacyChangedListener, Executor> mToggleListeners =
new ArrayMap<>();
/** Listeners registered using the deprecated APIs and which
- * OnToggleSensorPrivacyChangedListener they're using. */
+ * OnSensorPrivacyChangedListener they're using. */
@GuardedBy("mLock")
@NonNull
private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>,
- OnToggleSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
+ OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
/** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local
* listeners */
@@ -323,9 +313,9 @@
public void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled) {
synchronized (mLock) {
for (int i = 0; i < mToggleListeners.size(); i++) {
- OnToggleSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
+ OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
mToggleListeners.valueAt(i).execute(() -> listener
- .onSensorPrivacyChanged(new OnToggleSensorPrivacyChangedListener
+ .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
.SensorPrivacyChangedParams(toggleType, sensor, enabled)));
}
}
@@ -367,12 +357,24 @@
}
/**
+ * Returns the single instance of the SensorPrivacyManager.
+ *
+ * @hide
+ */
+ public static SensorPrivacyManager getInstance(Context context, ISensorPrivacyManager service) {
+ synchronized (sInstanceLock) {
+ sInstance = new SensorPrivacyManager(context, service);
+ return sInstance;
+ }
+ }
+
+ /**
* Checks if the given toggle is supported on this device
* @param sensor The sensor to check
* @return whether the toggle for the sensor is supported on this device.
*/
public boolean supportsSensorToggle(@Sensors.Sensor int sensor) {
- return supportsSensorToggle(ToggleTypes.SOFTWARE, sensor);
+ return supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor);
}
/**
@@ -380,10 +382,8 @@
* @param sensor The sensor to check
* @return whether the toggle for the sensor is supported on this device.
*
- * @hide
*/
- public boolean supportsSensorToggle(@ToggleTypes.ToggleType int toggleType,
- @Sensors.Sensor int sensor) {
+ public boolean supportsSensorToggle(@ToggleType int toggleType, @Sensors.Sensor int sensor) {
try {
Pair key = new Pair(toggleType, sensor);
synchronized (mLock) {
@@ -408,8 +408,6 @@
* @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
* privacy changes.
*
- * {@link #addSensorPrivacyListener(OnToggleSensorPrivacyChangedListener)}
- *
* @hide
*/
@SystemApi
@@ -429,8 +427,6 @@
* @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
* privacy changes.
*
- * {@link #addSensorPrivacyListener(OnToggleSensorPrivacyChangedListener)}
- *
* @hide
*/
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@@ -449,19 +445,22 @@
* @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
* privacy changes.
*
- * {@link #addSensorPrivacyListener(Executor, OnToggleSensorPrivacyChangedListener)}
- *
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull Executor executor,
@NonNull OnSensorPrivacyChangedListener listener) {
-
Pair<Integer, OnSensorPrivacyChangedListener> pair = new Pair(sensor, listener);
- OnToggleSensorPrivacyChangedListener toggleListener = params -> {
- if (params.getSensor() == sensor) {
- listener.onSensorPrivacyChanged(params.getSensor(), params.isEnabled());
+ OnSensorPrivacyChangedListener toggleListener = new OnSensorPrivacyChangedListener() {
+ @Override
+ public void onSensorPrivacyChanged(SensorPrivacyChangedParams params) {
+ if (params.getSensor() == sensor) {
+ listener.onSensorPrivacyChanged(params.getSensor(), params.isEnabled());
+ }
+ }
+ @Override
+ public void onSensorPrivacyChanged(int sensor, boolean enabled) {
}
};
@@ -476,13 +475,14 @@
* Registers a new listener to receive notification when the state of sensor privacy
* changes.
*
- * @param listener the OnToggleSensorPrivacyChangedListener to be notified when the state of
+ * @param listener the OnSensorPrivacyChangedListener to be notified when the state of
* sensor privacy changes.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public void addSensorPrivacyListener(@NonNull OnToggleSensorPrivacyChangedListener listener) {
+ public void addSensorPrivacyListener(@NonNull OnSensorPrivacyChangedListener listener) {
addSensorPrivacyListener(mContext.getMainExecutor(), listener);
}
@@ -492,14 +492,15 @@
* changes.
*
* @param executor the executor to dispatch the callback on
- * @param listener the OnToggleSensorPrivacyChangedListener to be notified when the state of
+ * @param listener the OnSensorPrivacyChangedListener to be notified when the state of
* sensor privacy changes.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
public void addSensorPrivacyListener(@NonNull Executor executor,
- @NonNull OnToggleSensorPrivacyChangedListener listener) {
+ @NonNull OnSensorPrivacyChangedListener listener) {
synchronized (mLock) {
addSensorPrivacyListenerLocked(executor, listener);
}
@@ -507,7 +508,7 @@
@GuardedBy("mLock")
private void addSensorPrivacyListenerLocked(@NonNull Executor executor,
- @NonNull OnToggleSensorPrivacyChangedListener listener) {
+ @NonNull OnSensorPrivacyChangedListener listener) {
if (!mToggleListenerRegistered) {
try {
mService.addToggleSensorPrivacyListener(mIToggleListener);
@@ -529,10 +530,6 @@
* @param listener the OnSensorPrivacyChangedListener to be unregistered from notifications when
* sensor privacy changes.
*
- * {@link #removeSensorPrivacyListener(OnToggleSensorPrivacyChangedListener)} with
- * {@link #addSensorPrivacyListener(OnToggleSensorPrivacyChangedListener)} or
- * {@link #addSensorPrivacyListener(Executor, OnToggleSensorPrivacyChangedListener)}
- *
* @hide
*/
@SystemApi
@@ -541,7 +538,7 @@
@NonNull OnSensorPrivacyChangedListener listener) {
Pair<Integer, OnSensorPrivacyChangedListener> pair = new Pair(sensor, listener);
synchronized (mLock) {
- OnToggleSensorPrivacyChangedListener onToggleSensorPrivacyChangedListener =
+ OnSensorPrivacyChangedListener onToggleSensorPrivacyChangedListener =
mLegacyToggleListeners.remove(pair);
if (onToggleSensorPrivacyChangedListener != null) {
removeSensorPrivacyListenerLocked(onToggleSensorPrivacyChangedListener);
@@ -553,14 +550,15 @@
* Unregisters the specified listener from receiving notifications when the state of any sensor
* privacy changes.
*
- * @param listener the {@link OnToggleSensorPrivacyChangedListener} to be unregistered from
+ * @param listener the {@link OnSensorPrivacyChangedListener} to be unregistered from
* notifications when sensor privacy changes.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
public void removeSensorPrivacyListener(
- @NonNull OnToggleSensorPrivacyChangedListener listener) {
+ @NonNull OnSensorPrivacyChangedListener listener) {
synchronized (mLock) {
removeSensorPrivacyListenerLocked(listener);
}
@@ -568,7 +566,7 @@
@GuardedBy("mLock")
private void removeSensorPrivacyListenerLocked(
- @NonNull OnToggleSensorPrivacyChangedListener listener) {
+ @NonNull OnSensorPrivacyChangedListener listener) {
mToggleListeners.remove(listener);
if (mToggleListeners.size() == 0) {
try {
@@ -586,12 +584,15 @@
*
* @return true if sensor privacy is currently enabled, false otherwise.
*
+ * @deprecated Prefer to use {@link #isSensorPrivacyEnabled(int, int)}
+ *
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) {
- return isSensorPrivacyEnabled(ToggleTypes.SOFTWARE, sensor);
+ return isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor);
}
/**
@@ -601,8 +602,9 @@
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public boolean isSensorPrivacyEnabled(@ToggleTypes.ToggleType int toggleType,
+ public boolean isSensorPrivacyEnabled(@ToggleType int toggleType,
@Sensors.Sensor int sensor) {
try {
return mService.isToggleSensorPrivacyEnabled(toggleType, sensor);
@@ -613,14 +615,16 @@
/**
* Returns whether sensor privacy is currently enabled for a specific sensor.
- * Combines the state of the SW + HW toggles and returns the actual privacy state.
+ * Combines the state of the SW + HW toggles and returns true if either the
+ * SOFTWARE or the HARDWARE toggles are enabled.
*
* @return true if sensor privacy is currently enabled, false otherwise.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public boolean isToggleSensorPrivacyEnabled(@Sensors.Sensor int sensor) {
+ public boolean areAnySensorPrivacyTogglesEnabled(@Sensors.Sensor int sensor) {
try {
return mService.isCombinedToggleSensorPrivacyEnabled(sensor);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 32a5ee7..18d86d6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -881,13 +881,13 @@
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/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
index 02db274..29b4570 100644
--- a/core/java/android/os/logcat/ILogcatManagerService.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -19,10 +19,54 @@
/**
* @hide
*/
-interface ILogcatManagerService {
+oneway interface ILogcatManagerService {
+ /**
+ * The function is called by logd to notify LogcatManagerService
+ * that a client makes privileged log data access request.
+ *
+ * @param uid The UID of client who makes the request.
+ * @param gid The GID of client who makes the request.
+ * @param pid The PID of client who makes the request.
+ * @param fd The FD (Socket) of client who makes the request.
+ */
void startThread(in int uid, in int gid, in int pid, in int fd);
+
+
+ /**
+ * The function is called by logd to notify LogcatManagerService
+ * that a client finished the privileged log data access.
+ *
+ * @param uid The UID of client who makes the request.
+ * @param gid The GID of client who makes the request.
+ * @param pid The PID of client who makes the request.
+ * @param fd The FD (Socket) of client who makes the request.
+ */
void finishThread(in int uid, in int gid, in int pid, in int fd);
+
+
+ /**
+ * The function is called by UX component to notify
+ * LogcatManagerService that the user approved
+ * the privileged log data access.
+ *
+ * @param uid The UID of client who makes the request.
+ * @param gid The GID of client who makes the request.
+ * @param pid The PID of client who makes the request.
+ * @param fd The FD (Socket) of client who makes the request.
+ */
void approve(in int uid, in int gid, in int pid, in int fd);
+
+
+ /**
+ * The function is called by UX component to notify
+ * LogcatManagerService that the user declined
+ * the privileged log data access.
+ *
+ * @param uid The UID of client who makes the request.
+ * @param gid The GID of client who makes the request.
+ * @param pid The PID of client who makes the request.
+ * @param fd The FD (Socket) of client who makes the request.
+ */
void decline(in int uid, in int gid, in int pid, in int fd);
}
diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java
index f5c59b5..462ac14 100644
--- a/core/java/android/service/attention/AttentionService.java
+++ b/core/java/android/service/attention/AttentionService.java
@@ -128,9 +128,9 @@
/** {@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 @@
/**
* 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 @@
}
}
- /** 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 8bb881b..e3ce114 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 @@
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
similarity index 77%
rename from core/java/android/service/attention/IProximityCallback.aidl
rename to core/java/android/service/attention/IProximityUpdateCallback.aidl
index 9ecf9bc..26daa5c 100644
--- a/core/java/android/service/attention/IProximityCallback.aidl
+++ b/core/java/android/service/attention/IProximityUpdateCallback.aidl
@@ -5,6 +5,6 @@
*
* @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 179d39d..9a11923 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -1318,6 +1318,7 @@
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 22cffc1..6a65efb 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -199,7 +199,7 @@
}
private void dispatchTriggerModelDownload(Intent intent) {
- RecognitionService.this.triggerModelDownload(intent);
+ RecognitionService.this.onTriggerModelDownload(intent);
}
private class StartListeningArgs {
@@ -283,7 +283,7 @@
/**
* 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 ff04825..c5ab82d 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -18,13 +18,17 @@
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 @@
*/
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 @@
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 c08177e..98cef958 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -389,9 +389,7 @@
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/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
index 6de0316..ef04b2c 100644
--- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
+++ b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
@@ -105,7 +105,7 @@
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 aaaf729..7718a3b 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -364,8 +364,13 @@
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 @@
mAllowEnterPip = in.readBoolean();
mStartRotation = in.readInt();
mEndRotation = in.readInt();
+ mEndFixedRotation = in.readInt();
mRotationAnimation = in.readInt();
mBackgroundColor = in.readInt();
}
@@ -441,6 +447,11 @@
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 @@
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 @@
dest.writeBoolean(mAllowEnterPip);
dest.writeInt(mStartRotation);
dest.writeInt(mEndRotation);
+ dest.writeInt(mEndFixedRotation);
dest.writeInt(mRotationAnimation);
dest.writeInt(mBackgroundColor);
}
@@ -584,7 +603,8 @@
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 17b675f..5007df5 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 @@
* @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 @@
* 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 @@
* @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/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index e1a67d8..34c47ed 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -98,6 +98,7 @@
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 @@
mHandler = handler;
mChoreographer = choreographer;
mSurfaceControlWrapper = surfaceControlWrapper;
+ mDeferMonitoring = config.shouldDeferMonitor();
// HWUI instrumentation init.
mRendererWrapper = mSurfaceOnly ? null : renderer;
@@ -228,12 +230,25 @@
*/
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 @@
*/
@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 @@
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 @@
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 5a66e9a..3746bfd 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -717,6 +717,7 @@
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 @@
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 @@
}
/**
+ * 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 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 @@
? context
: (view != null ? view.getContext().getApplicationContext() : null);
mSurfaceControl = surfaceControl;
+ mDeferMonitor = deferMonitor;
validate();
}
@@ -901,6 +916,13 @@
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/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index 80d8bd7..8c61a12 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 @@
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/res/res/anim/popup_enter_material.xml b/core/res/res/anim/popup_enter_material.xml
index ef5b7c0..9f771b9 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 1efa702..2b79ddf 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/dimens.xml b/core/res/res/values/dimens.xml
index 39b41b5..744c3dab 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -994,4 +994,8 @@
<!-- 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 f531c3a..e7eeecc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2358,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 0000000..c6f5924
--- /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 28f9ccc..90844ea 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 @@
@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 @@
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 @@
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 152992c..659cd98 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 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.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 java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Tests the TextView widget from an Activity
@@ -116,11 +120,16 @@
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 @@
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 @@
// 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 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 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
});
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 @@
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 @@
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 @@
});
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
}
@Test
@@ -385,7 +387,7 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 @@
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 @@
// 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 @@
() -> 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 @@
// 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 @@
() -> 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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
});
// 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 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 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 @@
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 @@
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 4f95cb8..0000000
--- 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 52cb9f3..a52d2e8 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -86,11 +86,13 @@
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 cd42a34..23ec3ea 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 @@
.check(matches(isDisplayed()));
}
+ @Ignore // b/220067877
@Test
public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
// enable the work tab feature flag
@@ -2304,6 +2305,7 @@
assertThat(logger.numCalls(), is(5));
}
+ @Ignore // b/220067877
@Test
public void testCopyTextToClipboardLogging() throws Exception {
Intent sendIntent = createSendTextIntent();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index ac5daf0..21b2cb0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -481,6 +481,9 @@
<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 e50ad38..9b41468 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 @@
*/
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/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 9219baa..c6a68dc 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 @@
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 656dae3..79a24b7 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.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 @@
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 @@
mMainExecutor = mainExecutor;
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
+ mCompatUIHintsState = new CompatUIHintsState();
}
/** Returns implementation of {@link CompatUI}. */
@@ -259,19 +260,9 @@
@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 7c6780a..bce3ec4 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 @@
int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
@VisibleForTesting
- boolean mShouldShowSizeCompatHint;
- @VisibleForTesting
- boolean mShouldShowCameraCompatHint;
+ CompatUIHintsState mCompatUIHintsState;
@Nullable
@VisibleForTesting
@@ -70,13 +68,12 @@
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 @@
}
// 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 @@
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 2e54c79..c94f3d1 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 @@
@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 @@
@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 @@
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 5c205f9..8f9636c 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.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.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 @@
/**
* 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 @@
}
/**
+ * 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 67b3983..1eb9501 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 @@
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 @@
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (isLaunchIntoPipTask()) {
+ exitLaunchIntoPipTask(wct);
+ return;
+ }
if (ENABLE_SHELL_TRANSITIONS) {
if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
@@ -468,6 +476,14 @@
});
}
+ 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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
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 3e5d5f6..60aac68 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 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.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 @@
/** 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 @@
@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 @@
// 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 @@
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 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 @@
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 @@
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 @@
}
// Keep track of the PIP task.
mCurrentPipTaskToken = enterPip.getContainer();
+ mHasFadeOut = false;
if (mFinishCallback != null) {
mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
@@ -465,12 +511,25 @@
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 @@
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 @@
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 @@
}
} 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 @@
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 @@
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 @@
.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 3403fb5..02e713d2 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 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 556742e..574a9f4 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 0a3321e..a57d3e6 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 19e020a..5fc80db 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 8729bb6..87e927f 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.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 @@
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 6c80daa..0ff260b 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 @@
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 4bc8eb1..388b5e0 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 352805b..7d3e718 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.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 @@
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 f9cfd12..e79b803 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
}
@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 29888e1..f140c68 100644
--- a/location/java/android/location/SatellitePvt.java
+++ b/location/java/android/location/SatellitePvt.java
@@ -144,8 +144,8 @@
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 @@
@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 @@
/**
* 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 @@
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 @@
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 @@
@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 @@
* @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 @@
* @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 @@
* @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 @@
/**
* 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 @@
/**
* 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 c7c1d54..6878f9d 100644
--- a/media/java/Android.bp
+++ b/media/java/Android.bp
@@ -16,7 +16,9 @@
exclude_srcs: [
":framework-media-tv-tunerresourcemanager-sources-aidl",
],
- visibility: ["//frameworks/base"],
+ visibility: [
+ "//frameworks/base",
+ ],
}
filegroup {
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 5348d4e..74c5499 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -112,20 +112,11 @@
// 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 @@
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 @@
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 @@
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 @@
*/
@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/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp b/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp
index f8a8168..39cbaf7 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 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 e8b3d4c..ef9ebb5 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_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.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 @@
// 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 @@
*/
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 @@
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 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 @@
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 @@
// 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 @@
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/SettingsSpinner/res/drawable/settings_spinner_background.xml b/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_background.xml
deleted file mode 100644
index 139cd95..0000000
--- a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_background.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<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">
-
- <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="?priv-android:attr/colorAccentPrimary"/>
- <size android:height="@dimen/spinner_height"/>
- </shape>
- </item>
-
- <item
- android:gravity="center|end"
- android:width="18dp"
- android:height="18dp"
- android:end="8dp"
- android:drawable="@drawable/arrow_drop_down"/>
- </layer-list>
- </item>
-</ripple>
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
similarity index 78%
rename from packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_dropdown_view.xml
rename to packages/SettingsLib/SettingsSpinner/res/layout-v31/settings_spinner_dropdown_view.xml
index a342c84..cea1133 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 7d5b6db..4c75344 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
similarity index 72%
rename from packages/SettingsLib/SettingsSpinner/res/values-night/colors.xml
rename to packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml
index abcf822..8fda876 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 0000000..fc3ec43
--- /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 799b35e..0000000
--- 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 f665f38..8ea1f9a 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
similarity index 77%
rename from packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinnerAdapter.java
rename to packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java
index 83da512..2611207 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 SettingsSpinnerAdapter(Context context) {
super(context, DEFAULT_RESOURCE);
- setDropDownViewResource(DFAULT_DROPDOWN_RESOURCE);
+ setDropDownViewResource(getDropdownResource());
mDefaultInflater = LayoutInflater.from(context);
}
@@ -59,6 +59,11 @@
* 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 d993e44..6952875 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.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 @@
@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 14286fa..0000000
--- 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
similarity index 93%
rename from packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_arrow_drop_down.xml
index 0544526..770a69d 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 a4c780b..ae63928 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 0000000..e1764af
--- /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
similarity index 75%
rename from packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_dropdown_background.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_dropdown_background.xml
index aa451ae..056fb82 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/SettingsSpinner/res/drawable/arrow_drop_down.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_arrow_drop_down.xml
similarity index 80%
copy from packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml
copy to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_arrow_drop_down.xml
index 0544526..6ed215d 100644
--- a/packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/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.
@@ -16,11 +16,11 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="18"
- android:viewportHeight="18"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
android:width="24dp"
android:height="24dp">
<path
android:pathData="M7 10l5 5 5 -5z"
- android:fillColor="@android:color/black"/>
+ android:fillColor="?android:attr/textColorPrimary"/>
</vector>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_spinner_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_spinner_background.xml
new file mode 100644
index 0000000..7466712
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_spinner_background.xml
@@ -0,0 +1,49 @@
+<?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="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="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 3f2b8ac..77c4533 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 ec3c336..6adb789 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 11546c8..29fdab1 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 8e7226b..9d39911 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 7bf75bc..e9bbcc78 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 aaab0f0..fa27bb6 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 2d881d1..8dc0f3c 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/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java
index 53a382a..b81d13d 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.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 @@
private Context mContext;
private PreferenceViewHolder mViewHolder;
- private SettingsSpinner mSpinner;
+ private Spinner mSpinner;
private SettingsSpinnerPreference mSpinnerPreference;
@Before
@@ -53,7 +51,7 @@
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 d7b366e..bb6b293 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 @@
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 d53a3e8..298ee90 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.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 @@
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 @@
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 7be176a..a8e6075 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.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 @@
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 @@
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 6f7f73a..c122a37 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.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 @@
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 @@
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 3b7fbc7..c7e96bc 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 @@
}
@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 @@
}
@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 741fe4f..1df326a 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 46adfeb..f7bcf1f 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -39,5 +39,5 @@
],
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 4540b77..c3f6a5d 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 @@
* 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 @@
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 @@
}
/**
+ * 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 @@
* this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
* before the cancellation.
*/
- @JvmDefault
fun onLaunchAnimationCancelled() {}
}
@@ -317,7 +341,9 @@
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 @@
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 @@
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 @@
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 3051d80..a3c5649 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 @@
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.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 @@
/**
* 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 @@
dialog = dialog,
animateBackgroundBoundsChange,
animatedParent,
- forceDisableSynchronization
+ isForTesting
)
openedDialogs.add(animatedDialog)
@@ -141,6 +145,100 @@
}
/**
+ * 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 @@
// 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 @@
* (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 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 77386cf..a4c5c30 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 @@
* 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 @@
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 @@
* 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 @@
* 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 @@
}
/** 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 @@
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 @@
}
controller.onLaunchAnimationEnd(isExpandingFullyAbove)
launchContainerOverlay.remove(windowBackgroundLayer)
+
+ if (moveBackgroundLayerWhenAppIsVisible) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ }
}
})
@@ -318,11 +340,29 @@
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 0000000..5b3e45c
--- /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/res/drawable/ic_chevron_icon.xml b/packages/SystemUI/res/drawable/ic_chevron_icon.xml
new file mode 100644
index 0000000..acbbbcb
--- /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 5d80da8..e1dbe69 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/idle_host_view.xml b/packages/SystemUI/res/layout/idle_host_view.xml
deleted file mode 100644
index f407874..0000000
--- a/packages/SystemUI/res/layout/idle_host_view.xml
+++ /dev/null
@@ -1,23 +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.
- -->
-
-
-<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"/>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 39d7f4f..c7910afc 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 b318bbc..f1e93c2 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 faf518e..cbda439 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 bb1ffa8..178f93a 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 a08d824..3704134 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 b983545..09ae3f9 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 11fffd0..f5084f5 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.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.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 @@
&& 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 @@
.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 @@
}
/**
+ * 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 2d59e13..0000000
--- 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 e1f1ac4..814b251 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.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.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 javax.inject.Named;
import javax.inject.Provider;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
@@ -58,22 +52,12 @@
*/
@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 @@
}
/**
- * 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 47e749c..4819bf5 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 @@
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 @@
cws.control?.let {
title.setText(it.title)
subtitle.setText(it.subtitle)
+ chevronIcon.visibility = if (usePanel()) View.VISIBLE else View.INVISIBLE
}
}
@@ -469,6 +471,7 @@
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 b926692..b02074a 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.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 java.util.Optional;
+import dagger.BindsInstance;
import dagger.Subcomponent;
/**
@@ -64,6 +69,10 @@
*/
@Subcomponent.Builder
interface Builder {
+
+ @BindsInstance
+ Builder setShellMainThread(@Nullable @ShellMainThread HandlerThread t);
+
WMComponent build();
}
@@ -120,10 +129,10 @@
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 befb648..789ad62 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 @@
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 f0371fc..84fa6a6 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 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.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.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 @@
private Optional<StatusBar> mStatusBarOptional;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private LockPatternUtils mLockPatternUtils;
+ private float mWindowDimAmount;
protected ViewGroup mContainer;
@@ -2251,6 +2256,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeLayout();
+ mWindowDimAmount = getWindow().getAttributes().dimAmount;
}
@Override
@@ -2459,6 +2465,96 @@
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 fa00dbb..0000000
--- 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 7d79279..0000000
--- 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 624d01f..0000000
--- 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 be0a378..0000000
--- 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 7b06b96..0000000
--- 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 9754b1f..0000000
--- 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 2dff947..c3b4354 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.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 @@
@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 @@
@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 @@
@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 @@
@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 5b29719..3cd3905 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 @@
}
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 @@
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 @@
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 @@
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 @@
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 85d1795..1f11d0c 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 @@
.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 @@
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 e141cccc..7bc0f52 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.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 @@
private int mColorInactiveItem;
private int mColorSeekbarProgress;
private int mColorButtonBackground;
+ private int mColorItemBackground;
@Inject
public MediaOutputController(@NonNull Context context, String packageName,
@@ -142,6 +144,8 @@
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 @@
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 @@
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 @@
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);
- mCallback.dismissDialog();
+ if (controller == null) {
+ 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 @@
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 4993105..ee2fba0 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.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 @@
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 @@
/** 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 @@
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 @@
// 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 18623c0..214a888 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 @@
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.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 @@
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 da767ea..482e604 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 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 @@
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 d16c019..8b39e5c 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 @@
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 @@
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 aa1117c..a049736 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.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 @@
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 @@
NavBarHelper navBarHelper,
TaskbarDelegate taskbarDelegate,
NavigationBar.Factory navigationBarFactory,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
DumpManager dumpManager,
AutoHideController autoHideController,
LightBarController lightBarController,
@@ -122,6 +125,7 @@
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 @@
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 9199911..9ea2763 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 @@
// 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 17f85ee..3c7933f 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 @@
: 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 a70f534..e088f54 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.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 @@
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 4fe155c..e1d2070 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 @@
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 8b6ddb4..d1c7844 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 @@
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 @@
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 @@
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 f89b7a3..b3bc3be 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.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 @@
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 launchWifiNetworkDetailsSetting(String key) {
+ void launchNetworkSetting(View view) {
+ startActivity(getSettingsIntent(), view);
+ }
+
+ 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 8c8c5c8..88aa734 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 @@
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 @@
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/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index b312ce2..8366bdd 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 @@
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 @@
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 fcbe179..02870a3 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.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 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 @@
}
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 5361a671..2b924a4 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.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 @@
}
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 d93c013..a0f8d05 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.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 @@
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 @@
@Nullable
private CommunalHostViewController mCommunalViewController;
private KeyguardStatusViewController mKeyguardStatusViewController;
- @Nullable
- private IdleHostViewController mIdleHostViewController;
private LockIconViewController mLockIconViewController;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
private NotificationsQSContainerController mNotificationsQSContainerController;
@@ -369,7 +363,6 @@
private VelocityTracker mQsVelocityTracker;
private boolean mQsTracking;
- private IdleHostView mIdleHostView;
private CommunalHostView mCommunalView;
/**
@@ -768,7 +761,6 @@
KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
CommunalViewComponent.Factory communalViewComponentFactory,
- IdleViewComponent.Factory idleViewComponentFactory,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
@@ -839,7 +831,6 @@
mCommunalViewComponentFactory = communalViewComponentFactory;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
- mIdleViewComponentFactory = idleViewComponentFactory;
mDepthController = notificationShadeDepthController;
mContentResolver = contentResolver;
mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
@@ -966,7 +957,6 @@
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 @@
.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 @@
mView.findViewById(R.id.keyguard_status_view),
userAvatarContainer,
keyguardUserSwitcherView,
- mIdleHostView,
mCommunalView);
NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
@@ -1101,7 +1084,6 @@
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
FrameLayout userAvatarView,
KeyguardUserSwitcherView keyguardUserSwitcherView,
- IdleHostView idleHostView,
CommunalHostView communalView) {
// Re-associate the KeyguardStatusViewController
KeyguardStatusViewComponent statusViewComponent =
@@ -1109,13 +1091,6 @@
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 @@
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 8e4feb8..82e0e67 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 @@
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 @@
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 @@
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 @@
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 d42a423..11d9c31 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.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 @@
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 @@
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 @@
private boolean mPulsing;
private boolean mGesturalNav;
private boolean mIsDocked;
+ private boolean mScreenOffAnimationPlaying;
protected boolean mFirstUpdate = true;
protected boolean mLastShowing;
@@ -199,6 +205,7 @@
private boolean mLastIsDocked;
private boolean mLastPulsing;
private int mLastBiometricMode;
+ private boolean mLastScreenOffAnimationPlaying;
private boolean mQsExpanded;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -246,6 +253,7 @@
NotificationMediaManager notificationMediaManager,
KeyguardBouncer.Factory keyguardBouncerFactory,
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
+ Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
Lazy<ShadeController> shadeController,
LatencyTracker latencyTracker) {
mContext = context;
@@ -264,6 +272,8 @@
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
+ mFoldAodAnimationController = sysUIUnfoldComponent
+ .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
}
@Override
@@ -317,6 +327,9 @@
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 @@
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 @@
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
mLastPulsing = mPulsing;
+ mLastScreenOffAnimationPlaying = mScreenOffAnimationPlaying;
mLastBiometricMode = mBiometricUnlockController.getMode();
mLastGesturalNav = mGesturalNav;
mLastIsDocked = mIsDocked;
@@ -1054,14 +1072,15 @@
/**
* @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 @@
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 @@
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 2ba37c2..09fca100 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 @@
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 6e1ec9c..9722528 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.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 @@
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 @@
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 @@
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 2e627a8..2a9076e 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 @@
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 @@
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 @@
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/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index 589eeb5..d5df9fe 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 @@
@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 @@
@Before
fun setup() {
- activityLaunchAnimator = ActivityLaunchAnimator(launchAnimator)
+ activityLaunchAnimator = ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator)
activityLaunchAnimator.callback = callback
activityLaunchAnimator.addListener(listener)
}
@@ -129,13 +129,11 @@
@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 61e78f5..fe2efa5 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 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 @@
@Before
fun setUp() {
dialogLaunchAnimator = DialogLaunchAnimator(
- dreamManager, launchAnimator, forceDisableSynchronization = true)
+ dreamManager, launchAnimator, isForTesting = true)
}
@After
@@ -92,11 +94,6 @@
// 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 @@
assertTrue(firstDialog.isShowing)
assertTrue(secondDialog.isShowing)
runOnMainThreadAndWaitForIdleSync {
- dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
dialogLaunchAnimator.dismissStack(secondDialog)
}
@@ -118,7 +114,63 @@
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 @@
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 500205c..0000000
--- 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 47ab17d..d3c465d 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.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 @@
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 @@
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 8078b6c..5684429 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 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 @@
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 98b9252..0000000
--- 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 3c24a3a..0000000
--- 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 ebc7345..0000000
--- 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 421ae03..11326e7 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 @@
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 @@
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 14afece..794bc09 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.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.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 f05d621..ea0a5a4 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 @@
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 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.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 @@
@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 @@
}
@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 @@
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 @@
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 44f691c..117a6c8 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 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 @@
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 @@
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 @@
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 dc39893..b440064 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
/** 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 @@
/** 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/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 73d2b0b..bb42c12 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.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 @@
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 090ce43..48d3857 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 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 @@
@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 @@
.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 @@
@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 @@
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 @@
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 @@
@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 @@
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 0d65541..a2959e2 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 @@
private LocationController mLocationController;
@Mock
private DialogLaunchAnimator mDialogLaunchAnimator;
+ @Mock
+ private View mDialogLaunchView;
private TestableResources mTestableResources;
private InternetDialogController mInternetDialogController;
@@ -384,7 +386,8 @@
@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 @@
@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 ed35dcb..cf97bda 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 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 8695b29..030c65a 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.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 @@
@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 @@
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 @@
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 9f152e1..b7b3088 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 @@
@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 f6eff82..479c271 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.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 @@
@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 @@
.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 @@
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 107ba81..8b93de5 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.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.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -100,6 +103,8 @@
@Mock
private ShadeController mShadeController;
@Mock
+ private SysUIUnfoldComponent mSysUiUnfoldComponent;
+ @Mock
private DreamOverlayStateController mDreamOverlayStateController;
@Mock
private LatencyTracker mLatencyTracker;
@@ -130,6 +135,7 @@
mock(NotificationMediaManager.class),
mKeyguardBouncerFactory,
mKeyguardMessageAreaFactory,
+ Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
mLatencyTracker);
mStatusBarKeyguardViewManager.registerStatusBar(
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index e122993..75d9b7e 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -275,12 +275,10 @@
if (index >= 0) {
RemoteViews presentation = dataset.getFieldDialogPresentation(index);
if (presentation == null) {
- Slog.w(TAG, "fallback to presentation");
- presentation = dataset.getFieldPresentation(index);
- }
- if (presentation == null) {
- Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
- + "service didn't provide a presentation for it on " + dataset);
+ if (sDebug) {
+ Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+ + "service didn't provide a presentation for it on " + dataset);
+ }
continue;
}
final View view;
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 4afa96c..bc1f28d 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.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 @@
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 @@
@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 @@
* 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 387d911..c0a904f 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 @@
});
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 d218af3..6986d3b 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.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 @@
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 @@
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 @@
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 @@
}
}
- @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 8e53101..16ff167 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -18,6 +18,7 @@
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 @@
}
}
+ /** 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/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index 90aefe0..d239c02 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 @@
"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 @@
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 @@
"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 @@
"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 @@
"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 @@
}
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 @@
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 @@
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 @@
*
* @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 d2fa386..567d1ae 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.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.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 @@
* @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 @@
/** 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 @@
@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 @@
@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 @@
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 @@
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 @@
}
}
- class TestableProximityCallbackInternal extends ProximityCallbackInternal {
+ class TestableProximityUpdateCallbackInternal extends ProximityUpdateCallbackInternal {
private double mLastCallbackCode = PROXIMITY_UNKNOWN;
@Override
@@ -932,8 +933,8 @@
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 @@
return cmdClearTestableAttentionService();
case "getLastTestCallbackCode":
return cmdGetLastTestCallbackCode();
- case "getLastTestProximityCallbackCode":
- return cmdGetLastTestProximityCallbackCode();
+ case "getLastTestProximityUpdateCallbackCode":
+ return cmdGetLastTestProximityUpdateCallbackCode();
default:
return handleDefaultCommands(cmd);
}
@@ -990,7 +991,7 @@
private int cmdClearTestableAttentionService() {
sTestAttentionServicePackage = "";
mTestableAttentionCallback.reset();
- mTestableProximityCallback.reset();
+ mTestableProximityUpdateCallback.reset();
resetStates();
return 0;
}
@@ -1011,14 +1012,14 @@
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 @@
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 @@
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 @@
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/display/color/ReduceBrightColorsTintController.java b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
index cb93cc8..f529c4c 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 @@
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/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 34614d5..490e00e 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -329,6 +329,20 @@
if (mStart) {
+ ActivityManagerInternal ami = LocalServices.getService(
+ ActivityManagerInternal.class);
+ boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
+
+ // The instrumented apks only run for testing, so we don't check user permission.
+ if (isCallerInstrumented) {
+ try {
+ getLogdService().approve(mUid, mGid, mPid, mFd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+
// TODO Temporarily approve all the requests to unblock testing failures.
try {
getLogdService().approve(mUid, mGid, mPid, mFd);
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 410fa97..da22b17 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.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 @@
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 @@
}
@DataClass.Generated.Member
- public @Nullable WatchedArrayMap<String,OverlayPaths> getSharedLibraryOverlayPaths() {
- return mSharedLibraryOverlayPaths;
- }
-
- @DataClass.Generated.Member
public @Nullable String getSplashScreenTheme() {
return mSplashScreenTheme;
}
@@ -774,10 +778,10 @@
}
@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 18c45e4..4ad6ed1 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.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 @@
+ 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/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 70a030d..bd58472 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1177,7 +1177,10 @@
@Override
public void onBootPhase(int phase) {
synchronized (mLock) {
- if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ systemReady();
+
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
incrementBootCount();
} else if (phase == PHASE_BOOT_COMPLETED) {
@@ -1203,7 +1206,7 @@
}
}
- public void systemReady() {
+ private void systemReady() {
synchronized (mLock) {
mSystemReady = true;
mDreamManager = getLocalService(DreamManagerInternal.class);
@@ -2684,8 +2687,8 @@
@GuardedBy("mLock")
private void updateUserActivitySummaryLocked(long now, int dirty) {
// Update the status of the user activity timeout timer.
- if ((dirty & (DIRTY_DISPLAY_GROUP_WAKEFULNESS | DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY
- | DIRTY_WAKEFULNESS | DIRTY_SETTINGS | DIRTY_ATTENTIVE)) == 0) {
+ if ((dirty & (DIRTY_DISPLAY_GROUP_WAKEFULNESS | DIRTY_WAKE_LOCKS
+ | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) == 0) {
return;
}
mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
@@ -2772,11 +2775,6 @@
screenDimDuration);
}
- if (isAttentiveTimeoutExpired(powerGroup, now)) {
- groupUserActivitySummary = 0;
- groupNextTimeout = -1;
- }
-
hasUserActivitySummary |= groupUserActivitySummary != 0;
if (nextTimeout == -1) {
@@ -3132,7 +3130,7 @@
Message msg = mHandler.obtainMessage(MSG_SANDMAN);
msg.arg1 = powerGroup.getGroupId();
msg.setAsynchronous(true);
- mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+ mHandler.sendMessage(msg);
}
}
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index ffcb2bd..b4613a7 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -276,7 +276,7 @@
}
/**
- * Called by {@link PowerManagerService#systemReady}, *with no lock held.*
+ * Called by {@link PowerManagerService#onBootPhase}, *with no lock held.*
*/
public void systemReady() {
ConcurrentUtils.wtfIfLockHeld(TAG, mLock);
diff --git a/services/core/java/com/android/server/sensorprivacy/PersistedState.java b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
index 06f5fc0..e79efdb8 100644
--- a/services/core/java/com/android/server/sensorprivacy/PersistedState.java
+++ b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
@@ -296,7 +296,7 @@
SensorState sensorState = states.valueAt(i);
// Do not persist hardware toggle states. Will be restored on reboot
- if (userSensor.mType != SensorPrivacyManager.ToggleTypes.SOFTWARE) {
+ if (userSensor.mType != SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
continue;
}
@@ -478,7 +478,7 @@
for (int j = 0; j < numSensors; j++) {
int sensor = userIndividualEnabled.keyAt(j);
SensorState sensorState = userIndividualEnabled.valueAt(j);
- result.addState(SensorPrivacyManager.ToggleTypes.SOFTWARE,
+ result.addState(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
userId, sensor, sensorState.getState(), sensorState.getLastChange());
}
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 358f69e..a8e2d43 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -39,8 +39,8 @@
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
-import static android.hardware.SensorPrivacyManager.ToggleTypes.HARDWARE;
-import static android.hardware.SensorPrivacyManager.ToggleTypes.SOFTWARE;
+import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
+import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
import static android.os.UserHandle.USER_NULL;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
@@ -310,11 +310,12 @@
// Reset sensor privacy when restriction is added
if (!prevRestrictions.getBoolean(UserManager.DISALLOW_CAMERA_TOGGLE)
&& newRestrictions.getBoolean(UserManager.DISALLOW_CAMERA_TOGGLE)) {
- setToggleSensorPrivacyUnchecked(SOFTWARE, userId, OTHER, CAMERA, false);
+ setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, OTHER, CAMERA, false);
}
if (!prevRestrictions.getBoolean(UserManager.DISALLOW_MICROPHONE_TOGGLE)
&& newRestrictions.getBoolean(UserManager.DISALLOW_MICROPHONE_TOGGLE)) {
- setToggleSensorPrivacyUnchecked(SOFTWARE, userId, OTHER, MICROPHONE, false);
+ setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, OTHER, MICROPHONE,
+ false);
}
}
@@ -565,7 +566,7 @@
*/
private String getSensorUseActivityName(ArraySet<Integer> sensors) {
for (Integer sensor : sensors) {
- if (isToggleSensorPrivacyEnabled(HARDWARE, sensor)) {
+ if (isToggleSensorPrivacyEnabled(TOGGLE_TYPE_HARDWARE, sensor)) {
return mContext.getResources().getString(
R.string.config_sensorUseStartedActivity_hwToggle);
}
@@ -691,7 +692,7 @@
return;
}
- setToggleSensorPrivacyUnchecked(SOFTWARE, userId, source, sensor, enable);
+ setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, enable);
}
private void setToggleSensorPrivacyUnchecked(int toggleType, int userId, int source,
@@ -866,8 +867,8 @@
@Override
public boolean isCombinedToggleSensorPrivacyEnabled(int sensor) {
- return isToggleSensorPrivacyEnabled(SOFTWARE, sensor) || isToggleSensorPrivacyEnabled(
- HARDWARE, sensor);
+ return isToggleSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor)
+ || isToggleSensorPrivacyEnabled(TOGGLE_TYPE_HARDWARE, sensor);
}
private boolean isToggleSensorPrivacyEnabledInternal(int userId, int toggleType,
@@ -879,7 +880,7 @@
@Override
public boolean supportsSensorToggle(int toggleType, int sensor) {
- if (toggleType == SOFTWARE) {
+ if (toggleType == TOGGLE_TYPE_SOFTWARE) {
if (sensor == MICROPHONE) {
return mContext.getResources()
.getBoolean(R.bool.config_supportsMicToggle);
@@ -887,7 +888,7 @@
return mContext.getResources()
.getBoolean(R.bool.config_supportsCamToggle);
}
- } else if (toggleType == SensorPrivacyManager.ToggleTypes.HARDWARE) {
+ } else if (toggleType == TOGGLE_TYPE_HARDWARE) {
if (sensor == MICROPHONE) {
return mContext.getResources()
.getBoolean(R.bool.config_supportsHardwareMicToggle);
@@ -1003,37 +1004,41 @@
final int hwToggleIdx = 1;
// Get SW toggles state
mSensorPrivacyStateController.atomic(() -> {
- prevMicState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(from, SOFTWARE,
- MICROPHONE);
- prevCamState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(from, SOFTWARE,
- CAMERA);
- micState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(to, SOFTWARE,
- MICROPHONE);
- camState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(to, SOFTWARE, CAMERA);
+ prevMicState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(from,
+ TOGGLE_TYPE_SOFTWARE, MICROPHONE);
+ prevCamState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(from,
+ TOGGLE_TYPE_SOFTWARE, CAMERA);
+ micState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(to,
+ TOGGLE_TYPE_SOFTWARE, MICROPHONE);
+ camState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(to,
+ TOGGLE_TYPE_SOFTWARE, CAMERA);
});
// Get HW toggles state
mSensorPrivacyStateController.atomic(() -> {
- prevMicState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(from, HARDWARE,
- MICROPHONE);
- prevCamState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(from, HARDWARE,
- CAMERA);
- micState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(to, HARDWARE,
- MICROPHONE);
- camState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(to, HARDWARE, CAMERA);
+ prevMicState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(from,
+ TOGGLE_TYPE_HARDWARE, MICROPHONE);
+ prevCamState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(from,
+ TOGGLE_TYPE_HARDWARE, CAMERA);
+ micState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(to,
+ TOGGLE_TYPE_HARDWARE, MICROPHONE);
+ camState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(to,
+ TOGGLE_TYPE_HARDWARE, CAMERA);
});
if (from == USER_NULL || prevMicState[swToggleIdx] != micState[swToggleIdx]
|| prevMicState[hwToggleIdx] != micState[hwToggleIdx]) {
- mHandler.handleSensorPrivacyChanged(to, SOFTWARE, MICROPHONE,
+ mHandler.handleSensorPrivacyChanged(to, TOGGLE_TYPE_SOFTWARE, MICROPHONE,
micState[swToggleIdx]);
- mHandler.handleSensorPrivacyChanged(to, HARDWARE, MICROPHONE,
+ mHandler.handleSensorPrivacyChanged(to, TOGGLE_TYPE_HARDWARE, MICROPHONE,
micState[hwToggleIdx]);
setGlobalRestriction(MICROPHONE, micState[swToggleIdx] || micState[hwToggleIdx]);
}
if (from == USER_NULL || prevCamState[swToggleIdx] != camState[swToggleIdx]
|| prevCamState[hwToggleIdx] != camState[hwToggleIdx]) {
- mHandler.handleSensorPrivacyChanged(to, SOFTWARE, CAMERA, camState[swToggleIdx]);
- mHandler.handleSensorPrivacyChanged(to, HARDWARE, CAMERA, camState[hwToggleIdx]);
+ mHandler.handleSensorPrivacyChanged(to, TOGGLE_TYPE_SOFTWARE, CAMERA,
+ camState[swToggleIdx]);
+ mHandler.handleSensorPrivacyChanged(to, TOGGLE_TYPE_HARDWARE, CAMERA,
+ camState[hwToggleIdx]);
setGlobalRestriction(CAMERA, camState[swToggleIdx] || camState[hwToggleIdx]);
}
}
@@ -1437,7 +1442,7 @@
public boolean isSensorPrivacyEnabled(int userId, int sensor) {
return SensorPrivacyService.this
.mSensorPrivacyServiceImpl.isToggleSensorPrivacyEnabledInternal(userId,
- SOFTWARE, sensor);
+ TOGGLE_TYPE_SOFTWARE, sensor);
}
@Override
@@ -1487,10 +1492,12 @@
userId = (userId == UserHandle.USER_CURRENT ? mCurrentUser : userId);
final int realUserId = (userId == UserHandle.USER_NULL ? mContext.getUserId() : userId);
- sps.setToggleSensorPrivacyUnchecked(HARDWARE, realUserId, OTHER, sensor, enable);
+ sps.setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_HARDWARE, realUserId, OTHER, sensor,
+ enable);
// Also disable the SW toggle when disabling the HW toggle
if (!enable) {
- sps.setToggleSensorPrivacyUnchecked(SOFTWARE, realUserId, OTHER, sensor, enable);
+ sps.setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, realUserId, OTHER, sensor,
+ enable);
}
}
}
@@ -1546,9 +1553,9 @@
if (!mIsInEmergencyCall) {
mIsInEmergencyCall = true;
if (mSensorPrivacyServiceImpl
- .isToggleSensorPrivacyEnabled(SOFTWARE, MICROPHONE)) {
+ .isToggleSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, MICROPHONE)) {
mSensorPrivacyServiceImpl.setToggleSensorPrivacyUnchecked(
- SOFTWARE, mCurrentUser, OTHER, MICROPHONE, false);
+ TOGGLE_TYPE_SOFTWARE, mCurrentUser, OTHER, MICROPHONE, false);
mMicUnmutedForEmergencyCall = true;
} else {
mMicUnmutedForEmergencyCall = false;
@@ -1574,7 +1581,7 @@
mIsInEmergencyCall = false;
if (mMicUnmutedForEmergencyCall) {
mSensorPrivacyServiceImpl.setToggleSensorPrivacyUnchecked(
- SOFTWARE, mCurrentUser, OTHER, MICROPHONE, true);
+ TOGGLE_TYPE_SOFTWARE, mCurrentUser, OTHER, MICROPHONE, true);
mMicUnmutedForEmergencyCall = false;
}
}
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 8ec0556..1b15351 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.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 @@
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 @@
registerAccessibilityFloatingMenuStats();
registerMediaCapabilitiesStats();
registerPendingIntentsPerPackagePuller();
+ registerPinnerServiceStats();
}
private void initAndRegisterNetworkStatsPullers() {
@@ -4607,6 +4612,26 @@
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 8b80b4a..597f7f2 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.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 @@
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 @@
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 fdd9913..1f1f40b 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 @@
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 @@
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 @@
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 @@
clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
}
} finally {
- mCallbacks.onVibratorsReleased();
+ mVibratorManagerHooks.onVibrationThreadReleased();
}
}
@@ -263,7 +273,7 @@
private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
if (!mCalledVibrationCompleteCallback) {
mCalledVibrationCompleteCallback = true;
- mCallbacks.onVibrationCompleted(mVibration.id, completedStatus);
+ mVibratorManagerHooks.onVibrationCompleted(mVibration.id, completedStatus);
}
}
@@ -272,25 +282,29 @@
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 @@
}
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 @@
@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 @@
@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 @@
}
}
- /** 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 @@
* 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 @@
}
} finally {
synchronized (mLock) {
- processVibratorCallbacks();
+ processVibratorCompleteCallbacks();
}
}
}
@@ -485,10 +497,10 @@
*/
@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 @@
}
} finally {
synchronized (mLock) {
- processVibratorCallbacks();
+ processVibratorCompleteCallbacks();
}
}
}
@@ -540,7 +552,7 @@
}
} finally {
synchronized (mLock) {
- processVibratorCallbacks();
+ processVibratorCompleteCallbacks();
}
}
}
@@ -548,7 +560,7 @@
@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 @@
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 @@
}
/**
- * 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 @@
* 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 @@
boolean hasTriggered = false;
long maxDuration = 0;
try {
- hasPrepared = mCallbacks.prepareSyncedVibration(
+ hasPrepared = mVibratorManagerHooks.prepareSyncedVibration(
effectMapping.getRequiredSyncCapabilities(),
effectMapping.getVibratorIds());
@@ -821,13 +833,13 @@
// 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 @@
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 @@
* @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 @@
}
@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 @@
* <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 @@
@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 @@
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 @@
* 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 @@
}
@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 @@
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 b2e34da..63f3af3 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 @@
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 @@
VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService,
- mVibrationCallbacks);
+ mVibrationThreadCallbacks);
if (mCurrentVibration == null) {
return startVibrationThreadLocked(vibThread);
@@ -1115,10 +1116,10 @@
}
/**
- * 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 @@
}
@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 87ba859..b0efa5b 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 @@
// 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 @@
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 @@
/**
* 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 @@
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 @@
mRotationAnimationHint = rotationAnimation;
}
+ if (options.getLaunchIntoPipParams() != null) {
+ pictureInPictureArgs = options.getLaunchIntoPipParams();
+ }
+
mOverrideTaskTransition = options.getOverrideTaskTransition();
}
@@ -2515,7 +2537,6 @@
}
removeStartingWindowAnimation(true /* prepareAnimation */);
- // TODO(b/215316431): Add tests
final Task task = getTask();
if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
&& task != null) {
@@ -7695,7 +7716,6 @@
* 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 5ffe214..ef0ee12 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 @@
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 1741d0f..0497477 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 @@
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 @@
@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/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 132396b..0868111 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 @@
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 2a06d8b..ddfdddc 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 @@
*/
private boolean mLastWallpaperVisible = false;
- private Rect mBaseDisplayRect = new Rect();
-
// Accessed directly by all users.
private boolean mLayoutNeeded;
int pendingLayoutChanges;
@@ -488,9 +486,6 @@
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 @@
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 @@
* @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 @@
mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
mDisplayInfo);
- mBaseDisplayRect.set(0, 0, dw, dh);
-
if (isDefaultDisplay) {
mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
mCompatDisplayMetrics);
@@ -2213,14 +2206,14 @@
*/
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 @@
/** 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 @@
+ mBaseDisplayHeight + " on display:" + getDisplayId());
}
}
-
- if (mBaseDisplayWidth != originalWidth || mBaseDisplayHeight != originalHeight
- || mBaseDisplayDensity != originalDensity) {
- mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
- updateBounds();
- }
}
/**
@@ -3021,7 +3004,7 @@
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 @@
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 @@
}
}
- 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 @@
}
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 30fffd3..4148d8b 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 cbefe7f..8f97220 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 @@
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 @@
}
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 d28dfd5..433f1071 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 @@
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 @@
}
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 @@
}
/**
- * @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,17 +263,131 @@
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 WindowState#getInsetsState()
+ */
+ 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.
+ 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;
}
/**
- * @see InsetsStateController#getInsetsForWindowMetrics
+ * 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 getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
- final InsetsState originalState = mStateController.getInsetsForWindowMetrics(attrs);
- return adjustVisibilityForTransientTypes(originalState);
+ 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) {
@@ -644,7 +772,7 @@
}
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 21eea94..4c7a297 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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
}
/**
- * @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 @@
// 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 @@
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 @@
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 @@
* 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 @@
}
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 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
}
private boolean isMirroredSource() {
- if (mWin == null) {
+ if (mWindowContainer == null) {
return false;
}
- final int[] provides = mWin.mAttrs.providesInsetsTypes;
+ if (mWindowContainer.asWindowState() == null) {
+ return false;
+ }
+ final int[] provides = ((WindowState) mWindowContainer).mAttrs.providesInsetsTypes;
if (provides == null) {
return false;
}
@@ -542,9 +578,9 @@
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 a1468cc..32e70d9 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.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 @@
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 @@
return result;
}
+ ArrayMap<Integer, InsetsSourceProvider> getSourceProviders() {
+ return mProviders;
+ }
+
/**
* @return The provider of a specific type.
*/
@@ -303,7 +167,7 @@
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 @@
}
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 @@
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 d535018..8ab2ee0 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 @@
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 @@
.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 @@
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 d1460f4..6bb63d9 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 @@
*/
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 58cf4bb..358a615 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 @@
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 @@
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 1bba103..f0cca18 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 @@
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 @@
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 @@
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 @@
return mRootHomeTask;
}
- @Nullable
- Task getRootRecentsTask() {
- return mRootRecentsTask;
- }
-
Task getRootPinnedTask() {
return mRootPinnedTask;
}
@@ -288,16 +277,6 @@
} 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 @@
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 dd1f29e..7fab94c 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_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.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 @@
* 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 cbef60c..0b965c3 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 @@
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 e1746cc..45fdc04 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.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 @@
* 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 @@
// 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 @@
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 @@
}
}
+ public SparseArray<InsetsSource> getProvidedInsetsSources() {
+ if (mProvidedInsetsSources == null) {
+ mProvidedInsetsSources = new SparseArray<>();
+ }
+ return mProvidedInsetsSources;
+ }
+
DisplayContent getDisplayContent() {
return mDisplayContent;
}
@@ -2962,6 +2998,16 @@
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 49f742c..1ab8cbf 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.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.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 @@
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 @@
*/
private boolean mIsDimming = false;
- private @Nullable InsetsSourceProvider mControllableInsetProvider;
private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
/**
@@ -1647,11 +1641,39 @@
}
/**
- * Returns the insets state for the window. Its sources may be the copies with visibility
- * modification according to the state of transient bars.
+ * See {@link WindowState#getInsetsState(boolean)}
*/
InsetsState getInsetsState() {
- return getDisplayContent().getInsetsPolicy().getInsetsForWindow(this);
+ 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(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 @@
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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2e801ab..e895d37 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2707,15 +2707,6 @@
systemTheme.rebase();
}
- t.traceBegin("MakePowerManagerServiceReady");
- try {
- // TODO: use boot phase
- mPowerManagerService.systemReady();
- } catch (Throwable e) {
- reportWtf("making Power Manager Service ready", e);
- }
- t.traceEnd();
-
// Permission policy service
t.traceBegin("StartPermissionPolicyService");
mSystemServiceManager.startService(PermissionPolicyService.class);
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index f72b23c..2d082e1 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -63,6 +63,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -128,6 +129,19 @@
private final PackageManager mPackageManager;
+ private static final String MIDI_LEGACY_STRING = "MIDI 1.0";
+ private static final String MIDI_UNIVERSAL_STRING = "MIDI 2.0";
+
+ // Used to lock mUsbMidiLegacyDeviceOpenCount and mUsbMidiUniversalDeviceInUse.
+ private final Object mUsbMidiLock = new Object();
+
+ // Number of times a USB MIDI 1.0 device has opened, based on the device name.
+ private final HashMap<String, Integer> mUsbMidiLegacyDeviceOpenCount =
+ new HashMap<String, Integer>();
+
+ // Whether a USB MIDI device has opened, based on the device name.
+ private final HashSet<String> mUsbMidiUniversalDeviceInUse = new HashSet<String>();
+
// UID of BluetoothMidiService
private int mBluetoothServiceUid;
@@ -271,6 +285,12 @@
}
for (DeviceConnection connection : mDeviceConnections.values()) {
+ if (connection.getDevice().getDeviceInfo().getType()
+ == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+ }
+ }
connection.getDevice().removeDeviceConnection(connection);
}
}
@@ -748,6 +768,7 @@
synchronized (mDevicesByInfo) {
for (Device device : mDevicesByInfo.values()) {
if (device.isUidAllowed(uid)) {
+ deviceInfos.add(device.getDeviceInfo());
// UMP devices have protocols that are not PROTOCOL_UNKNOWN
if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
if (device.getDeviceInfo().getDefaultProtocol()
@@ -784,6 +805,15 @@
}
}
+ if (deviceInfo.getType() == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ if (isUsbMidiDeviceInUseLocked(deviceInfo)) {
+ throw new IllegalArgumentException("device already in use: " + deviceInfo);
+ }
+ addUsbMidiDeviceLocked(deviceInfo);
+ }
+ }
+
// clear calling identity so bindService does not fail
final long identity = Binder.clearCallingIdentity();
try {
@@ -1216,4 +1246,82 @@
}
pw.decreaseIndent();
}
+
+ // hold mUsbMidiLock before calling this
+ private boolean isUsbMidiDeviceInUseLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return false;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ // Only one MIDI 2.0 device can be used at once.
+ // Multiple MIDI 1.0 devices can be used at once.
+ if (mUsbMidiUniversalDeviceInUse.contains(deviceName)
+ || ((tagName).equals(MIDI_UNIVERSAL_STRING)
+ && (mUsbMidiLegacyDeviceOpenCount.containsKey(deviceName)))) {
+ return true;
+ }
+ return false;
+ }
+
+ // hold mUsbMidiLock before calling this
+ void addUsbMidiDeviceLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
+ mUsbMidiUniversalDeviceInUse.add(deviceName);
+ } else if ((tagName).equals(MIDI_LEGACY_STRING)) {
+ int count = mUsbMidiLegacyDeviceOpenCount.getOrDefault(deviceName, 0) + 1;
+ mUsbMidiLegacyDeviceOpenCount.put(deviceName, count);
+ }
+ }
+
+ // hold mUsbMidiLock before calling this
+ void removeUsbMidiDeviceLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
+ mUsbMidiUniversalDeviceInUse.remove(deviceName);
+ } else if ((tagName).equals(MIDI_LEGACY_STRING)) {
+ if (mUsbMidiLegacyDeviceOpenCount.containsKey(deviceName)) {
+ int count = mUsbMidiLegacyDeviceOpenCount.get(deviceName);
+ if (count > 1) {
+ mUsbMidiLegacyDeviceOpenCount.put(deviceName, count - 1);
+ } else {
+ mUsbMidiLegacyDeviceOpenCount.remove(deviceName);
+ }
+ }
+ }
+ }
+
+ // The USB property name is in the form "manufacturer product#Id MIDI 1.0".
+ // This is defined in UsbDirectMidiDevice.java.
+ // This function extracts out the "manufacturer product#Id " part.
+ // Two devices would have the same device name if they had the following property name:
+ // "manufacturer product#Id MIDI 1.0"
+ // "manufacturer product#Id MIDI 2.0"
+ // Note that MIDI_LEGACY_STRING and MIDI_UNIVERSAL_STRING are the same length.
+ String extractUsbDeviceName(String propertyName) {
+ return propertyName.substring(0, propertyName.length() - MIDI_LEGACY_STRING.length());
+ }
+
+ // The USB property name is in the form "manufacturer product#Id MIDI 1.0".
+ // This is defined in UsbDirectMidiDevice.java.
+ // This function extracts the "MIDI 1.0" part.
+ // Note that MIDI_LEGACY_STRING and MIDI_UNIVERSAL_STRING are the same length.
+ String extractUsbDeviceTag(String propertyName) {
+ return propertyName.substring(propertyName.length() - MIDI_LEGACY_STRING.length());
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
index 0411b94..9cf6c03 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
@@ -263,7 +263,7 @@
@Test
public void testUserActivityOnDeviceStateChange() {
createService();
- mService.systemReady();
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
final DisplayInfo info = new DisplayInfo();
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
index 64e8613..8b7cc74 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -18,8 +18,8 @@
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
-import static android.hardware.SensorPrivacyManager.ToggleTypes.HARDWARE;
-import static android.hardware.SensorPrivacyManager.ToggleTypes.SOFTWARE;
+import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
+import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -102,106 +102,106 @@
public void testMigration1() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE1);
- assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
- assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
@Test
public void testMigration2() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE2);
- assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
- assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertTrue(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
- assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA).isEnabled());
- assertNull(ps.getState(SOFTWARE, 11, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 11, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 11, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 11, CAMERA));
- assertTrue(ps.getState(SOFTWARE, 12, MICROPHONE).isEnabled());
- assertNull(ps.getState(SOFTWARE, 12, CAMERA));
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 12, MICROPHONE).isEnabled());
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 12, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 11, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 11, CAMERA));
- assertNull(ps.getState(HARDWARE, 12, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 12, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 11, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 11, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 12, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 12, CAMERA));
}
@Test
public void testMigration3() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE3);
- assertFalse(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
- assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
@Test
public void testMigration4() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE4);
- assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
- assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertFalse(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE).isEnabled());
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
@Test
public void testMigration5() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE5);
- assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
- assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA).isEnabled());
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
@Test
public void testMigration6() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE6);
- assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA));
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
private PersistedState migrateFromFile(String fileName) throws IOException {
@@ -233,32 +233,32 @@
public void testPersistence1Version2() throws IOException {
PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE7);
- assertEquals(1, ps.getState(SOFTWARE, 0, MICROPHONE).getState());
- assertEquals(123L, ps.getState(SOFTWARE, 0, MICROPHONE).getLastChange());
- assertEquals(2, ps.getState(SOFTWARE, 0, CAMERA).getState());
- assertEquals(123L, ps.getState(SOFTWARE, 0, CAMERA).getLastChange());
+ assertEquals(1, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).getState());
+ assertEquals(123L, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).getLastChange());
+ assertEquals(2, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).getState());
+ assertEquals(123L, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).getLastChange());
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
}
@Test
public void testPersistence2Version2() throws IOException {
PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE8);
- assertEquals(1, ps.getState(HARDWARE, 0, MICROPHONE).getState());
- assertEquals(1234L, ps.getState(HARDWARE, 0, MICROPHONE).getLastChange());
- assertEquals(2, ps.getState(HARDWARE, 0, CAMERA).getState());
- assertEquals(1234L, ps.getState(HARDWARE, 0, CAMERA).getLastChange());
+ assertEquals(1, ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE).getState());
+ assertEquals(1234L, ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE).getLastChange());
+ assertEquals(2, ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA).getState());
+ assertEquals(1234L, ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA).getLastChange());
- assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 0, CAMERA));
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
private PersistedState getPersistedStateV2(String version2FilePath) throws IOException {
@@ -296,13 +296,15 @@
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
- SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
- SensorState camState = sensorPrivacyStateController.getState(SOFTWARE, 0, CAMERA);
+ SensorState micState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
+ MICROPHONE);
+ SensorState camState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
+ CAMERA);
assertEquals(SensorPrivacyManager.StateTypes.DISABLED, micState.getState());
assertEquals(SensorPrivacyManager.StateTypes.DISABLED, camState.getState());
- verify(persistedState, times(1)).getState(SOFTWARE, 0, MICROPHONE);
- verify(persistedState, times(1)).getState(SOFTWARE, 0, CAMERA);
+ verify(persistedState, times(1)).getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE);
+ verify(persistedState, times(1)).getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA);
} finally {
mockitoSession.finishMocking();
}
@@ -319,14 +321,16 @@
PersistedState persistedState = mock(PersistedState.class);
SensorState sensorState = mock(SensorState.class);
doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
- doReturn(sensorState).when(persistedState).getState(SOFTWARE, 0, MICROPHONE);
+ doReturn(sensorState).when(persistedState).getState(TOGGLE_TYPE_SOFTWARE, 0,
+ MICROPHONE);
doReturn(SensorPrivacyManager.StateTypes.ENABLED).when(sensorState).getState();
doReturn(0L).when(sensorState).getLastChange();
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
- SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
+ SensorState micState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
+ MICROPHONE);
assertEquals(SensorPrivacyManager.StateTypes.ENABLED, micState.getState());
assertEquals(0L, micState.getLastChange());
@@ -349,13 +353,13 @@
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
- sensorPrivacyStateController.setState(SOFTWARE, 0, MICROPHONE, true,
+ sensorPrivacyStateController.setState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE, true,
mock(Handler.class), changed -> {});
ArgumentCaptor<SensorState> captor = ArgumentCaptor.forClass(SensorState.class);
- verify(persistedState, times(1)).setState(eq(SOFTWARE), eq(0), eq(MICROPHONE),
- captor.capture());
+ verify(persistedState, times(1)).setState(eq(TOGGLE_TYPE_SOFTWARE), eq(0),
+ eq(MICROPHONE), captor.capture());
assertEquals(SensorPrivacyManager.StateTypes.ENABLED, captor.getValue().getState());
} finally {
mockitoSession.finishMocking();
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 3890d4d..897b91e 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.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.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 @@
@Mock
Context mContext;
@Mock
- private ProximityCallbackInternal mMockProximityCallbackInternal;
+ private ProximityUpdateCallbackInternal mMockProximityUpdateCallbackInternal;
@Before
public void setUp() throws RemoteException {
@@ -119,7 +119,8 @@
public void testRegisterProximityUpdates_returnFalseWhenServiceDisabled() {
mSpyAttentionManager.mIsServiceEnabled = false;
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isFalse();
}
@@ -128,7 +129,8 @@
mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(false).when(mSpyAttentionManager).isServiceAvailable();
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isFalse();
}
@@ -139,7 +141,8 @@
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(false).when(mMockIPowerManager).isInteractive();
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isFalse();
}
@@ -149,9 +152,10 @@
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 @@
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 @@
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 @@
};
mSpyAttentionManager.onStopProximityUpdates(mismatchedCallback);
- verifyNoMoreInteractions(mMockProximityCallbackInternal);
+ verifyNoMoreInteractions(mMockProximityUpdateCallbackInternal);
}
@Test
@@ -205,8 +211,8 @@
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 @@
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 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 2ae2854..9aac81c 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 @@
}
@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/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index ed7aac7..1b92017 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -321,7 +321,7 @@
}
private void startSystem() {
- mService.systemReady();
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
// Grab the BatteryReceiver
ArgumentCaptor<BatteryReceiver> batCaptor = ArgumentCaptor.forClass(BatteryReceiver.class);
@@ -439,7 +439,7 @@
@Test
public void testGetDesiredScreenPolicy_WithVR() {
createService();
- mService.systemReady();
+ startSystem();
// Brighten up the screen
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
null, null);
@@ -627,8 +627,7 @@
public void testWasDeviceIdleFor_true() {
int interval = 1000;
createService();
- mService.systemReady();
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ startSystem();
mService.onUserActivity();
advanceTime(interval + 1 /* just a little more */);
assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue();
@@ -638,8 +637,7 @@
public void testWasDeviceIdleFor_false() {
int interval = 1000;
createService();
- mService.systemReady();
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ startSystem();
mService.onUserActivity();
assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
}
@@ -647,8 +645,7 @@
@Test
public void testForceSuspend_putsDeviceToSleep() {
createService();
- mService.systemReady();
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ startSystem();
// Verify that we start awake
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
@@ -693,8 +690,7 @@
//
// TEST STARTS HERE
//
- mService.systemReady();
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ startSystem();
// Verify that we start awake
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
@@ -792,7 +788,7 @@
createService();
assertTrue(isAcquired[0]);
- mService.systemReady();
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
assertTrue(isAcquired[0]);
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -1058,23 +1054,6 @@
assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_ASLEEP);
}
-
- @SuppressWarnings("GuardedBy")
- @Test
- public void testInattentiveSleep_goesToSleepFromDream() throws Exception {
- setAttentiveTimeout(20000);
- createService();
- startSystem();
- setPluggedIn(true);
- forceAwake();
- forceDream();
- when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-
- advanceTime(20500);
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
- }
-
@Test
public void testWakeLock_affectsProperDisplayGroup() {
final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
@@ -1248,7 +1227,7 @@
public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
createService();
- mService.systemReady();
+ startSystem();
mService.getBinderServiceInstance().wakeUp(mClock.now(),
PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
@@ -1461,7 +1440,7 @@
@Test
public void testSetPowerBoost_redirectsCallToNativeWrapper() {
createService();
- mService.systemReady();
+ startSystem();
mService.getBinderServiceInstance().setPowerBoost(Boost.INTERACTION, 1234);
@@ -1471,7 +1450,7 @@
@Test
public void testSetPowerMode_redirectsCallToNativeWrapper() {
createService();
- mService.systemReady();
+ startSystem();
// Enabled launch boost in BatterySaverController to allow setting launch mode.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(false);
@@ -1487,7 +1466,7 @@
@Test
public void testSetPowerMode_withLaunchBoostDisabledAndModeLaunch_ignoresCallToEnable() {
createService();
- mService.systemReady();
+ startSystem();
// Disables launch boost in BatterySaverController.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
@@ -1503,7 +1482,7 @@
@Test
public void testSetPowerModeChecked_returnsNativeCallResult() {
createService();
- mService.systemReady();
+ startSystem();
// Disables launch boost in BatterySaverController.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
@@ -1666,7 +1645,7 @@
@Test
public void testGetFullPowerSavePolicy_returnsStateMachineResult() {
createService();
- mService.systemReady();
+ startSystem();
BatterySaverPolicyConfig mockReturnConfig = new BatterySaverPolicyConfig.Builder().build();
when(mBatterySaverStateMachineMock.getFullBatterySaverPolicy())
.thenReturn(mockReturnConfig);
@@ -1681,7 +1660,7 @@
@Test
public void testSetFullPowerSavePolicy_callsStateMachine() {
createService();
- mService.systemReady();
+ startSystem();
BatterySaverPolicyConfig mockSetPolicyConfig =
new BatterySaverPolicyConfig.Builder().build();
when(mBatterySaverStateMachineMock.setFullBatterySaverPolicy(any())).thenReturn(true);
@@ -1695,7 +1674,7 @@
@Test
public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() {
createService();
- mService.systemReady();
+ startSystem();
WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
mService.setDeviceIdleModeInternal(true);
@@ -1706,7 +1685,7 @@
@Test
public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() {
createService();
- mService.systemReady();
+ startSystem();
WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
mService.setDeviceIdleModeInternal(true);
@@ -1718,7 +1697,7 @@
@Test
public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() {
createService();
- mService.systemReady();
+ startSystem();
WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
mService.setDeviceIdleModeInternal(true);
@@ -1731,7 +1710,7 @@
@Test
public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() {
createService();
- mService.systemReady();
+ startSystem();
WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP);
mService.setDeviceIdleModeInternal(true);
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 020d9f8..e1a4989 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 MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
- private VibrationThread.VibrationCallbacks mThreadCallbacks;
+ private VibrationThread.VibratorManagerHooks mManagerHooks;
@Mock
private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
@Mock
@@ -648,9 +648,9 @@
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 @@
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 @@
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 @@
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 @@
| 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 @@
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 @@
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 @@
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 @@
| 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 @@
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 @@
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 @@
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 @@
}
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 184ea52..fe1ea0d 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.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.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 @@
}
@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 043bc07..ffc10d7 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 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 @@
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 @@
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 @@
// 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 @@
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 c8e48a4..87abc53 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.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 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 @@
}
@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 @@
// 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 497ae1d..db22757 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 @@
.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 5f96267..ca8481a 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 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 2987f94..c61b88b 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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 2eece4c..c7a1b07 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 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 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 @@
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 @@
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 @@
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 @@
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 @@
@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 ee17f52..ba65104 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 @@
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 @@
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 @@
// 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 b815c38..7b38a95 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_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 @@
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 80192f7..459e3a5 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 @@
* Tests for the {@link WindowState} class.
*
* Build/Install/Run:
- * atest WmTests:WindowStateTests
+ * atest WmTests:WindowStateTests
*/
@SmallTest
@Presubmit
@@ -411,7 +411,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.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 5dbb4c5..bd1f9d5 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 @@
private boolean mOnTop = false;
private ActivityInfo.WindowLayout mWindowLayout;
private boolean mVisible = true;
+ private ActivityOptions mLaunchIntoPipOpts;
ActivityBuilder(ActivityTaskManagerService service) {
mService = service;
@@ -1076,6 +1077,11 @@
return this;
}
+ ActivityBuilder setLaunchIntoPipActivityOptions(ActivityOptions opts) {
+ mLaunchIntoPipOpts = opts;
+ return this;
+ }
+
ActivityRecord build() {
SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
try {
@@ -1132,7 +1138,9 @@
}
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 74cff10..4b5f330a 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 @@
@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/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index fd9b995..42a5af7 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -36,7 +36,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
/**
@@ -107,12 +106,6 @@
return false;
}
- /**
- * List of connected MIDI devices
- */
- private final HashMap<String, UsbMidiDevice>
- mMidiDevices = new HashMap<String, UsbMidiDevice>();
-
// UsbMidiDevice for USB peripheral mode (gadget) device
private UsbMidiDevice mPeripheralMidiDevice = null;
@@ -256,45 +249,6 @@
}
}
- // look for MIDI devices
- boolean hasMidi = parser.hasMIDIInterface();
- int midiNumInputs = parser.calculateNumLegacyMidiInputs();
- int midiNumOutputs = parser.calculateNumLegacyMidiOutputs();
- if (DEBUG) {
- Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
- Slog.d(TAG, "midiNumInputs: " + midiNumInputs + " midiNumOutputs:" + midiNumOutputs);
- }
- if (hasMidi && mHasMidiFeature) {
- int device = 0;
- Bundle properties = new Bundle();
- String manufacturer = usbDevice.getManufacturerName();
- String product = usbDevice.getProductName();
- String version = usbDevice.getVersion();
- String name;
- if (manufacturer == null || manufacturer.isEmpty()) {
- name = product;
- } else if (product == null || product.isEmpty()) {
- name = manufacturer;
- } else {
- name = manufacturer + " " + product;
- }
- properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
- properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
- properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
- properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
- properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
- usbDevice.getSerialNumber());
- properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
- properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
- properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
-
- UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
- cardRec.getCardNum(), 0 /*device*/, midiNumInputs, midiNumOutputs);
- if (usbMidiDevice != null) {
- mMidiDevices.put(deviceAddress, usbMidiDevice);
- }
- }
-
logDevices("deviceAdded()");
if (DEBUG) {
@@ -315,13 +269,6 @@
selectDefaultDevice(); // if there any external devices left, select one of them
}
- // MIDI
- UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
- if (usbMidiDevice != null) {
- Slog.i(TAG, "USB MIDI Device Removed: " + usbMidiDevice);
- IoUtils.closeQuietly(usbMidiDevice);
- }
-
logDevices("usbDeviceRemoved()");
}
@@ -377,12 +324,6 @@
usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
}
- for (String deviceAddr : mMidiDevices.keySet()) {
- // A UsbMidiDevice does not have a handle to the UsbDevice anymore
- mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
- UsbAlsaManagerProto.MIDI_DEVICES);
- }
-
dump.end(token);
}
diff --git a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
similarity index 65%
rename from services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
rename to services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
index 13d404c..0fa79df 100644
--- a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
@@ -20,6 +20,7 @@
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceServer;
@@ -43,16 +44,20 @@
import java.util.ArrayList;
/**
- * A MIDI device that opens device connections to MIDI 2.0 endpoints.
+ * Opens device connections to MIDI 1.0 or MIDI 2.0 endpoints.
+ * This endpoint will not use ALSA and opens a UsbDeviceConnection directly.
*/
-public final class UsbUniversalMidiDevice implements Closeable {
- private static final String TAG = "UsbUniversalMidiDevice";
+public final class UsbDirectMidiDevice implements Closeable {
+ private static final String TAG = "UsbDirectMidiDevice";
private static final boolean DEBUG = false;
private Context mContext;
private UsbDevice mUsbDevice;
private UsbDescriptorParser mParser;
private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces;
+ private final boolean mIsUniversalMidiDevice;
+ private final String mUniqueUsbDeviceIdentifier;
+ private final boolean mShouldCallSetInterface;
// USB outputs are MIDI inputs
private final InputReceiverProxy[] mMidiInputPortReceivers;
@@ -64,6 +69,11 @@
// event schedulers for each input port of the physical device
private MidiEventScheduler[] mEventSchedulers;
+ // Arbitrary number for timeout to not continue sending/receiving number from
+ // an inactive device. This number tries to balances the number of cycles and
+ // not being permanently stuck.
+ private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 100;
+
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
@@ -74,6 +84,8 @@
private final Object mLock = new Object();
private boolean mIsOpen;
+ private final UsbMidiPacketConverter mUsbMidiPacketConverter = new UsbMidiPacketConverter();
+
private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
@Override
@@ -140,13 +152,15 @@
}
/**
- * Creates an UsbUniversalMidiDevice based on the input parameters. Read/Write streams
+ * Creates an UsbDirectMidiDevice based on the input parameters. Read/Write streams
* will be created individually as some devices don't have the same number of
* inputs and outputs.
*/
- public static UsbUniversalMidiDevice create(Context context, UsbDevice usbDevice,
- UsbDescriptorParser parser) {
- UsbUniversalMidiDevice midiDevice = new UsbUniversalMidiDevice(usbDevice, parser);
+ public static UsbDirectMidiDevice create(Context context, UsbDevice usbDevice,
+ UsbDescriptorParser parser, boolean isUniversalMidiDevice,
+ String uniqueUsbDeviceIdentifier) {
+ UsbDirectMidiDevice midiDevice = new UsbDirectMidiDevice(usbDevice, parser,
+ isUniversalMidiDevice, uniqueUsbDeviceIdentifier);
if (!midiDevice.register(context)) {
IoUtils.closeQuietly(midiDevice);
Log.e(TAG, "createDeviceServer failed");
@@ -155,11 +169,22 @@
return midiDevice;
}
- private UsbUniversalMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser) {
+ private UsbDirectMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser,
+ boolean isUniversalMidiDevice, String uniqueUsbDeviceIdentifier) {
mUsbDevice = usbDevice;
mParser = parser;
+ mUniqueUsbDeviceIdentifier = uniqueUsbDeviceIdentifier;
+ mIsUniversalMidiDevice = isUniversalMidiDevice;
- mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
+ // Set interface should only be called when alternate interfaces exist.
+ // Otherwise, USB devices may not handle this gracefully.
+ mShouldCallSetInterface = (parser.calculateMidiInterfaceDescriptorsCount() > 1);
+
+ if (isUniversalMidiDevice) {
+ mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
+ } else {
+ mUsbInterfaces = parser.findLegacyMidiInterfaceDescriptors();
+ }
int numInputs = 0;
int numOutputs = 0;
@@ -182,8 +207,8 @@
mNumInputs = numInputs;
mNumOutputs = numOutputs;
- Log.d(TAG, "Created UsbUniversalMidiDevice with " + numInputs + " inputs and "
- + numOutputs + " outputs");
+ Log.d(TAG, "Created UsbDirectMidiDevice with " + numInputs + " inputs and "
+ + numOutputs + " outputs. isUniversalMidiDevice: " + isUniversalMidiDevice);
// Create MIDI port receivers based on the number of output ports. The
// output of USB is the input of MIDI.
@@ -218,23 +243,25 @@
if (doesInterfaceContainInput
&& doesInterfaceContainOutput) {
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
-
- // The ALSA does not handle switching to the MIDI 2.0 interface correctly
- // and stops exposing /dev/snd/midiC1D0 after calling connection.setInterface().
- // Thus, simply use the control interface (interface zero).
+ UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
+ if (!updateUsbInterface(usbInterface, connection)) {
+ continue;
+ }
int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
- 0,
+ interfaceDescriptor.getInterfaceNumber(),
interfaceDescriptor.getAlternateSetting());
+
connection.close();
return defaultMidiProtocol;
}
}
- Log.d(TAG, "Cannot find interface with both input and output endpoints");
+ Log.w(TAG, "Cannot find interface with both input and output endpoints");
return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
}
private boolean openLocked() {
+ Log.d(TAG, "openLocked()");
UsbManager manager = mContext.getSystemService(UsbManager.class);
mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size());
@@ -258,8 +285,10 @@
}
if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
- connection.setInterface(interfaceDescriptor.toAndroid(mParser));
- connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true);
+ UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
+ if (!updateUsbInterface(usbInterface, connection)) {
+ continue;
+ }
mUsbDeviceConnections.add(connection);
mInputUsbEndpoints.add(inputEndpoints);
mOutputUsbEndpoints.add(outputEndpoints);
@@ -283,14 +312,17 @@
for (int endpointIndex = 0;
endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
endpointIndex++) {
- final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
- final UsbEndpoint epF = mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
- final int portF = portNumber;
+ final UsbDeviceConnection connectionFinal =
+ mUsbDeviceConnections.get(connectionIndex);
+ final UsbEndpoint endpointFinal =
+ mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
+ final int portFinal = portNumber;
- new Thread("UsbUniversalMidiDevice input thread " + portF) {
+ new Thread("UsbDirectMidiDevice input thread " + portFinal) {
@Override
public void run() {
- byte[] inputBuffer = new byte[epF.getMaxPacketSize()];
+ byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
+ Log.d(TAG, "input buffer size: " + inputBuffer.length);
try {
while (true) {
// Record time of event immediately after waking.
@@ -298,20 +330,34 @@
synchronized (mLock) {
if (!mIsOpen) break;
- int nRead = connectionF.bulkTransfer(epF, inputBuffer,
- inputBuffer.length, 0);
-
- // For USB, each 32 bit word of a UMP is
- // sent with the least significant byte first.
- swapEndiannessPerWord(inputBuffer, inputBuffer.length);
+ int nRead = connectionFinal.bulkTransfer(endpointFinal,
+ inputBuffer, inputBuffer.length,
+ BULK_TRANSFER_TIMEOUT_MILLISECONDS);
if (nRead > 0) {
if (DEBUG) {
- logByteArray("Input ", inputBuffer, 0,
- nRead);
+ logByteArray("Input before conversion ", inputBuffer,
+ 0, nRead);
}
- outputReceivers[portF].send(inputBuffer, 0, nRead,
- timestamp);
+ byte[] convertedArray;
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ convertedArray = swapEndiannessPerWord(inputBuffer,
+ nRead);
+ } else {
+ convertedArray =
+ mUsbMidiPacketConverter.usbMidiToRawMidi(
+ inputBuffer, nRead);
+ }
+
+ if (DEBUG) {
+ logByteArray("Input after conversion ", convertedArray,
+ 0, convertedArray.length);
+ }
+
+ outputReceivers[portFinal].send(convertedArray, 0,
+ convertedArray.length, timestamp);
}
}
}
@@ -333,19 +379,20 @@
for (int endpointIndex = 0;
endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
endpointIndex++) {
- final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
- final UsbEndpoint epF =
+ final UsbDeviceConnection connectionFinal =
+ mUsbDeviceConnections.get(connectionIndex);
+ final UsbEndpoint endpointFinal =
mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
- final int portF = portNumber;
- final MidiEventScheduler eventSchedulerF = mEventSchedulers[portF];
+ final int portFinal = portNumber;
+ final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal];
- new Thread("UsbUniversalMidiDevice output thread " + portF) {
+ new Thread("UsbDirectMidiDevice output thread " + portFinal) {
@Override
public void run() {
while (true) {
MidiEvent event;
try {
- event = (MidiEvent) eventSchedulerF.waitNextEvent();
+ event = (MidiEvent) eventSchedulerFinal.waitNextEvent();
} catch (InterruptedException e) {
// try again
continue;
@@ -354,16 +401,32 @@
break;
}
- // For USB, each 32 bit word of a UMP is
- // sent with the least significant byte first.
- swapEndiannessPerWord(event.data, event.count);
-
if (DEBUG) {
- logByteArray("Output ", event.data, 0,
+ logByteArray("Output before conversion ", event.data, 0,
event.count);
}
- connectionF.bulkTransfer(epF, event.data, event.count, 0);
- eventSchedulerF.addEventToPool(event);
+
+ byte[] convertedArray;
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ convertedArray = swapEndiannessPerWord(event.data,
+ event.count);
+ } else {
+ convertedArray =
+ mUsbMidiPacketConverter.rawMidiToUsbMidi(
+ event.data, event.count);
+ }
+
+ if (DEBUG) {
+ logByteArray("Output after conversion ", convertedArray, 0,
+ convertedArray.length);
+ }
+
+ connectionFinal.bulkTransfer(endpointFinal, convertedArray,
+ convertedArray.length,
+ BULK_TRANSFER_TIMEOUT_MILLISECONDS);
+ eventSchedulerFinal.addEventToPool(event);
}
Log.d(TAG, "output thread exit");
}
@@ -381,11 +444,15 @@
mContext = context;
MidiManager midiManager = context.getSystemService(MidiManager.class);
if (midiManager == null) {
- Log.e(TAG, "No MidiManager in UsbUniversalMidiDevice.create()");
+ Log.e(TAG, "No MidiManager in UsbDirectMidiDevice.create()");
return false;
}
- mDefaultMidiProtocol = calculateDefaultMidiProtocol();
+ if (mIsUniversalMidiDevice) {
+ mDefaultMidiProtocol = calculateDefaultMidiProtocol();
+ } else {
+ mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UNKNOWN;
+ }
Bundle properties = new Bundle();
String manufacturer = mUsbDevice.getManufacturerName();
@@ -397,8 +464,15 @@
} else if (product == null || product.isEmpty()) {
name = manufacturer;
} else {
- name = manufacturer + " " + product + " MIDI 2.0";
+ name = manufacturer + " " + product;
}
+ name += "#" + mUniqueUsbDeviceIdentifier;
+ if (mIsUniversalMidiDevice) {
+ name += " MIDI 2.0";
+ } else {
+ name += " MIDI 1.0";
+ }
+ Log.e(TAG, name);
properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
@@ -430,10 +504,13 @@
}
private void closeLocked() {
+ Log.d(TAG, "closeLocked()");
for (int i = 0; i < mEventSchedulers.length; i++) {
mMidiInputPortReceivers[i].setReceiver(null);
mEventSchedulers[i].close();
}
+ mEventSchedulers = null;
+
for (UsbDeviceConnection connection : mUsbDeviceConnections) {
connection.close();
}
@@ -444,15 +521,19 @@
mIsOpen = false;
}
- private void swapEndiannessPerWord(byte[] array, int size) {
- for (int i = 0; i + 3 < size; i += 4) {
- byte tmp = array[i];
- array[i] = array[i + 3];
- array[i + 3] = tmp;
- tmp = array[i + 1];
- array[i + 1] = array[i + 2];
- array[i + 2] = tmp;
+ private byte[] swapEndiannessPerWord(byte[] inputArray, int size) {
+ int numberOfExcessBytes = size & 3;
+ if (numberOfExcessBytes != 0) {
+ Log.e(TAG, "size not multiple of 4: " + size);
}
+ byte[] outputArray = new byte[size - numberOfExcessBytes];
+ for (int i = 0; i + 3 < size; i += 4) {
+ outputArray[i] = inputArray[i + 3];
+ outputArray[i + 1] = inputArray[i + 2];
+ outputArray[i + 2] = inputArray[i + 1];
+ outputArray[i + 3] = inputArray[i];
+ }
+ return outputArray;
}
private static void logByteArray(String prefix, byte[] value, int offset, int count) {
@@ -465,4 +546,24 @@
}
Log.d(TAG, builder.toString());
}
+
+ private boolean updateUsbInterface(UsbInterface usbInterface,
+ UsbDeviceConnection connection) {
+ if (usbInterface == null) {
+ Log.e(TAG, "Usb Interface is null");
+ return false;
+ }
+ if (!connection.claimInterface(usbInterface, true)) {
+ Log.e(TAG, "Can't claim interface");
+ return false;
+ }
+ if (mShouldCallSetInterface) {
+ if (!connection.setInterface(usbInterface)) {
+ Log.w(TAG, "Can't set interface");
+ }
+ } else {
+ Log.w(TAG, "no alternate interface");
+ }
+ return true;
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 94cc826..a70b0332 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -50,9 +50,12 @@
import libcore.io.IoUtils;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
+import java.util.Random;
/**
* UsbHostManager manages USB state in host mode.
@@ -94,10 +97,12 @@
private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>();
/**
- * List of connected MIDI devices
+ * List of connected MIDI devices. Key on deviceAddress.
*/
- private final HashMap<String, UsbUniversalMidiDevice>
- mMidiDevices = new HashMap<String, UsbUniversalMidiDevice>();
+ private final HashMap<String, ArrayList<UsbDirectMidiDevice>>
+ mMidiDevices = new HashMap<String, ArrayList<UsbDirectMidiDevice>>();
+ private final HashSet<String> mMidiUniqueCodes = new HashSet<String>();
+ private final Random mRandom = new Random();
private final boolean mHasMidiFeature;
/*
@@ -423,15 +428,35 @@
mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
if (mHasMidiFeature) {
+ // Use a 3 digit code to associate MIDI devices with one another.
+ // Each MIDI device already has mId for uniqueness. mId is generated
+ // sequentially. For clarity, this code is not generated sequentially.
+ String uniqueUsbDeviceIdentifier = generateNewUsbDeviceIdentifier();
+
+ ArrayList<UsbDirectMidiDevice> midiDevices =
+ new ArrayList<UsbDirectMidiDevice>();
if (parser.containsUniversalMidiDeviceEndpoint()) {
- UsbUniversalMidiDevice midiDevice = UsbUniversalMidiDevice.create(mContext,
- newDevice, parser);
+ UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
+ newDevice, parser, true, uniqueUsbDeviceIdentifier);
if (midiDevice != null) {
- mMidiDevices.put(deviceAddress, midiDevice);
+ midiDevices.add(midiDevice);
} else {
Slog.e(TAG, "Universal Midi Device is null.");
}
}
+ if (parser.containsLegacyMidiDeviceEndpoint()) {
+ UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
+ newDevice, parser, false, uniqueUsbDeviceIdentifier);
+ if (midiDevice != null) {
+ midiDevices.add(midiDevice);
+ } else {
+ Slog.e(TAG, "Legacy Midi Device is null.");
+ }
+ }
+
+ if (!midiDevices.isEmpty()) {
+ mMidiDevices.put(deviceAddress, midiDevices);
+ }
}
// Tracking
@@ -469,10 +494,13 @@
mPermissionManager.usbDeviceRemoved(device);
// MIDI
- UsbUniversalMidiDevice midiDevice = mMidiDevices.remove(deviceAddress);
- if (midiDevice != null) {
- Slog.i(TAG, "USB Universal MIDI Device Removed: " + deviceAddress);
- IoUtils.closeQuietly(midiDevice);
+ ArrayList<UsbDirectMidiDevice> midiDevices =
+ mMidiDevices.remove(deviceAddress);
+ for (UsbDirectMidiDevice midiDevice : midiDevices) {
+ if (midiDevice != null) {
+ Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
+ IoUtils.closeQuietly(midiDevice);
+ }
}
getCurrentUserSettings().usbDeviceRemoved(device);
@@ -606,6 +634,19 @@
return true;
}
+ // Generate a 3 digit code.
+ private String generateNewUsbDeviceIdentifier() {
+ String code;
+ do {
+ code = "";
+ for (int i = 0; i < 3; i++) {
+ code += mRandom.nextInt(10);
+ }
+ } while (mMidiUniqueCodes.contains(code));
+ mMidiUniqueCodes.add(code);
+ return code;
+ }
+
private native void monitorUsbHostBus();
private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
}
diff --git a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
new file mode 100644
index 0000000..7c93c76
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
@@ -0,0 +1,237 @@
+/*
+ * 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.server.usb;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Converts between MIDI packets and USB MIDI 1.0 packets.
+ */
+public class UsbMidiPacketConverter {
+
+ // Refer to Table 4-1 in USB MIDI 1.0 spec.
+ private static final int[] PAYLOAD_SIZE = new int[]{
+ /* 0x00 */ -1, // Miscellaneous function codes. Reserved for future extensions.
+ /* 0x01 */ -1, // Cable events. Reserved for future expansion.
+ /* 0x02 */ 2, // Two-byte System Common messages like MTC, SongSelect, etc
+ /* 0x03 */ 3, // Three-byte System Common messages like SPP, etc.
+ /* 0x04 */ 3, // SysEx starts or continues
+ /* 0x05 */ 1, // Single-byte System Common Message or single-byte SysEx ends.
+ /* 0x06 */ 2, // SysEx ends with following two bytes.
+ /* 0x07 */ 3, // SysEx ends with following three bytes.
+ /* 0x08 */ 3, // Note-off
+ /* 0x09 */ 3, // Note-on
+ /* 0x0a */ 3, // Poly-KeyPress
+ /* 0x0b */ 3, // Control Change
+ /* 0x0c */ 2, // Program Change
+ /* 0x0d */ 2, // Channel Pressure
+ /* 0x0e */ 3, // PitchBend Change
+ /* 0x0f */ 1 // Single Byte
+ };
+
+ // Each System MIDI message is a certain size. These can be mapped to a
+ // Code Index number defined in Table 4-1 of USB MIDI 1.0.
+ private static final int[] CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE = new int[]{
+ /* 0x00 */ -1, // Start of Exclusive. Special case.
+ /* 0x01 */ 2, // MIDI Time Code. Two byte message
+ /* 0x02 */ 3, // Song Point Pointer. Three byte message
+ /* 0x03 */ 2, // Song Select. Two byte message
+ /* 0x04 */ -1, // Undefined MIDI System Common
+ /* 0x05 */ -1, // Undefined MIDI System Common
+ /* 0x06 */ 5, // Tune Request. One byte message
+ /* 0x07 */ -1, // End of Exclusive. Special case.
+ /* 0x08 */ 5, // Timing clock. One byte message
+ /* 0x09 */ -1, // Undefined MIDI System Real-time
+ /* 0x0a */ 5, // Start. One byte message
+ /* 0x0b */ 5, // Continue. One byte message
+ /* 0x0c */ 5, // Stop. One byte message
+ /* 0x0d */ -1, // Undefined MIDI System Real-time
+ /* 0x0e */ 5, // Active Sensing. One byte message
+ /* 0x0f */ 5 // System Reset. One byte message
+ };
+
+ // These code index numbers also come from Table 4-1 in USB MIDI 1.0 spec.
+ private static final byte CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES = 0x4;
+ private static final byte CODE_INDEX_NUMBER_SINGLE_BYTE = 0xF;
+ private static final byte CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE = (byte) 0x5;
+
+ // System messages are defined in MIDI.
+ private static final byte FIRST_SYSTEM_MESSAGE_VALUE = (byte) 0xF0;
+ private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
+ private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
+
+ private UsbMidiEncoder mUsbMidiEncoder = new UsbMidiEncoder();
+ private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
+
+ /**
+ * Converts a USB MIDI array into a raw MIDI array.
+ *
+ * @param usbMidiBytes the USB MIDI bytes to convert
+ * @param size the size of usbMidiBytes
+ * @return byte array of raw MIDI packets
+ */
+ public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
+ return mUsbMidiDecoder.decode(usbMidiBytes, size);
+ }
+
+ /**
+ * Converts a raw MIDI array into a USB MIDI array.
+ *
+ * @param midiBytes the raw MIDI bytes to convert
+ * @param size the size of usbMidiBytes
+ * @return byte array of USB MIDI packets
+ */
+ public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size) {
+ return mUsbMidiEncoder.encode(midiBytes, size);
+ }
+
+ private class UsbMidiDecoder {
+ // Decodes the data from USB MIDI to raw MIDI.
+ // Each valid 4 byte input maps to a 1-3 byte output.
+ // Reference the USB MIDI 1.0 spec for more info.
+ public byte[] decode(byte[] usbMidiBytes, int size) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ for (int i = 0; i + 3 < size; i += 4) {
+ int codeIndex = usbMidiBytes[i] & 0x0f;
+ int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
+ if (numPayloadBytes < 0) {
+ continue;
+ }
+ outputStream.write(usbMidiBytes, i + 1, numPayloadBytes);
+ }
+ return outputStream.toByteArray();
+ }
+ }
+
+ private class UsbMidiEncoder {
+ // In order to facilitate large scale transfers, SysEx can be sent in multiple packets.
+ // If encode() is called without an SysEx end, we must continue SysEx for the next packet.
+ // All other packets should be 3 bytes or less and must be not be broken between packets.
+ private byte[] mStoredSystemExclusiveBytes = new byte[3];
+ private int mNumStoredSystemExclusiveBytes = 0;
+ private boolean mHasSystemExclusiveStarted = false;
+
+ private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
+
+ // Encodes the data from raw MIDI to USB MIDI.
+ // Each valid 1-3 byte input maps to a 4 byte output.
+ // Reference the USB MIDI 1.0 spec for more info.
+ // MidiFramer is not needed here as this code handles partial packets.
+ // Long SysEx messages split between packets will encode and return a
+ // byte stream even if the SysEx end has not been sent.
+ // If there are less than 3 remaining data bytes in a SysEx message left,
+ // these bytes will be combined with the next set of packets.
+ public byte[] encode(byte[] midiBytes, int size) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ int curLocation = 0;
+ while (curLocation < size) {
+ if (midiBytes[curLocation] >= 0) { // Data byte
+ if (mHasSystemExclusiveStarted) {
+ mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
+ midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes++;
+ if (mNumStoredSystemExclusiveBytes == 3) {
+ outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES);
+ outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
+ mNumStoredSystemExclusiveBytes = 0;
+ }
+ } else {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ }
+ curLocation++;
+ continue;
+ } else if (midiBytes[curLocation] != SYSEX_END_EXCLUSIVE) {
+ // SysEx operation was interrupted. Pass the data directly down.
+ if (mHasSystemExclusiveStarted) {
+ int index = 0;
+ while (index < mNumStoredSystemExclusiveBytes) {
+ writeSingleByte(outputStream, mStoredSystemExclusiveBytes[index]);
+ index++;
+ }
+ mNumStoredSystemExclusiveBytes = 0;
+ mHasSystemExclusiveStarted = false;
+ }
+ }
+
+ if (midiBytes[curLocation] < FIRST_SYSTEM_MESSAGE_VALUE) { // Channel message
+ byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
+ int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
+ if (curLocation + channelMessageSize <= size) {
+ outputStream.write(codeIndexNumber);
+ outputStream.write(midiBytes, curLocation, channelMessageSize);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
+ curLocation += channelMessageSize;
+ } else { // The packet is missing data. Use single byte messages.
+ while (curLocation < size) {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ }
+ }
+ } else if (midiBytes[curLocation] == SYSEX_START_EXCLUSIVE) {
+ mHasSystemExclusiveStarted = true;
+ mStoredSystemExclusiveBytes[0] = midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes = 1;
+ curLocation++;
+ } else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
+ // 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
+ outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
+ + mNumStoredSystemExclusiveBytes);
+ mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
+ midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes++;
+ outputStream.write(mStoredSystemExclusiveBytes, 0,
+ mNumStoredSystemExclusiveBytes);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - mNumStoredSystemExclusiveBytes);
+ mHasSystemExclusiveStarted = false;
+ mNumStoredSystemExclusiveBytes = 0;
+ curLocation++;
+ } else {
+ int systemType = midiBytes[curLocation] & 0x0f;
+ int codeIndexNumber = CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE[systemType];
+ if (codeIndexNumber < 0) { // Unknown type. Use single byte messages.
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ } else {
+ int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
+ if (curLocation + systemMessageSize <= size) {
+ outputStream.write(codeIndexNumber);
+ outputStream.write(midiBytes, curLocation, systemMessageSize);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
+ curLocation += systemMessageSize;
+ } else { // The packet is missing data. Use single byte messages.
+ while (curLocation < size) {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ }
+ }
+ }
+ }
+ }
+ return outputStream.toByteArray();
+ }
+
+ private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
+ outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE);
+ outputStream.write(byteToWrite);
+ outputStream.write(0);
+ outputStream.write(0);
+ }
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 6e68a91..cd6ea68 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -198,14 +198,20 @@
if (mCurInterfaceDescriptor != null) {
switch (mCurInterfaceDescriptor.getUsbClass()) {
case UsbDescriptor.CLASSID_AUDIO:
- descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
+ descriptor =
+ UsbACInterface.allocDescriptor(this, stream, length, type);
+ if (descriptor instanceof UsbMSMidiHeader) {
+ mCurInterfaceDescriptor.setMidiHeaderInterfaceDescriptor(
+ descriptor);
+ }
break;
case UsbDescriptor.CLASSID_VIDEO:
if (DEBUG) {
Log.d(TAG, " UsbDescriptor.CLASSID_VIDEO");
}
- descriptor = UsbVCInterface.allocDescriptor(this, stream, length, type);
+ descriptor =
+ UsbVCInterface.allocDescriptor(this, stream, length, type);
break;
case UsbDescriptor.CLASSID_AUDIOVIDEO:
@@ -218,7 +224,6 @@
Log.w(TAG, " Unparsed Class-specific");
break;
}
- mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor);
}
break;
@@ -674,6 +679,23 @@
public boolean containsUniversalMidiDeviceEndpoint() {
ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
findUniversalMidiInterfaceDescriptors();
+ return doesInterfaceContainEndpoint(interfaceDescriptors);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean containsLegacyMidiDeviceEndpoint() {
+ ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
+ findLegacyMidiInterfaceDescriptors();
+ return doesInterfaceContainEndpoint(interfaceDescriptors);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean doesInterfaceContainEndpoint(
+ ArrayList<UsbInterfaceDescriptor> interfaceDescriptors) {
int outputCount = 0;
int inputCount = 0;
for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size();
@@ -698,10 +720,24 @@
* @hide
*/
public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() {
+ return findMidiInterfaceDescriptors(MS_MIDI_2_0);
+ }
+
+ /**
+ * @hide
+ */
+ public ArrayList<UsbInterfaceDescriptor> findLegacyMidiInterfaceDescriptors() {
+ return findMidiInterfaceDescriptors(MS_MIDI_1_0);
+ }
+
+ /**
+ * @hide
+ */
+ private ArrayList<UsbInterfaceDescriptor> findMidiInterfaceDescriptors(int type) {
int count = 0;
ArrayList<UsbDescriptor> descriptors =
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
- ArrayList<UsbInterfaceDescriptor> universalMidiInterfaces =
+ ArrayList<UsbInterfaceDescriptor> midiInterfaces =
new ArrayList<UsbInterfaceDescriptor>();
for (UsbDescriptor descriptor : descriptors) {
@@ -709,14 +745,14 @@
if (descriptor instanceof UsbInterfaceDescriptor) {
UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
- UsbDescriptor classSpecificDescriptor =
- interfaceDescriptor.getClassSpecificInterfaceDescriptor();
- if (classSpecificDescriptor != null) {
- if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
+ UsbDescriptor midiHeaderDescriptor =
+ interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
+ if (midiHeaderDescriptor != null) {
+ if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
UsbMSMidiHeader midiHeader =
- (UsbMSMidiHeader) classSpecificDescriptor;
- if (midiHeader.getMidiStreamingClass() == MS_MIDI_2_0) {
- universalMidiInterfaces.add(interfaceDescriptor);
+ (UsbMSMidiHeader) midiHeaderDescriptor;
+ if (midiHeader.getMidiStreamingClass() == type) {
+ midiInterfaces.add(interfaceDescriptor);
}
}
}
@@ -726,10 +762,13 @@
+ " t:0x" + Integer.toHexString(descriptor.getType()));
}
}
- return universalMidiInterfaces;
+ return midiInterfaces;
}
- private int calculateNumLegacyMidiPorts(boolean isOutput) {
+ /**
+ * @hide
+ */
+ public int calculateMidiInterfaceDescriptorsCount() {
int count = 0;
ArrayList<UsbDescriptor> descriptors =
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
@@ -738,22 +777,12 @@
if (descriptor instanceof UsbInterfaceDescriptor) {
UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
- UsbDescriptor classSpecificDescriptor =
- interfaceDescriptor.getClassSpecificInterfaceDescriptor();
- if (classSpecificDescriptor != null) {
- if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
+ UsbDescriptor midiHeaderDescriptor =
+ interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
+ if (midiHeaderDescriptor != null) {
+ if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
UsbMSMidiHeader midiHeader =
- (UsbMSMidiHeader) classSpecificDescriptor;
- if (midiHeader.getMidiStreamingClass() != MS_MIDI_1_0) {
- continue;
- }
- }
- }
- for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
- UsbEndpointDescriptor endpoint =
- interfaceDescriptor.getEndpointDescriptor(i);
- // 0 is output, 1 << 7 is input.
- if ((endpoint.getDirection() == 0) == isOutput) {
+ (UsbMSMidiHeader) midiHeaderDescriptor;
count++;
}
}
@@ -769,20 +798,6 @@
/**
* @hide
*/
- public int calculateNumLegacyMidiInputs() {
- return calculateNumLegacyMidiPorts(false /*isOutput*/);
- }
-
- /**
- * @hide
- */
- public int calculateNumLegacyMidiOutputs() {
- return calculateNumLegacyMidiPorts(true /*isOutput*/);
- }
-
- /**
- * @hide
- */
public float getInputHeadsetProbability() {
if (hasMIDIInterface()) {
return 0.0f;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index ca4613b..9ddcb10 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -42,7 +42,8 @@
private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
new ArrayList<UsbEndpointDescriptor>();
- private UsbDescriptor mClassSpecificInterfaceDescriptor;
+ // Used for MIDI only.
+ private UsbDescriptor mMidiHeaderInterfaceDescriptor;
UsbInterfaceDescriptor(int length, byte type) {
super(length, type);
@@ -107,12 +108,12 @@
mEndpointDescriptors.add(endpoint);
}
- public void setClassSpecificInterfaceDescriptor(UsbDescriptor descriptor) {
- mClassSpecificInterfaceDescriptor = descriptor;
+ public void setMidiHeaderInterfaceDescriptor(UsbDescriptor descriptor) {
+ mMidiHeaderInterfaceDescriptor = descriptor;
}
- public UsbDescriptor getClassSpecificInterfaceDescriptor() {
- return mClassSpecificInterfaceDescriptor;
+ public UsbDescriptor getMidiHeaderInterfaceDescriptor() {
+ return mMidiHeaderInterfaceDescriptor;
}
/**
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 98d13e8..566c725 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 3b3a303..f834820 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 849e3ae..0d2869c 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 18d017d..00fee82 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.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 @@
@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 24716ff..1c06495 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 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 @@
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 0ba5369..b0e53e9 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 f8afcd9..8a08d07 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 d2f6c7f1..53560cc 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 0ce73f4..3958dd2 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 07fe274..cffed81 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 f603f6e..2b944c6 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 @@
* 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 @@
}
}
+ /**
+ * 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 d1bdeed..0becadf 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 @@
}
}
}
-}
\ 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 be55751..d397d59 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/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index c59a41e..9a9e42b 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -37,8 +37,6 @@
"vts",
],
data: [
- ":NotoSerif-Regular.ttf",
- ":NotoSerif-Bold.ttf",
":UpdatableSystemFontTestCertDer",
":UpdatableSystemFontTest_NotoColorEmoji.ttf",
":UpdatableSystemFontTest_NotoColorEmoji.sig",
@@ -48,7 +46,9 @@
":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 6effa7b..9e2a4b6 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 87fda0d..cbe13d9 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -84,7 +84,7 @@
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 @@
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 64b698d..0bdb3a8 100644
--- a/tests/UpdatableSystemFontTest/testdata/Android.bp
+++ b/tests/UpdatableSystemFontTest/testdata/Android.bp
@@ -21,11 +21,19 @@
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 @@
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 {
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
similarity index 100%
rename from tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttf
rename to tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttf
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttx
similarity index 100%
rename from tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttx
rename to tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttx
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf
new file mode 100644
index 0000000..66c1bd2
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx
new file mode 100644
index 0000000..8c4215e
--- /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
new file mode 100644
index 0000000..707ae28
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx
new file mode 100644
index 0000000..754eae3
--- /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 e547400..4cfa93b 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 @@
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());