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

Commit 961cae92 authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

New media button API.

This allows sending media buttons to any PendingIntent,
so they can be captured with a registered receiver.

Also add some new ViewTreeObserver APIs; this is all for
a new support library API to watch media buttons while an
app has input focus.

Change-Id: I3c51cef59460662b008c9a2cc87d6a6383c21855
parent b7f4c7b7
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -10956,6 +10956,7 @@ package android.media {
    method public void playSoundEffect(int);
    method public void playSoundEffect(int, float);
    method public void registerMediaButtonEventReceiver(android.content.ComponentName);
    method public void registerMediaButtonEventReceiver(android.app.PendingIntent);
    method public void registerRemoteControlClient(android.media.RemoteControlClient);
    method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int);
    method public deprecated void setBluetoothA2dpOn(boolean);
@@ -10976,6 +10977,7 @@ package android.media {
    method public void stopBluetoothSco();
    method public void unloadSoundEffects();
    method public void unregisterMediaButtonEventReceiver(android.content.ComponentName);
    method public void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
    method public void unregisterRemoteControlClient(android.media.RemoteControlClient);
    field public static final java.lang.String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY";
    field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED";
@@ -26132,6 +26134,8 @@ package android.view {
    method public void addOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
    method public void addOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
    method public void addOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
    method public void addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener);
    method public void addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener);
    method public final void dispatchOnDraw();
    method public final void dispatchOnGlobalLayout();
    method public final boolean dispatchOnPreDraw();
@@ -26143,6 +26147,8 @@ package android.view {
    method public void removeOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
    method public void removeOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
    method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
    method public void removeOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener);
    method public void removeOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener);
  }
  public static abstract interface ViewTreeObserver.OnDrawListener {
@@ -26169,6 +26175,15 @@ package android.view {
    method public abstract void onTouchModeChanged(boolean);
  }
  public static abstract interface ViewTreeObserver.OnWindowAttachListener {
    method public abstract void onWindowAttached();
    method public abstract void onWindowDetached();
  }
  public static abstract interface ViewTreeObserver.OnWindowFocusChangeListener {
    method public abstract void onWindowFocusChanged(boolean);
  }
  public abstract class Window {
    ctor public Window(android.content.Context);
    method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+3 −0
Original line number Diff line number Diff line
@@ -1217,6 +1217,7 @@ public final class ViewRootImpl implements ViewParent,
                host.setLayoutDirection(mLastConfiguration.getLayoutDirection());
            }
            host.dispatchAttachedToWindow(attachInfo, 0);
            attachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
            host.fitSystemWindows(mFitSystemWindowsInsets);
            //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);
@@ -2811,6 +2812,7 @@ public final class ViewRootImpl implements ViewParent,
                    mAttachInfo.mHardwareRenderer.isEnabled()) {
                mAttachInfo.mHardwareRenderer.validate();
            }
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }

@@ -3111,6 +3113,7 @@ public final class ViewRootImpl implements ViewParent,
                        }
                        mAttachInfo.mKeyDispatchState.reset();
                        mView.dispatchWindowFocusChanged(hasWindowFocus);
                        mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
                    }

                    // Note: must be done after the focus change callbacks,
+153 −0
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
 */
