[Nearby Media Devices] Add a SystemApi to get information about nearby

media devices from external clients.

Bug: 216313420
Test: CTS included in same topic
Test: atest NearbyMediaDevicesManagerTest
Change-Id: Ic5f26d3048a3475dbd232c913bb00bee46339b0a
This commit is contained in:
Caitlin Cassidy
2022-02-10 22:14:57 +00:00
parent 7a9511ca40
commit ba6b756d0e
24 changed files with 793 additions and 389 deletions

View File

@@ -916,8 +916,10 @@ package android.app {
public class StatusBarManager {
method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarModeOverride();
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerNearbyMediaDevicesProvider(@NonNull android.media.NearbyMediaDevicesProvider);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setNavBarModeOverride(int);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void unregisterNearbyMediaDevicesProvider(@NonNull android.media.NearbyMediaDevicesProvider);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable android.graphics.drawable.Icon, @Nullable CharSequence);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferSenderDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
field public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; // 0x0
@@ -6251,6 +6253,26 @@ package android.media {
field public static final int SYNC_EVENT_SHARE_AUDIO_HISTORY = 100; // 0x64
}
public final class NearbyDevice implements android.os.Parcelable {
ctor public NearbyDevice(@NonNull String, int);
method public int describeContents();
method @NonNull public String getMediaRoute2Id();
method public int getRangeZone();
method @NonNull public static String rangeZoneToString(int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.NearbyDevice> CREATOR;
field public static final int RANGE_CLOSE = 3; // 0x3
field public static final int RANGE_FAR = 1; // 0x1
field public static final int RANGE_LONG = 2; // 0x2
field public static final int RANGE_UNKNOWN = 0; // 0x0
field public static final int RANGE_WITHIN_REACH = 4; // 0x4
}
public interface NearbyMediaDevicesProvider {
method public void registerNearbyDevicesCallback(@NonNull java.util.function.Consumer<java.util.List<android.media.NearbyDevice>>);
method public void unregisterNearbyDevicesCallback(@NonNull java.util.function.Consumer<java.util.List<android.media.NearbyDevice>>);
}
public class PlayerProxy {
method public void pause();
method public void setPan(float);

View File

@@ -28,7 +28,11 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.media.INearbyMediaDevicesProvider;
import android.media.INearbyMediaDevicesUpdateCallback;
import android.media.MediaRoute2Info;
import android.media.NearbyDevice;
import android.media.NearbyMediaDevicesProvider;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -46,6 +50,9 @@ import com.android.internal.statusbar.NotificationVisibility;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -503,6 +510,16 @@ public class StatusBarManager {
@Retention(RetentionPolicy.SOURCE)
public @interface MediaTransferReceiverState {}
/**
* A map from a provider registered in
* {@link #registerNearbyMediaDevicesProvider(NearbyMediaDevicesProvider)} to the wrapper
* around the provider that was created internally. We need the wrapper to make the provider
* binder-compatible, and we need to store a reference to the wrapper so that when the provider
* is un-registered, we un-register the saved wrapper instance.
*/
private final Map<NearbyMediaDevicesProvider, NearbyMediaDevicesProviderWrapper>
nearbyMediaDevicesProviderMap = new HashMap<>();
@UnsupportedAppUsage
private Context mContext;
private IStatusBarService mService;
@@ -1033,6 +1050,67 @@ public class StatusBarManager {
}
}
/**
* Registers a provider that notifies callbacks about the status of nearby devices that are able
* to play media.
* <p>
* If multiple providers are registered, all the providers will be used for nearby device
* information.
* <p>
* @param provider the nearby device information provider to register
* <p>
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
public void registerNearbyMediaDevicesProvider(
@NonNull NearbyMediaDevicesProvider provider
) {
Objects.requireNonNull(provider);
if (nearbyMediaDevicesProviderMap.containsKey(provider)) {
return;
}
try {
final IStatusBarService svc = getService();
NearbyMediaDevicesProviderWrapper providerWrapper =
new NearbyMediaDevicesProviderWrapper(provider);
nearbyMediaDevicesProviderMap.put(provider, providerWrapper);
svc.registerNearbyMediaDevicesProvider(providerWrapper);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Unregisters a provider that gives information about nearby devices that are able to play
* media.
* <p>
* See {@link registerNearbyMediaDevicesProvider}.
* <p>
* @param provider the nearby device information provider to unregister
* <p>
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
public void unregisterNearbyMediaDevicesProvider(
@NonNull NearbyMediaDevicesProvider provider
) {
Objects.requireNonNull(provider);
if (!nearbyMediaDevicesProviderMap.containsKey(provider)) {
return;
}
try {
final IStatusBarService svc = getService();
NearbyMediaDevicesProviderWrapper providerWrapper =
nearbyMediaDevicesProviderMap.get(provider);
nearbyMediaDevicesProviderMap.remove(provider);
svc.unregisterNearbyMediaDevicesProvider(providerWrapper);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public static String windowStateToString(int state) {
if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";
@@ -1340,4 +1418,48 @@ public class StatusBarManager {
}
}
}
/**
* @hide
*/
static final class NearbyMediaDevicesProviderWrapper extends INearbyMediaDevicesProvider.Stub {
@NonNull
private final NearbyMediaDevicesProvider mProvider;
// Because we're wrapping a {@link NearbyMediaDevicesProvider} in a binder-compatible
// interface, we also need to wrap the callbacks that the provider receives. We use
// this map to keep track of the original callback and the wrapper callback so that
// unregistering the callback works correctly.
@NonNull
private final Map<INearbyMediaDevicesUpdateCallback, Consumer<List<NearbyDevice>>>
mRegisteredCallbacks = new HashMap<>();
NearbyMediaDevicesProviderWrapper(@NonNull NearbyMediaDevicesProvider provider) {
mProvider = provider;
}
@Override
public void registerNearbyDevicesCallback(
@NonNull INearbyMediaDevicesUpdateCallback callback) {
Consumer<List<NearbyDevice>> callbackAsConsumer = nearbyDevices -> {
try {
callback.onDevicesUpdated(nearbyDevices);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
};
mRegisteredCallbacks.put(callback, callbackAsConsumer);
mProvider.registerNearbyDevicesCallback(callbackAsConsumer);
}
@Override
public void unregisterNearbyDevicesCallback(
@NonNull INearbyMediaDevicesUpdateCallback callback) {
if (!mRegisteredCallbacks.containsKey(callback)) {
return;
}
mProvider.unregisterNearbyDevicesCallback(mRegisteredCallbacks.get(callback));
mRegisteredCallbacks.remove(callback);
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.StatusBarManager;
import java.util.List;
import java.util.function.Consumer;
/**
* An interface that provides information about nearby devices that are able to play media.
* <p>
* External clients can implement this interface and pass it to the system via
* {@link StatusBarManager#registerNearbyMediaDevicesProvider} to inform the system of nearby media
* devices.
* <p>
* @hide
*/
@SystemApi
public interface NearbyMediaDevicesProvider {
/**
* Registers a callback that should be notified each time nearby media device(s) change.
* <p>
* When a callback is newly registered, it should be immediately notified of the current nearby
* media devices. Afterwards, the list of devices passed to the callback should always contain
* the full set of nearby media devices any time you get an update. If a device is no longer
* valid (went offline, e.g.) then it should be omitted from the list in the next update.
* <p>
* @param callback the callback that will consume updates to the nearby media devices.
*/
void registerNearbyDevicesCallback(@NonNull Consumer<List<NearbyDevice>> callback);
/**
* Unregisters a callback. @see #registerNearbyDevicesCallback.
* <p>
* @param callback the callback to unregister.
*/
void unregisterNearbyDevicesCallback(@NonNull Consumer<List<NearbyDevice>> callback);
}

View File

@@ -24,6 +24,7 @@ import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -311,4 +312,10 @@ oneway interface IStatusBar
in MediaRoute2Info routeInfo,
in Icon appIcon,
in CharSequence appName);
/** Registers a nearby media devices provider. */
void registerNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
/** Unregisters a nearby media devices provider. */
void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
}

View File

@@ -24,6 +24,7 @@ import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.net.Uri;
import android.os.Bundle;
@@ -211,4 +212,10 @@ interface IStatusBarService
in MediaRoute2Info routeInfo,
in Icon appIcon,
in CharSequence appName);
/** Registers a nearby media devices provider. */
void registerNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
/** Unregisters a nearby media devices provider. */
void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
}

View File

@@ -14,25 +14,20 @@
* limitations under the License.
*/
package com.android.systemui.shared.media;
package android.media;
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback;
import com.android.systemui.shared.media.NearbyDevice;
import android.media.INearbyMediaDevicesUpdateCallback;
import android.media.NearbyDevice;
/**
* An interface that provides information about nearby devices that are able to play media.
* A binder-compatible version of {@link android.media.NearbyMediaDevicesProvider}. See that class
* for more information.
*
* External clients will implement this interface and System UI will invoke it if it's passed to
* SystemUI via {@link INearbyMediaDevicesService.registerProvider}.
* @hide
*/
interface INearbyMediaDevicesProvider {
/**
* Returns a list of nearby devices that are able to play media.
*/
List<NearbyDevice> getCurrentNearbyDevices() = 1;
/**
* Registers a callback that will be notified each time the status of a nearby device changes.
* Registers a callback that should be notified each time nearby media device(s) change.
*/
oneway void registerNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 2;

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import android.media.NearbyDevice;
/**
* A callback used to receive updates about the status of nearby devices that are able to play
* media.
*
* External clients may allow registration of these callbacks and external clients will be
* responsible for notifying the callbacks appropriately.
*
* @hide
*/
oneway interface INearbyMediaDevicesUpdateCallback {
/**
* Invoked by external clients when changes in nearby media device(s) are detected.
*
* When a callback is newly registered, it should be immediately notified of the current nearby
* media devices. Afterwards, the list of devices passed to the callback should always contain
* the full set of nearby media devices any time you get an update. If a device is no longer
* valid (went offline, e.g.) then it should be omitted from the list in the next update.
*
* @param nearbyDevices the list of nearby devices that have changed status due to moving closer
* or further away.
*/
void onDevicesUpdated(in List<NearbyDevice> nearbyDevices);
}

View File

@@ -14,6 +14,6 @@
* limitations under the License.
*/
package com.android.systemui.shared.media;
package android.media;
parcelable NearbyDevice;

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* A parcelable representing a nearby device that can be used for media transfer.
* <p>
* This class includes:
* <ul>
* <li>an ID identifying the media route.</li>
* <li>a range zone specifying how far away this device is from the device with the media route.
* </li>
* </ul>
* @hide
*/
@SystemApi
public final class NearbyDevice implements Parcelable {
/**
* Unknown distance range.
*/
public static final int RANGE_UNKNOWN = 0;
/**
* Distance is very far away from the peer device.
*/
public static final int RANGE_FAR = 1;
/**
* Distance is relatively long from the peer device, typically a few meters.
*/
public static final int RANGE_LONG = 2;
/**
* Distance is close to the peer device, typically with one or two meter.
*/
public static final int RANGE_CLOSE = 3;
/**
* Distance is very close to the peer device, typically within one meter or less.
*/
public static final int RANGE_WITHIN_REACH = 4;
/**
* The various range zones a device can be in, in relation to the current device.
*
* @hide
*/
@IntDef(prefix = { "RANGE_" }, value = {
RANGE_UNKNOWN,
RANGE_FAR,
RANGE_LONG,
RANGE_CLOSE,
RANGE_WITHIN_REACH
})
@Retention(RetentionPolicy.SOURCE)
public @interface RangeZone {}
/**
* Gets a human-readable string of the range zone.
*/
@NonNull
public static String rangeZoneToString(@RangeZone int rangeZone) {
switch (rangeZone) {
case RANGE_UNKNOWN:
return "UNKNOWN";
case RANGE_FAR:
return "FAR";
case RANGE_LONG:
return "LONG";
case RANGE_CLOSE:
return "CLOSE";
case RANGE_WITHIN_REACH:
return "WITHIN_REACH";
default:
return "Invalid";
}
}
@NonNull private final String mMediaRoute2Id;
@RangeZone private final int mRangeZone;
public NearbyDevice(@NonNull String mediaRoute2Id, int rangeZone) {
mMediaRoute2Id = mediaRoute2Id;
mRangeZone = rangeZone;
}
private NearbyDevice(@NonNull Parcel in) {
mMediaRoute2Id = in.readString();
mRangeZone = in.readInt();
}
@NonNull
public static final Creator<NearbyDevice> CREATOR = new Creator<NearbyDevice>() {
@Override
public NearbyDevice createFromParcel(@NonNull Parcel in) {
return new NearbyDevice(in);
}
@Override
public NearbyDevice[] newArray(int size) {
return new NearbyDevice[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public String toString() {
return "NearbyDevice{mediaRoute2Id=" + mMediaRoute2Id
+ " rangeZone=" + rangeZoneToString(mRangeZone) + "}";
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mMediaRoute2Id);
dest.writeInt(mRangeZone);
}
/**
* Returns the ID of the media route associated with the device.
*
* @see MediaRoute2Info#getId
*/
@NonNull
public String getMediaRoute2Id() {
return mMediaRoute2Id;
}
/** Returns the range that the device is currently in. */
@RangeZone
public int getRangeZone() {
return mRangeZone;
}
}

View File

@@ -886,12 +886,6 @@
android:singleUser="true"
android:permission="android.permission.BIND_DREAM_SERVICE" />
<!-- Service for external clients to notify us of nearby media devices -->
<!-- TODO(b/216313420): Export and guard with a permission. -->
<service
android:name=".media.nearby.NearbyMediaDevicesService"
/>
<receiver
android:name=".tuner.TunerService$ClearReceiver"
android:exported="false">

View File

@@ -1,33 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.shared.media;
import com.android.systemui.shared.media.INearbyMediaDevicesProvider;
/**
* An interface that can be invoked to notify System UI of nearby media devices.
*
* External clients wanting to notify System UI about the status of nearby media devices should
* implement {@link INearbyMediaDevicesProvider} and then register it with system UI using this
* service.
*
* System UI will implement this interface and external clients will invoke it.
*/
interface INearbyMediaDevicesService {
/** Registers a new provider. */
oneway void registerProvider(INearbyMediaDevicesProvider provider) = 1;
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.shared.media;
/**
* A callback used to notify implementors of changes in the status of nearby devices that are able
* to play media.
*
* External clients may allow registration of these callbacks and external clients will be
* responsible for notifying the callbacks appropriately. System UI is only a mediator between the
* external client and these callbacks.
*/
interface INearbyMediaDevicesUpdateCallback {
/** Unknown distance range. */
const int RANGE_UNKNOWN = 0;
/** Distance is very far away from the peer device. */
const int RANGE_FAR = 1;
/** Distance is relatively long from the peer device, typically a few meters. */
const int RANGE_LONG = 2;
/** Distance is close to the peer device, typically with one or two meter. */
const int RANGE_CLOSE = 3;
/** Distance is very close to the peer device, typically within one meter or less. */
const int RANGE_WITHIN_REACH = 4;
/** Invoked by external clients when media device changes are detected. */
oneway void nearbyDeviceUpdate(in String routeId, in int rangeZone) = 1;
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.shared.media
import android.os.Parcel
import android.os.Parcelable
/**
* A parcelable representing a nearby device that can be used for media transfer.
*
* This class includes:
* - [routeId] identifying the media route
* - [rangeZone] specifying how far away the device with the media route is from this device.
*/
class NearbyDevice(
val routeId: String?,
@RangeZone val rangeZone: Int
) : Parcelable {
private constructor(parcel: Parcel) : this(
routeId = parcel.readString() ?: null,
rangeZone = parcel.readInt()
)
override fun describeContents() = 0
override fun writeToParcel(out: Parcel, flags: Int) {
out.writeString(routeId)
out.writeInt(rangeZone)
}
companion object CREATOR : Parcelable.Creator<NearbyDevice?> {
override fun createFromParcel(parcel: Parcel) = NearbyDevice(parcel)
override fun newArray(size: Int) = arrayOfNulls<NearbyDevice?>(size)
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.shared.media
import androidx.annotation.IntDef
import kotlin.annotation.AnnotationRetention
@IntDef(
INearbyMediaDevicesUpdateCallback.RANGE_UNKNOWN,
INearbyMediaDevicesUpdateCallback.RANGE_FAR,
INearbyMediaDevicesUpdateCallback.RANGE_LONG,
INearbyMediaDevicesUpdateCallback.RANGE_CLOSE,
INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
)
@Retention(AnnotationRetention.SOURCE)
/** The various range zones a device can be in, in relation to the current device. */
annotation class RangeZone

View File

@@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.PerUser;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
@@ -152,6 +153,7 @@ public interface SysUIComponent {
getMediaTttChipControllerReceiver();
getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -231,6 +233,9 @@ public interface SysUIComponent {
/** */
Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
/** */
Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
/**
* Returns {@link CoreStartable}s that should be started with the application.
*/

View File

@@ -146,6 +146,7 @@ public class Flags {
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true);
public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, false);
public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
// 1000 - dock

View File

@@ -42,4 +42,10 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
* Check whether we support displaying information about mute await connections.
*/
fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
/**
* Check whether we enable support for nearby media devices. See
* [android.app.StatusBarManager.registerNearbyMediaDevicesProvider] for more information.
*/
fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES)
}

View File

@@ -16,7 +16,6 @@
package com.android.systemui.media.dagger;
import android.app.Service;
import android.content.Context;
import android.os.Handler;
import android.view.WindowManager;
@@ -30,7 +29,7 @@ import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesService;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
@@ -43,11 +42,9 @@ import java.util.concurrent.Executor;
import javax.inject.Named;
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
/** Dagger module for the media package. */
@Module(subcomponents = {
@@ -159,9 +156,15 @@ public interface MediaModule {
return Optional.of(new MediaMuteAwaitConnectionCli(commandRegistry, context));
}
/** Inject into NearbyMediaDevicesService. */
@Binds
@IntoMap
@ClassKey(NearbyMediaDevicesService.class)
Service bindMediaNearbyDevicesService(NearbyMediaDevicesService service);
/** */
@Provides
@SysUISingleton
static Optional<NearbyMediaDevicesManager> providesNearbyMediaDevicesManager(
MediaFlags mediaFlags,
Lazy<NearbyMediaDevicesManager> nearbyMediaDevicesManagerLazy) {
if (!mediaFlags.areNearbyMediaDevicesEnabled()) {
return Optional.empty();
}
return Optional.of(nearbyMediaDevicesManagerLazy.get());
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.media.nearby
import android.media.INearbyMediaDevicesProvider
import android.media.INearbyMediaDevicesUpdateCallback
import com.android.systemui.dagger.SysUISingleton
import android.os.IBinder
import com.android.systemui.statusbar.CommandQueue
import javax.inject.Inject
/**
* A service that acts as a bridge between (1) external clients that have data on nearby devices
* that are able to play media and (2) internal clients (like media Output Switcher) that need data
* on these nearby devices.
*
* TODO(b/216313420): Add logging to this class.
*/
@SysUISingleton
class NearbyMediaDevicesManager @Inject constructor(
commandQueue: CommandQueue
) {
private var providers: MutableList<INearbyMediaDevicesProvider> = mutableListOf()
private var activeCallbacks: MutableList<INearbyMediaDevicesUpdateCallback> = mutableListOf()
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun registerNearbyMediaDevicesProvider(newProvider: INearbyMediaDevicesProvider) {
if (providers.contains(newProvider)) {
return
}
activeCallbacks.forEach {
newProvider.registerNearbyDevicesCallback(it)
}
providers.add(newProvider)
newProvider.asBinder().linkToDeath(deathRecipient, /* flags= */ 0)
}
override fun unregisterNearbyMediaDevicesProvider(
newProvider: INearbyMediaDevicesProvider
) {
providers.remove(newProvider)
}
}
private val deathRecipient = object : IBinder.DeathRecipient {
override fun binderDied() {
// Should not be used as binderDied(IBinder who) is overridden.
}
override fun binderDied(who: IBinder) {
binderDiedInternal(who)
}
}
init {
commandQueue.addCallback(commandQueueCallbacks)
}
/**
* Registers [callback] to be notified each time a device's range changes or when a new device
* comes within range.
*
* If a new provider is added, previously-registered callbacks will be registered with the
* new provider.
*/
fun registerNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
providers.forEach {
it.registerNearbyDevicesCallback(callback)
}
activeCallbacks.add(callback)
}
/**
* Un-registers [callback]. See [registerNearbyDevicesCallback].
*/
fun unregisterNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
activeCallbacks.remove(callback)
providers.forEach {
it.unregisterNearbyDevicesCallback(callback)
}
}
private fun binderDiedInternal(who: IBinder) {
synchronized(providers) {
for (i in providers.size - 1 downTo 0) {
if (providers[i].asBinder() == who) {
providers.removeAt(i)
break
}
}
}
}
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.media.nearby
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.media.INearbyMediaDevicesProvider
import com.android.systemui.shared.media.INearbyMediaDevicesService
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
import com.android.systemui.shared.media.NearbyDevice
import javax.inject.Inject
/**
* A service that acts as a bridge between (1) external clients that have data on nearby devices
* that are able to play media and (2) internal clients (like media Output Switcher) that need data
* on these nearby devices.
*
* TODO(b/216313420): Add logging to this class.
*/
@SysUISingleton
class NearbyMediaDevicesService @Inject constructor() : Service() {
private var provider: INearbyMediaDevicesProvider? = null
private val binder: IBinder = object : INearbyMediaDevicesService.Stub() {
override fun registerProvider(newProvider: INearbyMediaDevicesProvider) {
provider = newProvider
newProvider.asBinder().linkToDeath(
{
// We might've gotten a new provider before the old provider died, so we only
// need to clear our provider if the most recent provider died.
if (provider == newProvider) {
provider = null
}
},
/* flags= */ 0
)
}
}
override fun onBind(intent: Intent?): IBinder = binder
/** Returns a list containing the current nearby devices. */
fun getCurrentNearbyDevices(): List<NearbyDevice> {
val currentProvider = provider ?: return emptyList()
return currentProvider.currentNearbyDevices
}
/**
* Registers [callback] to be notified each time a device's range changes or when a new device
* comes within range.
*/
fun registerNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
val currentProvider = provider ?: return
currentProvider.registerNearbyDevicesCallback(callback)
}
/**
* Un-registers [callback]. See [registerNearbyDevicesCallback].
*/
fun unregisterNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
val currentProvider = provider ?: return
currentProvider.unregisterNearbyDevicesCallback(callback)
}
}

View File

@@ -43,6 +43,7 @@ import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.os.Bundle;
import android.os.Handler;
@@ -160,6 +161,8 @@ public class CommandQueue extends IStatusBar.Stub implements
private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT;
private static final int MSG_MEDIA_TRANSFER_SENDER_STATE = 64 << MSG_SHIFT;
private static final int MSG_MEDIA_TRANSFER_RECEIVER_STATE = 65 << MSG_SHIFT;
private static final int MSG_REGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 66 << MSG_SHIFT;
private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -456,6 +459,18 @@ public class CommandQueue extends IStatusBar.Stub implements
@NonNull MediaRoute2Info routeInfo,
@Nullable Icon appIcon,
@Nullable CharSequence appName) {}
/**
* @see IStatusBar#registerNearbyMediaDevicesProvider
*/
default void registerNearbyMediaDevicesProvider(
@NonNull INearbyMediaDevicesProvider provider) {}
/**
* @see IStatusBar#unregisterNearbyMediaDevicesProvider
*/
default void unregisterNearbyMediaDevicesProvider(
@NonNull INearbyMediaDevicesProvider provider) {}
}
public CommandQueue(Context context) {
@@ -1221,6 +1236,18 @@ public class CommandQueue extends IStatusBar.Stub implements
mHandler.obtainMessage(MSG_MEDIA_TRANSFER_RECEIVER_STATE, args).sendToTarget();
}
@Override
public void registerNearbyMediaDevicesProvider(@NonNull INearbyMediaDevicesProvider provider) {
mHandler.obtainMessage(MSG_REGISTER_NEARBY_MEDIA_DEVICE_PROVIDER, provider).sendToTarget();
}
@Override
public void unregisterNearbyMediaDevicesProvider(
@NonNull INearbyMediaDevicesProvider provider) {
mHandler.obtainMessage(MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER, provider)
.sendToTarget();
}
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1643,6 +1670,18 @@ public class CommandQueue extends IStatusBar.Stub implements
}
args.recycle();
break;
case MSG_REGISTER_NEARBY_MEDIA_DEVICE_PROVIDER:
INearbyMediaDevicesProvider provider = (INearbyMediaDevicesProvider) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).registerNearbyMediaDevicesProvider(provider);
}
break;
case MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER:
provider = (INearbyMediaDevicesProvider) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).unregisterNearbyMediaDevicesProvider(provider);
}
break;
}
}
}

