Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 18b8febd authored by Jin Seok Park's avatar Jin Seok Park
Browse files

Expose remote volume controller-related APIs

MediaSessionManager#registerRemoteVolumeController allows apps to
attach a binder to media service in order to be notified when
the session or volume of the remote volume controller is changed.

Since binders cannot be exposed, this CL instead adds and exposes
as system API a Callback interface that apps can implement.

Bug: 161332642
Test: Manual test by changing volume when remote casting
Change-Id: Ibb009559e818711ca40f7bc1b3694a49484960b3
parent 0ef85393
Loading
Loading
Loading
Loading
+229 −0
Original line number Diff line number Diff line
// Signature format: 2.0
package android.app {

  public class ActivityManager {
    method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
    method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
  }

  public class AppOpsManager {
    field public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage";
  }

  public abstract class HomeVisibilityListener {
    ctor public HomeVisibilityListener();
    method public abstract void onHomeVisibilityChanged(boolean);
  }

  public class NotificationManager {
    method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
    field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
  }

  public class StatusBarManager {
    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
  }

}

package android.app.role {

  public final class RoleManager {
    method @Nullable public String getSmsRoleHolder(int);
  }

}

package android.content.rollback {

  public class RollbackManagerFrameworkInitializer {
    method public static void initialize();
  }

}

package android.graphics {

  public final class Compatibility {
    method public static void setTargetSdkVersion(int);
  }

  public final class ImageDecoder implements java.lang.AutoCloseable {
    method @AnyThread @NonNull public static android.graphics.ImageDecoder.Source createSource(@NonNull android.content.ContentResolver, @NonNull android.net.Uri, @Nullable android.content.res.Resources);
  }

}

package android.media {

  public class AudioManager {
    method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
    method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
    method public void setStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
    field public static final int FLAG_FROM_KEY = 4096; // 0x1000
  }

  public class MediaMetadataRetriever implements java.lang.AutoCloseable {
    field public static final int METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40; // 0x28
  }

  @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;
  }

}

package android.media.session {

  public static final class MediaController.PlaybackInfo implements android.os.Parcelable {
    ctor public MediaController.PlaybackInfo(int, int, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.media.AudioAttributes, @Nullable String);
  }

  public final class MediaSession {
    field public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 65536; // 0x10000
  }

  public static final class MediaSession.Token implements android.os.Parcelable {
    method public int getUid();
  }

  public final class MediaSessionManager {
    method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, int, @Nullable android.os.Handler);
    method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent);
    method public boolean dispatchMediaKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
    method public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
    method public void registerRemoteVolumeControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.RemoteVolumeControllerCallback);
    method public void unregisterRemoteVolumeControllerCallback(@NonNull android.media.session.MediaSessionManager.RemoteVolumeControllerCallback);
    field public static final int RESULT_MEDIA_KEY_HANDLED = 1; // 0x1
    field public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0; // 0x0
  }

  public static interface MediaSessionManager.RemoteVolumeControllerCallback {
    method public void onSessionChanged(@Nullable android.media.session.MediaSession.Token);
    method public void onVolumeChanged(@NonNull android.media.session.MediaSession.Token, int);
  }

  public final class PlaybackState implements android.os.Parcelable {
    method public boolean isActiveState();
  }

}

package android.net {

  public final class TetheringConstants {
    field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
    field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
    field public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
    field public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
    field public static final String EXTRA_SET_ALARM = "extraSetAlarm";
  }

  public class TetheringManager {
    ctor public TetheringManager(@NonNull android.content.Context, @NonNull java.util.function.Supplier<android.os.IBinder>);
    method public int getLastTetherError(@NonNull String);
    method @NonNull public String[] getTetherableBluetoothRegexs();
    method @NonNull public String[] getTetherableIfaces();
    method @NonNull public String[] getTetherableUsbRegexs();
    method @NonNull public String[] getTetherableWifiRegexs();
    method @NonNull public String[] getTetheredIfaces();
    method @NonNull public String[] getTetheringErroredIfaces();
    method public boolean isTetheringSupported();
    method public boolean isTetheringSupported(@NonNull String);
    method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean);
    method @Deprecated public int setUsbTethering(boolean);
    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
    method @Deprecated public int tether(@NonNull String);
    method @Deprecated public int untether(@NonNull String);
  }

  public static interface TetheringManager.TetheringEventCallback {
    method public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
  }

  public static class TetheringManager.TetheringInterfaceRegexps {
    method @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
    method @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
    method @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
  }

}

package android.os {

  public class Binder implements android.os.IBinder {
    method public final void markVintfStability();
  }

  public interface Parcelable {
    method public default int getStability();
  }

  public class StatsFrameworkInitializer {
    method public static void registerServiceWrappers();
    method public static void setStatsServiceManager(@NonNull android.os.StatsServiceManager);
  }

  public class StatsServiceManager {
    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsCompanionServiceRegisterer();
    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsManagerServiceRegisterer();
    method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsdServiceRegisterer();
  }

  public static class StatsServiceManager.ServiceNotFoundException extends java.lang.Exception {
    ctor public StatsServiceManager.ServiceNotFoundException(@NonNull String);
  }

  public static final class StatsServiceManager.ServiceRegisterer {
    method @Nullable public android.os.IBinder get();
    method @Nullable public android.os.IBinder getOrThrow() throws android.os.StatsServiceManager.ServiceNotFoundException;
  }

}