public final class ViewTreeObserver {
    // Recursive listeners use CopyOnWriteArrayList
    private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
    private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
    private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
    private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;

@@ -48,6 +50,36 @@ public final class ViewTreeObserver {

    private boolean mAlive = true;

    /**
     * Interface definition for a callback to be invoked when the view hierarchy is
     * attached to and detached from its window.
     */
    public interface OnWindowAttachListener {
        /**
         * Callback method to be invoked when the view hierarchy is attached to a window
         */
        public void onWindowAttached();

        /**
         * Callback method to be invoked when the view hierarchy is detached from a window
         */
        public void onWindowDetached();
    }

    /**
     * Interface definition for a callback to be invoked when the view hierarchy's window
     * focus state changes.
     */
    public interface OnWindowFocusChangeListener {
        /**
         * Callback method to be invoked when the window focus changes in the view tree.
         *
         * @param hasFocus Set to true if the window is gaining focus, false if it is
         * losing focus.
         */
        public void onWindowFocusChanged(boolean hasFocus);
    }

    /**
     * Interface definition for a callback to be invoked when the focus state within
     * the view tree changes.
@@ -272,6 +304,22 @@ public final class ViewTreeObserver {
     * @param observer The ViewTreeObserver whose listeners must be added to this observer
     */
    void merge(ViewTreeObserver observer) {
        if (observer.mOnWindowAttachListeners != null) {
            if (mOnWindowAttachListeners != null) {
                mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
            } else {
                mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
            }
        }

        if (observer.mOnWindowFocusListeners != null) {
            if (mOnWindowFocusListeners != null) {
                mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
            } else {
                mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
            }
        }

        if (observer.mOnGlobalFocusListeners != null) {
            if (mOnGlobalFocusListeners != null) {
                mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
@@ -323,6 +371,76 @@ public final class ViewTreeObserver {
        observer.kill();
    }

    /**
     * Register a callback to be invoked when the view hierarchy is attached to a window.
     *
     * @param listener The callback to add
     *
     * @throws IllegalStateException If {@link #isAlive()} returns false
     */
    public void addOnWindowAttachListener(OnWindowAttachListener listener) {
        checkIsAlive();

        if (mOnWindowAttachListeners == null) {
            mOnWindowAttachListeners
                    = new CopyOnWriteArrayList<OnWindowAttachListener>();
        }

        mOnWindowAttachListeners.add(listener);
    }

    /**
     * Remove a previously installed window attach callback.
     *
     * @param victim The callback to remove
     *
     * @throws IllegalStateException If {@link #isAlive()} returns false
     *
     * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener)
     */
    public void removeOnWindowAttachListener(OnWindowAttachListener victim) {
        checkIsAlive();
        if (mOnWindowAttachListeners == null) {
            return;
        }
        mOnWindowAttachListeners.remove(victim);
    }

    /**
     * Register a callback to be invoked when the window focus state within the view tree changes.
     *
     * @param listener The callback to add
     *
     * @throws IllegalStateException If {@link #isAlive()} returns false
     */
    public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
        checkIsAlive();

        if (mOnWindowFocusListeners == null) {
            mOnWindowFocusListeners
                    = new CopyOnWriteArrayList<OnWindowFocusChangeListener>();
        }

        mOnWindowFocusListeners.add(listener);
    }

    /**
     * Remove a previously installed window focus change callback.
     *
     * @param victim The callback to remove
     *
     * @throws IllegalStateException If {@link #isAlive()} returns false
     *
     * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener)
     */
    public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
        checkIsAlive();
        if (mOnWindowFocusListeners == null) {
            return;
        }
        mOnWindowFocusListeners.remove(victim);
    }

    /**
     * Register a callback to be invoked when the focus state within the view tree changes.
     *
@@ -620,6 +738,41 @@ public final class ViewTreeObserver {
        mAlive = false;
    }

    /**
     * Notifies registered listeners that window has been attached/detached.
     */
    final void dispatchOnWindowAttachedChange(boolean attached) {
        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
        // perform the dispatching. The iterator is a safe guard against listeners that
        // could mutate the list by calling the various add/remove methods. This prevents
        // the array from being modified while we iterate it.
        final CopyOnWriteArrayList<OnWindowAttachListener> listeners
                = mOnWindowAttachListeners;
        if (listeners != null && listeners.size() > 0) {
            for (OnWindowAttachListener listener : listeners) {
                if (attached) listener.onWindowAttached();
                else listener.onWindowDetached();
            }
        }
    }

    /**
     * Notifies registered listeners that window focus has changed.
     */
    final void dispatchOnWindowFocusChange(boolean hasFocus) {
        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
        // perform the dispatching. The iterator is a safe guard against listeners that
        // could mutate the list by calling the various add/remove methods. This prevents
        // the array from being modified while we iterate it.
        final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners
                = mOnWindowFocusListeners;
        if (listeners != null && listeners.size() > 0) {
            for (OnWindowFocusChangeListener listener : listeners) {
                listener.onWindowFocusChanged(hasFocus);
            }
        }
    }

    /**
     * Notifies registered listeners that focus has changed.
     */
+30 −1
Original line number Diff line number Diff line
@@ -2046,12 +2046,29 @@ public class AudioManager {
        registerMediaButtonIntent(pi, eventReceiver);
    }

    /**
     * Register a component to be the sole receiver of MEDIA_BUTTON intents.  This is like
     * {@link #registerMediaButtonEventReceiver(android.content.ComponentName)}, but allows
     * the buttons to go to any PendingIntent.  Note that you should only use this form if
     * you know you will continue running for the full time until unregistering the
     * PendingIntent.
     * @param eventReceiver target that will receive media button intents.  The PendingIntent
     * will be sent as-is when a media button action occurs, with {@link Intent#EXTRA_KEY_EVENT}
     * added and holding the key code of the media button that was pressed.
     */
    public void registerMediaButtonEventReceiver(PendingIntent eventReceiver) {
        if (eventReceiver == null) {
            return;
        }
        registerMediaButtonIntent(eventReceiver, null);
    }

    /**
     * @hide
     * no-op if (pi == null) or (eventReceiver == null)
     */
    public void registerMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
        if ((pi == null) || (eventReceiver == null)) {
        if (pi == null) {
            Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
            return;
        }
@@ -2113,6 +2130,18 @@ public class AudioManager {
        unregisterMediaButtonIntent(pi, eventReceiver);
    }

    /**
     * Unregister the receiver of MEDIA_BUTTON intents.
     * @param eventReceiver same PendingIntent that was registed with
     *      {@link #registerMediaButtonEventReceiver(PendingIntent)}.
     */
    public void unregisterMediaButtonEventReceiver(PendingIntent eventReceiver) {
        if (eventReceiver == null) {
            return;
        }
        unregisterMediaButtonIntent(eventReceiver, null);
    }

    /**
     * @hide
     */
+25 −21
Original line number Diff line number Diff line
@@ -776,7 +776,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        }
    }

    /** @see AudioManager#adjustVolume(int, int, int) */
    /** @see AudioManager#adjustVolume(int, int) */
    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
        if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType);
        int streamType;
@@ -916,7 +916,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        sendVolumeUpdate(streamType, oldIndex, index, flags);
    }

    /** @see AudioManager#adjustMasterVolume(int) */
    /** @see AudioManager#adjustMasterVolume(int, int) */
    public void adjustMasterVolume(int steps, int flags) {
        ensureValidSteps(steps);
        int volume = Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
@@ -1233,7 +1233,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        return (mStreamStates[streamType].muteCount() != 0);
    }

    /** @see AudioManager#setMasterMute(boolean, IBinder) */
    /** @see AudioManager#setMasterMute(boolean, int) */
    public void setMasterMute(boolean state, int flags, IBinder cb) {
        if (state != AudioSystem.getMasterMute()) {
            AudioSystem.setMasterMute(state);
@@ -1315,7 +1315,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        return Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
    }

    /** @see AudioManager#getMasterStreamType(int) */
    /** @see AudioManager#getMasterStreamType()  */
    public int getMasterStreamType() {
        if (mVoiceCapable) {
            return AudioSystem.STREAM_RING;
@@ -1975,7 +1975,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        }
    }

    /** @see AudioManager#setSpeakerphoneOn() */
    /** @see AudioManager#setSpeakerphoneOn(boolean) */
    public void setSpeakerphoneOn(boolean on){
        if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
            return;
@@ -1991,7 +1991,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        return (mForcedUseForComm == AudioSystem.FORCE_SPEAKER);
    }

    /** @see AudioManager#setBluetoothScoOn() */
    /** @see AudioManager#setBluetoothScoOn(boolean) */
    public void setBluetoothScoOn(boolean on){
        if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
            return;
@@ -2009,7 +2009,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO);
    }

    /** @see AudioManager#setBluetoothA2dpOn() */
    /** @see AudioManager#setBluetoothA2dpOn(boolean) */
    public void setBluetoothA2dpOn(boolean on) {
        synchronized (mBluetoothA2dpEnabledLock) {
            mBluetoothA2dpEnabled = on;
@@ -4127,7 +4127,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
                AudioSystem.setParameters("screen_state=on");
            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                AudioSystem.setParameters("screen_state=off");
            } else if (action.equalsIgnoreCase(Intent.ACTION_CONFIGURATION_CHANGED)) {
            } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                handleConfigurationChanged(context);
            } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
                // attempt to stop music playback for background user