View File

@@ -0,0 +1,146 @@
package com.android.systemui.media.nearby
import android.media.INearbyMediaDevicesProvider
import android.media.INearbyMediaDevicesUpdateCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import android.media.NearbyDevice
import com.android.systemui.statusbar.CommandQueue
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@SmallTest
class NearbyMediaDevicesManagerTest : SysuiTestCase() {
private lateinit var manager: NearbyMediaDevicesManager
@Mock
private lateinit var commandQueue: CommandQueue
private lateinit var commandQueueCallbacks: CommandQueue.Callbacks
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
manager = NearbyMediaDevicesManager(commandQueue)
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
Mockito.verify(commandQueue).addCallback(callbackCaptor.capture())
commandQueueCallbacks = callbackCaptor.value!!
}
@Test
fun registerNearbyDevicesCallback_noProviderRegistered_noCrash() {
// No assert, just needs no crash
manager.registerNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun onDevicesUpdated(nearbyDevices: List<NearbyDevice>) {}
})
}
@Test
fun registerNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
val provider = TestProvider()
commandQueueCallbacks.registerNearbyMediaDevicesProvider(provider)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun onDevicesUpdated(nearbyDevices: List<NearbyDevice>) {}
}
manager.registerNearbyDevicesCallback(callback)
assertThat(provider.lastRegisteredCallback).isEqualTo(callback)
}
@Test
fun registerNearbyDevicesCallback_multipleProviders_allProvidersReceiveCallback() {
val provider1 = TestProvider()
val provider2 = TestProvider()
commandQueueCallbacks.registerNearbyMediaDevicesProvider(provider1)
commandQueueCallbacks.registerNearbyMediaDevicesProvider(provider2)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun onDevicesUpdated(nearbyDevices: List<NearbyDevice>) {}
}
manager.registerNearbyDevicesCallback(callback)
assertThat(provider1.lastRegisteredCallback).isEqualTo(callback)
assertThat(provider2.lastRegisteredCallback).isEqualTo(callback)
}
@Test
fun unregisterNearbyDevicesCallback_noProviderRegistered_noCrash() {
// No assert, just needs no crash
manager.unregisterNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun onDevicesUpdated(nearbyDevices: List<NearbyDevice>) {}
})
}
@Test
fun unregisterNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
val provider = TestProvider()
commandQueueCallbacks.registerNearbyMediaDevicesProvider(provider)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun onDevicesUpdated(nearbyDevices: List<NearbyDevice>) {}
}
manager.unregisterNearbyDevicesCallback(callback)
assertThat(provider.lastUnregisteredCallback).isEqualTo(callback)
}
@Test
fun unregisterNearbyDevicesCallback_multipleProviders_allProvidersReceiveCallback() {
val provider1 = TestProvider()
val provider2 = TestProvider()
commandQueueCallbacks.registerNearbyMediaDevicesProvider(provider1)
commandQueueCallbacks.registerNearbyMediaDevicesProvider(provider2)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun onDevicesUpdated(nearbyDevices: List<NearbyDevice>) {}
}
manager.unregisterNearbyDevicesCallback(callback)
assertThat(provider1.lastUnregisteredCallback).isEqualTo(callback)
assertThat(provider2.lastUnregisteredCallback).isEqualTo(callback)
}
@Test
fun newProviderRegisteredAfterCallbacksRegistered_providerGetsPreviouslyRegisteredCallbacks() {
// Start off with an existing provider and callback
val provider1 = TestProvider()
commandQueueCallbacks.registerNearbyMediaDevicesProvider(provider1)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun onDevicesUpdated(nearbyDevices: List<NearbyDevice>) {}
}
manager.registerNearbyDevicesCallback(callback)
// Add a new provider
val provider2 = TestProvider()
commandQueueCallbacks.registerNearbyMediaDevicesProvider(provider2)
// Verify the new provider received the previously-registered callbacks
assertThat(provider2.lastRegisteredCallback).isEqualTo(callback)
}
private class TestProvider : INearbyMediaDevicesProvider.Stub() {
var lastRegisteredCallback: INearbyMediaDevicesUpdateCallback? = null
var lastUnregisteredCallback: INearbyMediaDevicesUpdateCallback? = null
override fun registerNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback
) {
lastRegisteredCallback = callback
}
override fun unregisterNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback
) {
lastUnregisteredCallback = callback
}
}
}

