diff options
author | Dianne Hackborn <hackbod@google.com> | 2013-03-22 23:13:49 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-03-22 23:13:49 +0000 |
commit | 1a89c5324badd10dac142a5a0c40a203503db65f (patch) | |
tree | 269f901041482278e146a8447a41437939ff3471 | |
parent | 5341f377ccdd71471894d23afaab4d7a75fd03ba (diff) | |
parent | 961cae92540763226648813d111c5b5c3b0f1597 (diff) | |
download | frameworks_base-1a89c5324badd10dac142a5a0c40a203503db65f.zip frameworks_base-1a89c5324badd10dac142a5a0c40a203503db65f.tar.gz frameworks_base-1a89c5324badd10dac142a5a0c40a203503db65f.tar.bz2 |
Merge "New media button API." into jb-mr2-dev
-rw-r--r-- | api/current.txt | 15 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 3 | ||||
-rw-r--r-- | core/java/android/view/ViewTreeObserver.java | 153 | ||||
-rw-r--r-- | media/java/android/media/AudioManager.java | 31 | ||||
-rw-r--r-- | media/java/android/media/AudioService.java | 46 |
5 files changed, 226 insertions, 22 deletions
diff --git a/api/current.txt b/api/current.txt index f4b4d53..8b77cc9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 69e2d1a..3ce4c13 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1233,6 +1233,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); @@ -2827,6 +2828,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mHardwareRenderer.isEnabled()) { mAttachInfo.mHardwareRenderer.validate(); } + mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); mView.dispatchDetachedFromWindow(); } @@ -3127,6 +3129,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, diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index cfcf3c0..072c95f 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -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; @@ -49,6 +51,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); @@ -324,6 +372,76 @@ public final class ViewTreeObserver { } /** + * 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. * * @param listener The callback to add @@ -621,6 +739,41 @@ public final class ViewTreeObserver { } /** + * 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. */ final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 135d2c8..6f284f8 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2047,11 +2047,28 @@ public class AudioManager { } /** + * 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; } @@ -2114,6 +2131,18 @@ public class AudioManager { } /** + * 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 */ public void unregisterMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) { diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 3e4d6f3..47c70f8 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -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 - mAudioHandler.sendMessage( - mAudioHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, - mRCStack.peek().mReceiverComponent)); + // 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, + 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. |