package android.provider {

  public final class DeviceConfig {
    field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
    field public static final String NAMESPACE_APP_STANDBY = "app_standby";
    field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
  }

}

package android.telephony {

  public abstract class CellSignalStrength {
    method public static int getNumSignalStrengthLevels();
  }

  public class TelephonyManager {
    method @NonNull public static int[] getAllNetworkTypes();
  }

}

package android.util {

  public class AtomicFile {
    ctor public AtomicFile(@NonNull java.io.File, @Nullable android.util.SystemConfigFileCommitEventLogger);
  }

  public final class Log {
    method public static int logToRadioBuffer(int, @Nullable String, @Nullable String);
  }

  public class SystemConfigFileCommitEventLogger {
    ctor public SystemConfigFileCommitEventLogger(@NonNull String);
    method public void setStartTime(long);
  }

}
+7 −0
Original line number Diff line number Diff line
@@ -89,10 +89,17 @@ package android.media.session {
    method public boolean dispatchMediaKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
    method public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
    method public void registerRemoteVolumeControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.RemoteVolumeControllerCallback);
    method public void unregisterRemoteVolumeControllerCallback(@NonNull android.media.session.MediaSessionManager.RemoteVolumeControllerCallback);
    field public static final int RESULT_MEDIA_KEY_HANDLED = 1; // 0x1
    field public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0; // 0x0
  }

  public static interface MediaSessionManager.RemoteVolumeControllerCallback {
    method public void onSessionChanged(@Nullable android.media.session.MediaSession.Token);
    method public void onVolumeChanged(@NonNull android.media.session.MediaSession.Token, int);
  }

  public final class PlaybackState implements android.os.Parcelable {
    method public boolean isActiveState();
  }
+3 −3
Original line number Diff line number Diff line
@@ -25,9 +25,9 @@ import android.media.session.MediaSession;
 * TODO add in better support for multiple remote sessions.
 * @hide
 */
