Merge "Remove apex/media - migrated to packages/modules/Media" am: 8aa8ce4f91
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1987167 Change-Id: I13e1c0f75834c11957aaf5ed7e94da85ab325329
This commit is contained in:
@@ -176,7 +176,6 @@ java_defaults {
|
|||||||
"sax/java",
|
"sax/java",
|
||||||
"telecomm/java",
|
"telecomm/java",
|
||||||
|
|
||||||
"apex/media/aidl/stable",
|
|
||||||
// TODO(b/147699819): remove this
|
// TODO(b/147699819): remove this
|
||||||
"telephony/java",
|
"telephony/java",
|
||||||
],
|
],
|
||||||
@@ -275,6 +274,7 @@ java_defaults {
|
|||||||
// TODO: remove when moved to the below package
|
// TODO: remove when moved to the below package
|
||||||
"frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
|
"frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
|
||||||
"packages/modules/Connectivity/framework/aidl-export",
|
"packages/modules/Connectivity/framework/aidl-export",
|
||||||
|
"packages/modules/Media/apex/aidl/stable",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
dxflags: [
|
dxflags: [
|
||||||
@@ -552,11 +552,9 @@ stubs_defaults {
|
|||||||
stubs_defaults {
|
stubs_defaults {
|
||||||
name: "module-classpath-stubs-defaults",
|
name: "module-classpath-stubs-defaults",
|
||||||
aidl: {
|
aidl: {
|
||||||
local_include_dirs: [
|
|
||||||
"apex/media/aidl/stable",
|
|
||||||
],
|
|
||||||
include_dirs: [
|
include_dirs: [
|
||||||
"packages/modules/Connectivity/framework/aidl-export",
|
"packages/modules/Connectivity/framework/aidl-export",
|
||||||
|
"packages/modules/Media/apex/aidl/stable",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
libs: [
|
libs: [
|
||||||
|
|||||||
@@ -140,11 +140,9 @@ droidstubs {
|
|||||||
"api-versions-jars-dir",
|
"api-versions-jars-dir",
|
||||||
],
|
],
|
||||||
aidl: {
|
aidl: {
|
||||||
local_include_dirs: [
|
|
||||||
"apex/media/aidl/stable",
|
|
||||||
],
|
|
||||||
include_dirs: [
|
include_dirs: [
|
||||||
"packages/modules/Connectivity/framework/aidl-export",
|
"packages/modules/Connectivity/framework/aidl-export",
|
||||||
|
"packages/modules/Media/apex/aidl/stable",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,31 +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;
|
|
||||||
|
|
||||||
/** {@hide} */
|
|
||||||
interface IMediaCommunicationService {
|
|
||||||
void notifySession2Created(in Session2Token sessionToken);
|
|
||||||
boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid);
|
|
||||||
MediaParceledListSlice getSession2Tokens(int userId);
|
|
||||||
|
|
||||||
void registerCallback(IMediaCommunicationServiceCallback callback, String packageName);
|
|
||||||
void unregisterCallback(IMediaCommunicationServiceCallback callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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>;
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,161 +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",
|
|
||||||
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",
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"presubmit": [
|
|
||||||
{
|
|
||||||
"name": "CtsMediaParserTestCases"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CtsMediaParserHostTestCases"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
package android.media {
|
|
||||||
|
|
||||||
public class MediaCommunicationManager {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
rule com.android.modules.** android.media.internal.@1
|
|
||||||
rule com.google.android.exoplayer2.** android.media.internal.exo.@1
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 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);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,288 +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 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)
|
|
||||||
@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;
|
|
||||||
private final IMediaCommunicationService mService;
|
|
||||||
|
|
||||||
private final Object mLock = new Object();
|
|
||||||
private final CopyOnWriteArrayList<SessionCallbackRecord> mTokenCallbackRecords =
|
|
||||||
new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
private MediaCommunicationServiceCallbackStub mCallbackStub;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public MediaCommunicationManager(@NonNull Context context) {
|
|
||||||
if (!SdkLevel.isAtLeastS()) {
|
|
||||||
throw new UnsupportedOperationException("Android version must be S or greater.");
|
|
||||||
}
|
|
||||||
mContext = context;
|
|
||||||
mService = IMediaCommunicationService.Stub.asInterface(
|
|
||||||
MediaFrameworkInitializer.getMediaServiceManager()
|
|
||||||
.getMediaCommunicationServiceRegisterer()
|
|
||||||
.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
mService.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 mService.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 {
|
|
||||||
mService.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 {
|
|
||||||
mService.unregisterCallback(mCallbackStub);
|
|
||||||
} catch (RemoteException ex) {
|
|
||||||
Log.e(TAG, "Failed to unregister callback.", ex);
|
|
||||||
}
|
|
||||||
mCallbackStub = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Session2Token> getSession2Tokens(int userId) {
|
|
||||||
try {
|
|
||||||
MediaParceledListSlice slice = mService.getSession2Tokens(userId);
|
|
||||||
return slice == null ? Collections.emptyList() : slice.getList();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.e(TAG, "Failed to get session tokens", e);
|
|
||||||
}
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +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 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)
|
|
||||||
@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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +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.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
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
|
|
||||||
@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];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,931 +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 whehther 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);
|
|
||||||
|
|
||||||
// Calling Bundle#size() will trigger Bundle#unparcel().
|
|
||||||
out.size();
|
|
||||||
} 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) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 when notification UI needs update. Override this method to show or cancel your own
|
|
||||||
* notification UI.
|
|
||||||
* <p>
|
|
||||||
* This would be called on {@link MediaSession2}'s callback executor when playback state is
|
|
||||||
* changed.
|
|
||||||
* <p>
|
|
||||||
* With the notification returned here, the service becomes foreground service when the playback
|
|
||||||
* is started. Apps must request the permission
|
|
||||||
* {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes
|
|
||||||
* background service after the playback is stopped.
|
|
||||||
*
|
|
||||||
* @param session a session that needs notification update.
|
|
||||||
* @return a {@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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,218 +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/SessionCommand2.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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") /* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -1,312 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`"
|
|
||||||
errorLine1=" new ApplicationMediaCapabilities.Builder();"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
|
|
||||||
line="208"
|
|
||||||
column="29"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`"
|
|
||||||
errorLine1=" ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder();"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
|
|
||||||
line="314"
|
|
||||||
column="56"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Call requires API level R (current min is 29): `android.os.RemoteException#rethrowFromSystemServer`"
|
|
||||||
errorLine1=" e.rethrowFromSystemServer();"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaCommunicationManager.java"
|
|
||||||
line="110"
|
|
||||||
column="15"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Call requires API level R (current min is 29): `android.os.Parcel#writeParcelableCreator`"
|
|
||||||
errorLine1=" dest.writeParcelableCreator((Parcelable) parcelable);"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java"
|
|
||||||
line="77"
|
|
||||||
column="14"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Call requires API level R (current min is 29): `android.os.Parcel#readParcelableCreator`"
|
|
||||||
errorLine1=" return from.readParcelableCreator(loader);"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java"
|
|
||||||
line="82"
|
|
||||||
column="21"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#mediaFormat`"
|
|
||||||
errorLine1=" this.mediaFormat = mediaFormat;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="273"
|
|
||||||
column="13"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#drmInitData`"
|
|
||||||
errorLine1=" this.drmInitData = drmInitData;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="274"
|
|
||||||
column="13"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
|
|
||||||
errorLine1=" this.timeMicros = timeMicros;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="295"
|
|
||||||
column="13"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
|
|
||||||
errorLine1=" this.position = position;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="296"
|
|
||||||
column="13"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
|
|
||||||
errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";"
|
|
||||||
errorLine2=" ~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="302"
|
|
||||||
column="66"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
|
|
||||||
errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";"
|
|
||||||
errorLine2=" ~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="302"
|
|
||||||
column="37"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Class requires API level R (current min is 29): `android.media.MediaParser.SeekPoint`"
|
|
||||||
errorLine1=" SeekPoint other = (SeekPoint) obj;"
|
|
||||||
errorLine2=" ~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="313"
|
|
||||||
column="32"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
|
|
||||||
errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
|
|
||||||
errorLine2=" ~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="314"
|
|
||||||
column="54"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
|
|
||||||
errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="314"
|
|
||||||
column="66"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
|
|
||||||
errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
|
|
||||||
errorLine2=" ~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="314"
|
|
||||||
column="20"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
|
|
||||||
errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="314"
|
|
||||||
column="34"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
|
|
||||||
errorLine1=" int result = (int) timeMicros;"
|
|
||||||
errorLine2=" ~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="319"
|
|
||||||
column="32"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
|
|
||||||
errorLine1=" result = 31 * result + (int) position;"
|
|
||||||
errorLine2=" ~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="320"
|
|
||||||
column="42"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
|
|
||||||
errorLine1=" public interface SeekableInputReader extends InputReader {"
|
|
||||||
errorLine2=" ~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="352"
|
|
||||||
column="50"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level 31 (current min is 29): `android.media.metrics.LogSessionId#LOG_SESSION_ID_NONE`"
|
|
||||||
errorLine1=" @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1071"
|
|
||||||
column="51"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Cast from `SeekableInputReader` to `InputReader` requires API level 30 (current min is 29)"
|
|
||||||
errorLine1=" mExoDataReader.mInputReader = seekableInputReader;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1201"
|
|
||||||
column="39"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
|
|
||||||
errorLine1=" mPendingSeekPosition = seekPoint.position;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1287"
|
|
||||||
column="36"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
|
|
||||||
errorLine1=" mPendingSeekTimeMicros = seekPoint.timeMicros;"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1288"
|
|
||||||
column="38"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
|
|
||||||
errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1290"
|
|
||||||
column="29"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
|
|
||||||
errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1290"
|
|
||||||
column="49"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Field requires API level R (current min is 29): `android.media.DrmInitData.SchemeInitData#uuid`"
|
|
||||||
errorLine1=" if (schemeInitData.uuid.equals(schemeUuid)) {"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1579"
|
|
||||||
column="21"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
|
|
||||||
errorLine1=" private static final class DataReaderAdapter implements InputReader {"
|
|
||||||
errorLine2=" ~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1872"
|
|
||||||
column="61"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="NewApi"
|
|
||||||
message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
|
|
||||||
errorLine1=" private static final class ParsableByteArrayAdapter implements InputReader {"
|
|
||||||
errorLine2=" ~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
|
|
||||||
line="1905"
|
|
||||||
column="68"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
</issues>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# Keep all symbols in android.media.
|
|
||||||
-keep class android.media.* {*;}
|
|
||||||
@@ -1,45 +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: [
|
|
||||||
"updatable-media",
|
|
||||||
],
|
|
||||||
sdk_version: "system_server_current",
|
|
||||||
min_sdk_version: "29", // TODO: We may need to bump this at some point.
|
|
||||||
apex_available: [
|
|
||||||
"com.android.media",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Signature format: 2.0
|
|
||||||
@@ -1,620 +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.os.Binder;
|
|
||||||
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 com.android.internal.annotations.GuardedBy;
|
|
||||||
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;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A system service that manages {@link android.media.MediaSession2} creations
|
|
||||||
* and their ongoing media playback state.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public class MediaCommunicationService extends SystemService {
|
|
||||||
private static final String TAG = "MediaCommunicationService";
|
|
||||||
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 List<CallbackRecord> mCallbackRecords = new ArrayList<>();
|
|
||||||
final NotificationManager mNotificationManager;
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Session2Token> getSession2TokensLocked(int userId) {
|
|
||||||
List<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) {
|
|
||||||
MediaParceledListSlice<Session2Token> allSession2Tokens;
|
|
||||||
MediaParceledListSlice<Session2Token> userSession2Tokens;
|
|
||||||
|
|
||||||
synchronized (mLock) {
|
|
||||||
allSession2Tokens =
|
|
||||||
new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier()));
|
|
||||||
userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId));
|
|
||||||
}
|
|
||||||
allSession2Tokens.setInlineCountLimit(1);
|
|
||||||
userSession2Tokens.setInlineCountLimit(1);
|
|
||||||
|
|
||||||
synchronized (mLock) {
|
|
||||||
for (CallbackRecord record : mCallbackRecords) {
|
|
||||||
if (record.mUserId == ALL.getIdentifier()) {
|
|
||||||
try {
|
|
||||||
record.mCallback.onSession2Changed(allSession2Tokens);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.w(TAG, "Failed to notify session2 tokens changed " + record);
|
|
||||||
}
|
|
||||||
} else if (record.mUserId == userId) {
|
|
||||||
try {
|
|
||||||
record.mCallback.onSession2Changed(userSession2Tokens);
|
|
||||||
} 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
List<Session2Token> result;
|
|
||||||
synchronized (mLock) {
|
|
||||||
result = getSession2TokensLocked(resolvedUserId);
|
|
||||||
}
|
|
||||||
MediaParceledListSlice parceledListSlice = new MediaParceledListSlice<>(result);
|
|
||||||
parceledListSlice.setInlineCountLimit(1);
|
|
||||||
return parceledListSlice;
|
|
||||||
} 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 Object mUserLock = new Object();
|
|
||||||
@GuardedBy("mUserLock")
|
|
||||||
private final List<Session2Record> mSessionRecords = new ArrayList<>();
|
|
||||||
|
|
||||||
FullUserRecord(int fullUserId) {
|
|
||||||
mFullUserId = fullUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSession(Session2Record record) {
|
|
||||||
synchronized (mUserLock) {
|
|
||||||
mSessionRecords.add(record);
|
|
||||||
}
|
|
||||||
mHandler.post(() -> dispatchSession2Created(record.mSessionToken));
|
|
||||||
mHandler.post(() -> dispatchSession2Changed(mFullUserId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeSession(Session2Record record) {
|
|
||||||
synchronized (mUserLock) {
|
|
||||||
mSessionRecords.remove(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() {
|
|
||||||
synchronized (mUserLock) {
|
|
||||||
return mSessionRecords.stream()
|
|
||||||
.map(Session2Record::getSessionToken)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Session2Token> getSession2Tokens(int userId) {
|
|
||||||
synchronized (mUserLock) {
|
|
||||||
return mSessionRecords.stream()
|
|
||||||
.filter(record -> record.getUserId() == userId)
|
|
||||||
.map(Session2Record::getSessionToken)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroyAllSessions() {
|
|
||||||
synchronized (mUserLock) {
|
|
||||||
for (Session2Record session : mSessionRecords) {
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
mSessionRecords.clear();
|
|
||||||
}
|
|
||||||
mHandler.post(() -> dispatchSession2Changed(mFullUserId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroySessionsForUser(int userId) {
|
|
||||||
boolean changed = false;
|
|
||||||
synchronized (mUserLock) {
|
|
||||||
for (int i = mSessionRecords.size() - 1; i >= 0; i--) {
|
|
||||||
Session2Record session = mSessionRecords.get(i);
|
|
||||||
if (session.getUserId() == userId) {
|
|
||||||
mSessionRecords.remove(i);
|
|
||||||
session.close();
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changed) {
|
|
||||||
mHandler.post(() -> dispatchSession2Changed(mFullUserId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user