View File

@@ -1,124 +0,0 @@
package com.android.systemui.media.nearby
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.media.INearbyMediaDevicesProvider
import com.android.systemui.shared.media.INearbyMediaDevicesService
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_LONG
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
import com.android.systemui.shared.media.NearbyDevice
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@SmallTest
class NearbyMediaDevicesServiceTest : SysuiTestCase() {
private lateinit var service: NearbyMediaDevicesService
private lateinit var binderInterface: INearbyMediaDevicesService
@Before
fun setUp() {
service = NearbyMediaDevicesService()
binderInterface = INearbyMediaDevicesService.Stub.asInterface(service.onBind(null))
}
@Test
fun getCurrentNearbyDevices_noProviderRegistered_returnsEmptyList() {
assertThat(service.getCurrentNearbyDevices()).isEmpty()
}
@Test
fun getCurrentNearbyDevices_providerRegistered_returnsProviderInfo() {
val nearbyDevice1 = NearbyDevice("routeId1", RANGE_LONG)
val nearbyDevice2 = NearbyDevice("routeId2", RANGE_WITHIN_REACH)
val provider = object : INearbyMediaDevicesProvider.Stub() {
override fun getCurrentNearbyDevices(): List<NearbyDevice> {
return listOf(nearbyDevice1, nearbyDevice2)
}
override fun registerNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {}
override fun unregisterNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {}
}
binderInterface.registerProvider(provider)
val returnedNearbyDevices = service.getCurrentNearbyDevices()
assertThat(returnedNearbyDevices).isEqualTo(listOf(nearbyDevice1, nearbyDevice2))
}
@Test
fun registerNearbyDevicesCallback_noProviderRegistered_noCrash() {
// No assert, just needs no crash
service.registerNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
})
}
@Test
fun registerNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
val provider = object : INearbyMediaDevicesProvider.Stub() {
var registeredCallback: INearbyMediaDevicesUpdateCallback? = null
override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
override fun registerNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {
registeredCallback = callback
}
override fun unregisterNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {}
}
binderInterface.registerProvider(provider)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
}
service.registerNearbyDevicesCallback(callback)
assertThat(provider.registeredCallback).isEqualTo(callback)
}
@Test
fun unregisterNearbyDevicesCallback_noProviderRegistered_noCrash() {
// No assert, just needs no crash
service.unregisterNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
})
}
@Test
fun unregisterNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
val provider = object : INearbyMediaDevicesProvider.Stub() {
var unregisteredCallback: INearbyMediaDevicesUpdateCallback? = null
override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
override fun registerNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {}
override fun unregisterNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {
unregisteredCallback = callback
}
}
binderInterface.registerProvider(provider)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
}
service.unregisterNearbyDevicesCallback(callback)
assertThat(provider.unregisteredCallback).isEqualTo(callback)
}
}