@@ -4296,7 +4296,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
     * Helper function:
     * Called synchronized on mAudioFocusLock
     * Remove a focus listener from the focus stack.
     * @param focusListenerToRemove the focus listener
     * @param clientToRemove the focus listener
     * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
     *   focus, notify the next item in the stack it gained focus.
     */
@@ -4402,7 +4402,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
    }


    /** @see AudioManager#requestAudioFocus(IAudioFocusDispatcher, int, int) */
    /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int)  */
    public int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName) {
        Log.i(TAG, " AudioFocus  requestAudioFocus() from " + clientId);
@@ -4475,7 +4475,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    }

    /** @see AudioManager#abandonAudioFocus(IAudioFocusDispatcher) */
    /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener)  */
    public int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) {
        Log.i(TAG, " AudioFocus  abandonAudioFocus() from " + clientId);
        try {
@@ -4813,8 +4813,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
     * remote control stack if necessary.
     */
    private class RcClientDeathHandler implements IBinder.DeathRecipient {
        private IBinder mCb; // To be notified of client's death
        private PendingIntent mMediaIntent;
        final private IBinder mCb; // To be notified of client's death
        final private PendingIntent mMediaIntent;

        RcClientDeathHandler(IBinder cb, PendingIntent pi) {
            mCb = cb;
@@ -4879,12 +4879,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
         * The target for the ACTION_MEDIA_BUTTON events.
         * Always non null.
         */
        public PendingIntent mMediaIntent;
        final public PendingIntent mMediaIntent;
        /**
         * The registered media button event receiver.
         * Always non null.
         */
        public ComponentName mReceiverComponent;
        final public ComponentName mReceiverComponent;
        public String mCallingPackageName;
        public int mCallingUid;
        /**
@@ -5048,7 +5048,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
                //  evaluated it, traversal order doesn't matter here)
                while(stackIterator.hasNext()) {
                    RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
                    if (packageName.equalsIgnoreCase(rcse.mReceiverComponent.getPackageName())) {
                    if (packageName.equals(rcse.mMediaIntent.getCreatorPackage())) {
                        // a stack entry is from the package being removed, remove it from the stack
                        stackIterator.remove();
                        rcse.unlinkToRcClientDeath();
@@ -5061,10 +5061,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
                                    null));
                } else if (oldTop != mRCStack.peek()) {
                    // the top of the stack has changed, save it in the system settings
                    // by posting a message to persist it
                    // by posting a message to persist it; only do this however if it has
                    // a concrete component name (is not a transient registration)
                    RemoteControlStackEntry rcse = mRCStack.peek();
                    if (rcse.mReceiverComponent != null) {
                        mAudioHandler.sendMessage(
                                mAudioHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0,
                                    mRCStack.peek().mReceiverComponent));
                                        rcse.mReceiverComponent));
                    }
                }
            }
        }
@@ -5211,7 +5215,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
    /**
     * Update the displays and clients with the new "focused" client generation and name
     * @param newClientGeneration the new generation value matching a client update
     * @param newClientEventReceiver the media button event receiver associated with the client.
     * @param newMediaIntent the media button event receiver associated with the client.
     *    May be null, which implies there is no registered media button event receiver.
     * @param clearing true if the new client generation value maps to a remote control update
     *    where the display should be cleared.