Remove apex/media - migrated to packages/modules/Media
Fix references for local_include_dirs for aidl support.
BUG: 171106157
Test: Local build and TH
Change-Id: Ice516a0ab0819c4a076c394c05be1643461d4309
diff --git a/Android.bp b/Android.bp
index ee5db70..05175d9 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/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 {