oneway interface IRemoteVolumeController {
    void remoteVolumeChanged(in MediaSession.Token sessionToken, int flags);
oneway interface IRemoteVolumeControllerCallback {
    void onVolumeChanged(in MediaSession.Token sessionToken, int flags);
    // sets the default session to use with the slider, replaces remoteSliderVisibility
    // on IVolumeController
    void updateRemoteController(in MediaSession.Token sessionToken);
    void onSessionChanged(in MediaSession.Token sessionToken);
}
+3 −3
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@ package android.media.session;

import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
import android.media.IRemoteVolumeController;
import android.media.IRemoteVolumeControllerCallback;
import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
import android.media.session.IOnMediaKeyEventDispatchedListener;
@@ -57,8 +57,8 @@ interface ISessionManager {
    void addSession2TokensListener(in ISession2TokensListener listener, int userId);
    void removeSession2TokensListener(in ISession2TokensListener listener);

    void registerRemoteVolumeController(in IRemoteVolumeController rvc);
    void unregisterRemoteVolumeController(in IRemoteVolumeController rvc);
    void registerRemoteVolumeControllerCallback(in IRemoteVolumeControllerCallback rvc);
    void unregisterRemoteVolumeControllerCallback(in IRemoteVolumeControllerCallback rvc);

    // For PhoneWindowManager to precheck media keys
    boolean isGlobalPriorityActive();
+96 −14
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.media.AudioManager;
import android.media.IRemoteVolumeController;
import android.media.IRemoteVolumeControllerCallback;
import android.media.MediaFrameworkInitializer;
import android.media.MediaSession2;
import android.media.Session2Token;
@@ -88,6 +88,8 @@ public final class MediaSessionManager {
    private final OnMediaKeyEventSessionChangedListenerStub
            mOnMediaKeyEventSessionChangedListenerStub =
            new OnMediaKeyEventSessionChangedListenerStub();
    private final RemoteVolumeControllerCallbackStub mRemoteVolumeControllerCallbackStub =
            new RemoteVolumeControllerCallbackStub();

    private final Object mLock = new Object();
    @GuardedBy("mLock")
@@ -106,6 +108,9 @@ public final class MediaSessionManager {
    private String mCurMediaKeyEventSessionPackage;
    @GuardedBy("mLock")
    private MediaSession.Token mCurMediaKeyEventSession;
    @GuardedBy("mLock")
    private final Map<RemoteVolumeControllerCallback, Executor>
            mRemoteVolumeControllerCallbacks = new ArrayMap<>();

    private Context mContext;
    private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
@@ -462,33 +467,62 @@ public final class MediaSessionManager {
    }

    /**
     * Set the remote volume controller to receive volume updates on.
     * Set the remote volume controller callback to receive volume updates on.
     * Only for use by System UI and Settings application.
     *
     * @param rvc The volume controller to receive updates on.
     * @param callback The volume controller callback to receive updates on.
     * @hide
     */
    public void registerRemoteVolumeController(IRemoteVolumeController rvc) {
    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
    public void registerRemoteVolumeControllerCallback(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull RemoteVolumeControllerCallback callback) {
        Objects.requireNonNull(executor, "executor shouldn't be null");
        Objects.requireNonNull(callback, "callback shouldn't be null");
        boolean shouldRegisterCallback = false;
        synchronized (mLock) {
            int prevCallbackCount = mRemoteVolumeControllerCallbacks.size();
            mRemoteVolumeControllerCallbacks.put(callback, executor);
            if (prevCallbackCount == 0 && mRemoteVolumeControllerCallbacks.size() == 1) {
                shouldRegisterCallback = true;
            }
        }
        if (shouldRegisterCallback) {
            try {
            mService.registerRemoteVolumeController(rvc);
                mService.registerRemoteVolumeControllerCallback(
                        mRemoteVolumeControllerCallbackStub);
            } catch (RemoteException e) {
            Log.e(TAG, "Error in registerRemoteVolumeController.", e);
                Log.e(TAG, "Failed to register remote volume controller callback", e);
            }
        }
    }

    /**
     * Unregisters the remote volume controller which was previously registered with
     * {@link #registerRemoteVolumeController(IRemoteVolumeController)}.
     * Unregisters the remote volume controller callback which was previously registered with
     * {@link #registerRemoteVolumeControllerCallback(Executor, RemoteVolumeControllerCallback)}.
     * Only for use by System UI and Settings application.
     *
     * @param rvc The volume controller which was registered.
     * @param callback The volume controller callback to receive updates on.
     * @hide
     */
    public void unregisterRemoteVolumeController(IRemoteVolumeController rvc) {
    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
    public void unregisterRemoteVolumeControllerCallback(
            @NonNull RemoteVolumeControllerCallback callback) {
        Objects.requireNonNull(callback, "callback shouldn't be null");
        boolean shouldUnregisterCallback = false;
        synchronized (mLock) {
            if (mRemoteVolumeControllerCallbacks.remove(callback) != null
                    && mRemoteVolumeControllerCallbacks.size() == 0) {
                shouldUnregisterCallback = true;
            }
        }
        try {
            mService.unregisterRemoteVolumeController(rvc);
            if (shouldUnregisterCallback) {
                mService.unregisterRemoteVolumeControllerCallback(
                        mRemoteVolumeControllerCallbackStub);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Error in unregisterRemoteVolumeController.", e);
            Log.e(TAG, "Failed to unregister remote volume controller callback", e);
        }
    }

@@ -1058,6 +1092,29 @@ public final class MediaSessionManager {
                @Nullable MediaSession.Token sessionToken);
    }

    /**
     * Callback to receive changes in the remote volume controller.
     *
     * @hide
     */
    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
    public interface RemoteVolumeControllerCallback {
        /**
         * Called when the volume is changed.
         *
         * @param sessionToken the remote media session token
         * @param flags any of the flags from {@link AudioManager}
         */
        void onVolumeChanged(@NonNull MediaSession.Token sessionToken, int flags);

        /**
         * Called when the session for the default remote controller is changed.
         *
         * @param sessionToken the remote media session token
         */
        void onSessionChanged(@Nullable MediaSession.Token sessionToken);
    }

    /**
     * Information of a remote user of {@link MediaSession} or {@link MediaBrowserService}.
     * This can be used to decide whether the remote user is trusted app, and also differentiate
@@ -1290,4 +1347,29 @@ public final class MediaSessionManager {
            }
        }
    }

    private final class RemoteVolumeControllerCallbackStub
            extends IRemoteVolumeControllerCallback.Stub {
        @Override
        public void onVolumeChanged(MediaSession.Token sessionToken, int flags) {
            Map<RemoteVolumeControllerCallback, Executor> callbacks = new ArrayMap<>();
            synchronized (mLock) {
                callbacks.putAll(mRemoteVolumeControllerCallbacks);
            }
            for (Map.Entry<RemoteVolumeControllerCallback, Executor> e : callbacks.entrySet()) {
                e.getValue().execute(() -> e.getKey().onVolumeChanged(sessionToken, flags));
            }
        }

        @Override
        public void onSessionChanged(MediaSession.Token sessionToken) {
            Map<RemoteVolumeControllerCallback, Executor> callbacks = new ArrayMap<>();
            synchronized (mLock) {
                callbacks.putAll(mRemoteVolumeControllerCallbacks);
            }
            for (Map.Entry<RemoteVolumeControllerCallback, Executor> e : callbacks.entrySet()) {
                e.getValue().execute(() -> e.getKey().onSessionChanged(sessionToken));
            }
        }
    }
}
Loading