View File

@@ -53,6 +53,7 @@ import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.net.Uri;
import android.os.Binder;
@@ -2045,6 +2046,56 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
}
/**
* Registers a provider that gives information about nearby devices that are able to play media.
* See {@link StatusBarmanager.registerNearbyMediaDevicesProvider}.
*
* Requires the caller to have the {@link android.Manifest.permission.MEDIA_CONTENT_CONTROL}
* permission.
*
* @param provider the nearby device information provider to register
*
* @hide
*/
@Override
public void registerNearbyMediaDevicesProvider(
@NonNull INearbyMediaDevicesProvider provider
) {
enforceMediaContentControl();
if (mBar != null) {
try {
mBar.registerNearbyMediaDevicesProvider(provider);
} catch (RemoteException e) {
Slog.e(TAG, "registerNearbyMediaDevicesProvider", e);
}
}
}
/**
* Unregisters a provider that gives information about nearby devices that are able to play
* media. See {@link StatusBarmanager.unregisterNearbyMediaDevicesProvider}.
*
* Requires the caller to have the {@link android.Manifest.permission.MEDIA_CONTENT_CONTROL}
* permission.
*
* @param provider the nearby device information provider to unregister
*
* @hide
*/
@Override
public void unregisterNearbyMediaDevicesProvider(
@NonNull INearbyMediaDevicesProvider provider
) {
enforceMediaContentControl();
if (mBar != null) {
try {
mBar.unregisterNearbyMediaDevicesProvider(provider);
} catch (RemoteException e) {
Slog.e(TAG, "unregisterNearbyMediaDevicesProvider", e);
}
}
}
/** @hide */
public void passThroughShellCommand(String[] args, FileDescriptor fd) {
enforceStatusBarOrShell();