From f0cff0456258478ba768097f73d4367ab67fd7a3 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Wed, 14 Sep 2011 18:11:09 -0700 Subject: Bug 5300223 RemoteControlClient uses PendingIntent for media button events Update the implementation of the RemoteControlClient / Display feature rely on PendingIntent as provided in the construction of the RemoteControlClient instance. The ComponentName that describes the target of the media button events is set as the target of the Intent from which a PendingIntent is constructed. This ComponentName is still saved in the stack for persisting the last media button event receiver. This CL also updates the lockscreen IRemoteControlDisplay implementation to use the PendingIntent supplied by the application when sending transport control events. A (good) side effect of doing this is that intent will be directly targeted at the application. Restoration of the media button event receiver after reboot is not fully functional yet. Change-Id: I2be82f2839e9dee1de02512437b3fb41cc386cde --- .../internal/widget/TransportControlView.java | 27 +++- media/java/android/media/AudioManager.java | 62 ++++++-- media/java/android/media/AudioService.java | 160 +++++++++++++-------- media/java/android/media/IAudioService.aidl | 12 +- .../java/android/media/IRemoteControlDisplay.aidl | 9 +- media/java/android/media/RemoteControlClient.java | 62 ++------ 6 files changed, 186 insertions(+), 146 deletions(-) diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java index 29ad15bc18b3..f9c2cce6674e 100644 --- a/core/java/com/android/internal/widget/TransportControlView.java +++ b/core/java/com/android/internal/widget/TransportControlView.java @@ -21,6 +21,8 @@ import java.lang.ref.WeakReference; import com.android.internal.widget.LockScreenWidgetCallback; import com.android.internal.widget.LockScreenWidgetInterface; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -68,7 +70,7 @@ public class TransportControlView extends FrameLayout implements OnClickListener private int mClientGeneration; private Metadata mMetadata = new Metadata(); private boolean mAttached; - private ComponentName mClientName; + private PendingIntent mClientIntent; private int mTransportControlFlags; private int mPlayState; private AudioManager mAudioManager; @@ -116,7 +118,7 @@ public class TransportControlView extends FrameLayout implements OnClickListener } } mClientGeneration = msg.arg1; - mClientName = (ComponentName) msg.obj; + mClientIntent = (PendingIntent) msg.obj; break; } @@ -174,12 +176,12 @@ public class TransportControlView extends FrameLayout implements OnClickListener } } - public void setCurrentClientId(int clientGeneration, ComponentName clientEventReceiver, + public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent, boolean clearing) throws RemoteException { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_GENERATION_ID, - clientGeneration, (clearing ? 1 : 0), clientEventReceiver).sendToTarget(); + clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget(); } } }; @@ -365,16 +367,27 @@ public class TransportControlView extends FrameLayout implements OnClickListener } private void sendMediaButtonClick(int keyCode) { - // TODO: target to specific player based on mClientName + // use the registered PendingIntent that will be processed by the registered + // media button event receiver, which is the component of mClientIntent KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - getContext().sendOrderedBroadcast(intent, null); + try { + mClientIntent.send(getContext(), 0, intent); + } catch (CanceledException e) { + Log.e(TAG, "Error sending intent for media button down: "+e); + e.printStackTrace(); + } keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode); intent = new Intent(Intent.ACTION_MEDIA_BUTTON); intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - getContext().sendOrderedBroadcast(intent, null); + try { + mClientIntent.send(getContext(), 0, intent); + } catch (CanceledException e) { + Log.e(TAG, "Error sending intent for media button up: "+e); + e.printStackTrace(); + } } public void setCallback(LockScreenWidgetCallback callback) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index cd8bb1d4b54f..a0881a7ea44f 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -18,8 +18,10 @@ package android.media; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.database.ContentObserver; import android.graphics.Bitmap; import android.os.Binder; @@ -1684,17 +1686,42 @@ public class AudioManager { * Register a component to be the sole receiver of MEDIA_BUTTON intents. * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver} * that will receive the media button intent. This broadcast receiver must be declared - * in the application manifest. + * in the application manifest. The package of the component must match that of + * the context you're registering from. */ public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { if (eventReceiver == null) { return; } + if (!eventReceiver.getPackageName().equals(mContext.getPackageName())) { + Log.e(TAG, "registerMediaButtonEventReceiver() error: " + + "receiver and context package names don't match"); + return; + } + // construct a PendingIntent for the media button and register it + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + // the associated intent will be handled by the component being registered + mediaButtonIntent.setComponent(eventReceiver); + PendingIntent pi = PendingIntent.getBroadcast(mContext, + 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); + registerMediaButtonIntent(pi, eventReceiver); + } + + /** + * @hide + * no-op if (pi == null) or (eventReceiver == null) + */ + public void registerMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) { + if ((pi == null) || (eventReceiver == null)) { + Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter"); + return; + } IAudioService service = getService(); try { - service.registerMediaButtonEventReceiver(eventReceiver); + // pi != null + service.registerMediaButtonIntent(pi, eventReceiver); } catch (RemoteException e) { - Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e); + Log.e(TAG, "Dead object in registerMediaButtonIntent"+e); } } @@ -1707,15 +1734,27 @@ public class AudioManager { if (eventReceiver == null) { return; } + // construct a PendingIntent for the media button and unregister it + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + // the associated intent will be handled by the component being registered + mediaButtonIntent.setComponent(eventReceiver); + PendingIntent pi = PendingIntent.getBroadcast(mContext, + 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); + unregisterMediaButtonIntent(pi, eventReceiver); + } + + /** + * @hide + */ + public void unregisterMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) { IAudioService service = getService(); try { - service.unregisterMediaButtonEventReceiver(eventReceiver); + service.unregisterMediaButtonIntent(pi, eventReceiver); } catch (RemoteException e) { - Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e); + Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e); } } - /** * Registers the remote control client for providing information to display on the remote * controls. @@ -1724,14 +1763,13 @@ public class AudioManager { * @see RemoteControlClient */ public void registerRemoteControlClient(RemoteControlClient rcClient) { - if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) { + if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) { return; } IAudioService service = getService(); try { - service.registerRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */ + service.registerRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent */ rcClient.getIRemoteControlClient(), /* rcClient */ - rcClient.toString(), /* clientName */ // used to match media button event receiver and audio focus mContext.getPackageName()); /* packageName */ } catch (RemoteException e) { @@ -1746,13 +1784,13 @@ public class AudioManager { * @see #registerRemoteControlClient(RemoteControlClient) */ public void unregisterRemoteControlClient(RemoteControlClient rcClient) { - if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) { + if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) { return; } IAudioService service = getService(); try { - service.unregisterRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */ - rcClient.getIRemoteControlClient()); /* rcClient */ + service.unregisterRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent */ + rcClient.getIRemoteControlClient()); /* rcClient */ } catch (RemoteException e) { Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e); } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index db27cfd110d1..8895c9e7348c 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -18,6 +18,8 @@ package android.media; import android.app.ActivityManagerNative; import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -35,6 +37,7 @@ import android.database.ContentObserver; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.os.Binder; +import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.IBinder; @@ -2084,7 +2087,7 @@ public class AudioService extends IAudioService.Stub { } } - private void persistMediaButtonReceiver(ComponentName receiver) { + private void onHandlePersistMediaButtonReceiver(ComponentName receiver) { Settings.System.putString(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, receiver == null ? "" : receiver.flattenToString()); } @@ -2201,7 +2204,7 @@ public class AudioService extends IAudioService.Stub { break; case MSG_PERSIST_MEDIABUTTONRECEIVER: - persistMediaButtonReceiver( (ComponentName) msg.obj ); + onHandlePersistMediaButtonReceiver( (ComponentName) msg.obj ); break; case MSG_RCDISPLAY_CLEAR: @@ -2894,14 +2897,22 @@ public class AudioService extends IAudioService.Stub { } synchronized(mRCStack) { if (!mRCStack.empty()) { - // create a new intent specifically aimed at the current registered listener + // create a new intent to fill in the extras of the registered PendingIntent Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - targetedIntent.putExtras(intent.getExtras()); - targetedIntent.setComponent(mRCStack.peek().mReceiverComponent); - // trap the current broadcast - abortBroadcast(); - //Log.v(TAG, " Sending intent" + targetedIntent); - context.sendBroadcast(targetedIntent, null); + Bundle extras = intent.getExtras(); + if (extras != null) { + targetedIntent.putExtras(extras); + // trap the current broadcast + abortBroadcast(); + //Log.v(TAG, " Sending intent" + targetedIntent); + // send the intent that was registered by the client + try { + mRCStack.peek().mMediaIntent.send(context, 0, targetedIntent); + } catch (CanceledException e) { + Log.e(TAG, "Error sending pending intent " + mRCStack.peek()); + e.printStackTrace(); + } + } } } } @@ -2936,18 +2947,18 @@ public class AudioService extends IAudioService.Stub { */ private class RcClientDeathHandler implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death - private ComponentName mRcEventReceiver; + private PendingIntent mMediaIntent; - RcClientDeathHandler(IBinder cb, ComponentName eventReceiver) { + RcClientDeathHandler(IBinder cb, PendingIntent pi) { mCb = cb; - mRcEventReceiver = eventReceiver; + mMediaIntent = pi; } public void binderDied() { Log.w(TAG, " RemoteControlClient died"); // remote control client died, make sure the displays don't use it anymore // by setting its remote control client to null - registerRemoteControlClient(mRcEventReceiver, null, null, null/*ignored*/); + registerRemoteControlClient(mMediaIntent, null, null/*ignored*/); } public IBinder getBinder() { @@ -2956,18 +2967,29 @@ public class AudioService extends IAudioService.Stub { } private static class RemoteControlStackEntry { - /** the target for the ACTION_MEDIA_BUTTON events */ - public ComponentName mReceiverComponent;// always non null + /** + * The target for the ACTION_MEDIA_BUTTON events. + * Always non null. + */ + public PendingIntent mMediaIntent; + /** + * The registered media button event receiver. + * Always non null. + */ + public ComponentName mReceiverComponent; public String mCallingPackageName; - public String mRcClientName; public int mCallingUid; - - /** provides access to the information to display on the remote control */ + /** + * Provides access to the information to display on the remote control. + * May be null (when a media button event receiver is registered, + * but no remote control client has been registered) */ public IRemoteControlClient mRcClient; public RcClientDeathHandler mRcClientDeathHandler; - public RemoteControlStackEntry(ComponentName r) { - mReceiverComponent = r; + /** precondition: mediaIntent != null, eventReceiver != null */ + public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) { + mMediaIntent = mediaIntent; + mReceiverComponent = eventReceiver; mCallingUid = -1; mRcClient = null; } @@ -3003,7 +3025,8 @@ public class AudioService extends IAudioService.Stub { Iterator stackIterator = mRCStack.iterator(); while(stackIterator.hasNext()) { RemoteControlStackEntry rcse = stackIterator.next(); - pw.println(" receiver: " + rcse.mReceiverComponent + + pw.println(" pi: " + rcse.mMediaIntent + + " -- ercvr: " + rcse.mReceiverComponent + " -- client: " + rcse.mRcClient + " -- uid: " + rcse.mCallingUid); } @@ -3035,7 +3058,6 @@ public class AudioService extends IAudioService.Stub { mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, null)); - return; } 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 @@ -3049,25 +3071,32 @@ public class AudioService extends IAudioService.Stub { /** * Helper function: - * Restore remote control receiver from the system settings + * Restore remote control receiver from the system settings. */ private void restoreMediaButtonReceiver() { String receiverName = Settings.System.getString(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER); if ((null != receiverName) && !receiverName.isEmpty()) { - ComponentName receiverComponentName = ComponentName.unflattenFromString(receiverName); - registerMediaButtonEventReceiver(receiverComponentName); + ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); + // construct a PendingIntent targeted to the restored component name + // for the media button and register it + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + // the associated intent will be handled by the component being registered + mediaButtonIntent.setComponent(eventReceiver); + PendingIntent pi = PendingIntent.getBroadcast(mContext, + 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); + registerMediaButtonIntent(pi, eventReceiver); } - // upon restoring (e.g. after boot), do we want to refresh all remotes? } /** * Helper function: - * Set the new remote control receiver at the top of the RC focus stack + * Set the new remote control receiver at the top of the RC focus stack. + * precondition: mediaIntent != null, target != null */ - private void pushMediaButtonReceiver(ComponentName newReceiver) { + private void pushMediaButtonReceiver(PendingIntent mediaIntent, ComponentName target) { // already at top of stack? - if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) { + if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) { return; } Iterator stackIterator = mRCStack.iterator(); @@ -3075,31 +3104,32 @@ public class AudioService extends IAudioService.Stub { boolean wasInsideStack = false; while(stackIterator.hasNext()) { rcse = (RemoteControlStackEntry)stackIterator.next(); - if(rcse.mReceiverComponent.equals(newReceiver)) { + if(rcse.mMediaIntent.equals(mediaIntent)) { wasInsideStack = true; stackIterator.remove(); break; } } if (!wasInsideStack) { - rcse = new RemoteControlStackEntry(newReceiver); + rcse = new RemoteControlStackEntry(mediaIntent, target); } mRCStack.push(rcse); // post message to persist the default media button receiver mAudioHandler.sendMessage( mAudioHandler.obtainMessage( - MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, newReceiver/*obj*/) ); + MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) ); } /** * Helper function: - * Remove the remote control receiver from the RC focus stack + * Remove the remote control receiver from the RC focus stack. + * precondition: pi != null */ - private void removeMediaButtonReceiver(ComponentName newReceiver) { + private void removeMediaButtonReceiver(PendingIntent pi) { Iterator stackIterator = mRCStack.iterator(); while(stackIterator.hasNext()) { RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); - if(rcse.mReceiverComponent.equals(newReceiver)) { + if(rcse.mMediaIntent.equals(pi)) { stackIterator.remove(); break; } @@ -3110,8 +3140,8 @@ public class AudioService extends IAudioService.Stub { * Helper function: * Called synchronized on mRCStack */ - private boolean isCurrentRcController(ComponentName eventReceiver) { - if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(eventReceiver)) { + private boolean isCurrentRcController(PendingIntent pi) { + if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) { return true; } return false; @@ -3124,12 +3154,12 @@ public class AudioService extends IAudioService.Stub { * Update the remote control displays with the new "focused" client generation */ private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, - ComponentName newClientEventReceiver, boolean clearing) { + PendingIntent newMediaIntent, boolean clearing) { // NOTE: Only one IRemoteControlDisplay supported in this implementation if (mRcDisplay != null) { try { mRcDisplay.setCurrentClientId( - newClientGeneration, newClientEventReceiver, clearing); + newClientGeneration, newMediaIntent, clearing); } catch (RemoteException e) { Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc() "+e); // if we had a display before, stop monitoring its death @@ -3167,10 +3197,9 @@ public class AudioService extends IAudioService.Stub { * where the display should be cleared. */ private void setNewRcClient_syncRcsCurrc(int newClientGeneration, - ComponentName newClientEventReceiver, boolean clearing) { + PendingIntent newMediaIntent, boolean clearing) { // send the new valid client generation ID to all displays - setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newClientEventReceiver, - clearing); + setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing); // send the new valid client generation ID to all clients setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration); } @@ -3186,7 +3215,7 @@ public class AudioService extends IAudioService.Stub { mCurrentRcClientGen++; // synchronously update the displays and clients with the new client generation setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, - null /*event receiver*/, true /*clearing*/); + null /*newMediaIntent*/, true /*clearing*/); } } } @@ -3204,7 +3233,7 @@ public class AudioService extends IAudioService.Stub { // synchronously update the displays and clients with // the new client generation setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, - rcse.mReceiverComponent /*event receiver*/, + rcse.mMediaIntent /*newMediaIntent*/, false /*clearing*/); // tell the current client that it needs to send info @@ -3301,27 +3330,34 @@ public class AudioService extends IAudioService.Stub { updateRemoteControlDisplay_syncAfRcs(infoChangedFlags); } - /** see AudioManager.registerMediaButtonEventReceiver(ComponentName eventReceiver) */ - public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { - Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver); + /** + * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) + * precondition: mediaIntent != null, target != null + */ + public void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) { + Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); synchronized(mAudioFocusLock) { synchronized(mRCStack) { - pushMediaButtonReceiver(eventReceiver); + pushMediaButtonReceiver(mediaIntent, eventReceiver); // new RC client, assume every type of information shall be queried checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } } } - /** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */ - public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { - Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver); + /** + * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) + * precondition: mediaIntent != null, eventReceiver != null + */ + public void unregisterMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) + { + Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); synchronized(mAudioFocusLock) { synchronized(mRCStack) { - boolean topOfStackWillChange = isCurrentRcController(eventReceiver); - removeMediaButtonReceiver(eventReceiver); + boolean topOfStackWillChange = isCurrentRcController(mediaIntent); + removeMediaButtonReceiver(mediaIntent); if (topOfStackWillChange) { // current RC client will change, assume every type of info needs to be queried checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); @@ -3331,8 +3367,8 @@ public class AudioService extends IAudioService.Stub { } /** see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) */ - public void registerRemoteControlClient(ComponentName eventReceiver, - IRemoteControlClient rcClient, String clientName, String callingPackageName) { + public void registerRemoteControlClient(PendingIntent mediaIntent, + IRemoteControlClient rcClient, String callingPackageName) { if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); synchronized(mAudioFocusLock) { synchronized(mRCStack) { @@ -3340,7 +3376,7 @@ public class AudioService extends IAudioService.Stub { Iterator stackIterator = mRCStack.iterator(); while(stackIterator.hasNext()) { RemoteControlStackEntry rcse = stackIterator.next(); - if(rcse.mReceiverComponent.equals(eventReceiver)) { + if(rcse.mMediaIntent.equals(mediaIntent)) { // already had a remote control client? if (rcse.mRcClientDeathHandler != null) { // stop monitoring the old client's death @@ -3357,7 +3393,6 @@ public class AudioService extends IAudioService.Stub { } } rcse.mCallingPackageName = callingPackageName; - rcse.mRcClientName = clientName; rcse.mCallingUid = Binder.getCallingUid(); if (rcClient == null) { rcse.mRcClientDeathHandler = null; @@ -3366,7 +3401,7 @@ public class AudioService extends IAudioService.Stub { // monitor the new client's death IBinder b = rcClient.asBinder(); RcClientDeathHandler rcdh = - new RcClientDeathHandler(b, rcse.mReceiverComponent); + new RcClientDeathHandler(b, rcse.mMediaIntent); try { b.linkToDeath(rcdh, 0); } catch (RemoteException e) { @@ -3380,7 +3415,7 @@ public class AudioService extends IAudioService.Stub { } // if the eventReceiver is at the top of the stack // then check for potential refresh of the remote controls - if (isCurrentRcController(eventReceiver)) { + if (isCurrentRcController(mediaIntent)) { checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } } @@ -3388,24 +3423,23 @@ public class AudioService extends IAudioService.Stub { } /** - * see AudioManager.unregisterRemoteControlClient(ComponentName eventReceiver, ...) + * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...) * rcClient is guaranteed non-null */ - public void unregisterRemoteControlClient(ComponentName eventReceiver, + public void unregisterRemoteControlClient(PendingIntent mediaIntent, IRemoteControlClient rcClient) { synchronized(mAudioFocusLock) { synchronized(mRCStack) { Iterator stackIterator = mRCStack.iterator(); while(stackIterator.hasNext()) { RemoteControlStackEntry rcse = stackIterator.next(); - if ((rcse.mReceiverComponent.equals(eventReceiver)) + if ((rcse.mMediaIntent.equals(mediaIntent)) && rcClient.equals(rcse.mRcClient)) { // we found the IRemoteControlClient to unregister // stop monitoring its death rcse.unlinkToRcClientDeath(); // reset the client-related fields rcse.mRcClient = null; - rcse.mRcClientName = null; rcse.mRcClientDeathHandler = null; rcse.mCallingPackageName = null; } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 7bf981442b27..5294d36ae03b 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -16,6 +16,7 @@ package android.media; +import android.app.PendingIntent; import android.content.ComponentName; import android.media.IAudioFocusDispatcher; import android.media.IRemoteControlClient; @@ -85,13 +86,12 @@ interface IAudioService { void unregisterAudioFocusClient(String clientId); - void registerMediaButtonEventReceiver(in ComponentName eventReceiver); + oneway void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c); + oneway void unregisterMediaButtonIntent(in PendingIntent pi, in ComponentName c); - void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); - - oneway void registerRemoteControlClient(in ComponentName eventReceiver, - in IRemoteControlClient rcClient, in String clientName, in String callingPackageName); - oneway void unregisterRemoteControlClient(in ComponentName eventReceiver, + oneway void registerRemoteControlClient(in PendingIntent mediaIntent, + in IRemoteControlClient rcClient, in String callingPackageName); + oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, in IRemoteControlClient rcClient); oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd); diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl index fd50b7e07ef6..e15b07c3c897 100644 --- a/media/java/android/media/IRemoteControlDisplay.aidl +++ b/media/java/android/media/IRemoteControlDisplay.aidl @@ -16,6 +16,7 @@ package android.media; +import android.app.PendingIntent; import android.content.ComponentName; import android.graphics.Bitmap; import android.os.Bundle; @@ -31,14 +32,12 @@ oneway interface IRemoteControlDisplay /** * Sets the generation counter of the current client that is displayed on the remote control. * @param clientGeneration the new RemoteControlClient generation - * @param clientEventReceiver the media button event receiver associated with the client. - * May be null, which implies there is no registered media button event receiver. This - * parameter is supplied as an optimization so a display can directly target media button - * events to the client. + * @param clientMediaIntent the PendingIntent 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. */ - void setCurrentClientId(int clientGeneration, in ComponentName clientEventReceiver, + void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent, boolean clearing); void setPlaybackState(int generationId, int state); diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index cdebba0e68cd..5dea87f756fd 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -34,6 +34,7 @@ import android.util.Log; import java.lang.IllegalArgumentException; /** + * TODO javadoc update for ComponentName - PendingIntent change * RemoteControlClient enables exposing information meant to be consumed by remote controls * capable of displaying metadata, artwork and media transport control buttons. * A remote control client object is associated with a media button event receiver. This @@ -204,50 +205,6 @@ public class RemoteControlClient */ public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3; - /** - * @hide - * TODO remove after modifying known (internal) media apps using this API - * Class constructor. - * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have - * been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} - * before this new RemoteControlClient can itself be registered with - * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. - * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) - * @see AudioManager#registerRemoteControlClient(RemoteControlClient) - */ - public RemoteControlClient(ComponentName mediaButtonEventReceiver) { - mRcEventReceiver = mediaButtonEventReceiver; - - Looper looper; - if ((looper = Looper.myLooper()) != null) { - mEventHandler = new EventHandler(this, looper); - } else if ((looper = Looper.getMainLooper()) != null) { - mEventHandler = new EventHandler(this, looper); - } else { - mEventHandler = null; - Log.e(TAG, "RemoteControlClient() couldn't find main application thread"); - } - } - - /** - * @hide - * TODO remove after modifying known (internal) media apps using this API - * Class constructor for a remote control client whose internal event handling - * happens on a user-provided Looper. - * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have - * been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} - * before this new RemoteControlClient can itself be registered with - * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. - * @param looper The Looper running the event loop. - * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) - * @see AudioManager#registerRemoteControlClient(RemoteControlClient) - */ - public RemoteControlClient(ComponentName mediaButtonEventReceiver, Looper looper) { - mRcEventReceiver = mediaButtonEventReceiver; - - mEventHandler = new EventHandler(this, looper); - } - /** * Class constructor. * @param mediaButtonIntent The intent that will be sent for the media button events sent @@ -262,8 +219,7 @@ public class RemoteControlClient * @see AudioManager#registerRemoteControlClient(RemoteControlClient) */ public RemoteControlClient(PendingIntent mediaButtonIntent) { - // TODO implement using PendingIntent instead of ComponentName - mRcEventReceiver = null; + mRcMediaIntent = mediaButtonIntent; Looper looper; if ((looper = Looper.myLooper()) != null) { @@ -292,8 +248,7 @@ public class RemoteControlClient * @see AudioManager#registerRemoteControlClient(RemoteControlClient) */ public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) { - // TODO implement using PendingIntent instead of ComponentName - mRcEventReceiver = null; + mRcMediaIntent = mediaButtonIntent; mEventHandler = new EventHandler(this, looper); } @@ -614,9 +569,10 @@ public class RemoteControlClient private int mInternalClientGenId = -2; /** - * The media button event receiver associated with this remote control client + * The media button intent description associated with this remote control client + * (can / should include target component for intent handling) */ - private final ComponentName mRcEventReceiver; + private final PendingIntent mRcMediaIntent; /** * The remote control display to which this client will send information. @@ -626,10 +582,10 @@ public class RemoteControlClient /** * @hide - * Accessor to media button event receiver + * Accessor to media button intent description (includes target component) */ - public ComponentName getRcEventReceiver() { - return mRcEventReceiver; + public PendingIntent getRcMediaIntent() { + return mRcMediaIntent; } /** * @hide -- cgit v1.2.3-59-g8ed1b From 6d66708706a99168cd1a7d536b9173ab866a5ed8 Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Fri, 16 Sep 2011 17:04:40 -0700 Subject: Added "No recent apps" message on phones Change-Id: I00bf163369775b79377090b7a9dae0c94ce5d0ac --- .../res/layout-land/status_bar_recent_panel.xml | 6 ++++ .../res/layout-port/status_bar_recent_panel.xml | 6 ++++ .../res/layout/status_bar_no_recent_apps.xml | 36 ++++++++++++++++++++++ packages/SystemUI/res/values/strings.xml | 4 +++ .../com/android/systemui/recent/Choreographer.java | 21 ++++++++++++- .../android/systemui/recent/RecentsPanelView.java | 15 +++++++-- 6 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 packages/SystemUI/res/layout/status_bar_no_recent_apps.xml diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml index f84cc192bba3..2fe22ff53e28 100644 --- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml @@ -69,6 +69,12 @@ + + + + + + + + + + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index bad7e1fb1e8e..b9e6d78645b2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -41,6 +41,10 @@ App info + + No recent apps + No notifications diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java index 9749a1dcdef8..fbf00d2e3ff3 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java +++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java @@ -38,17 +38,20 @@ import android.view.View; View mRootView; View mScrimView; View mContentView; + View mNoRecentAppsView; AnimatorSet mContentAnim; Animator.AnimatorListener mListener; // the panel will start to appear this many px from the end final int HYPERSPACE_OFFRAMP = 200; - public Choreographer(View root, View scrim, View content, Animator.AnimatorListener listener) { + public Choreographer(View root, View scrim, View content, + View noRecentApps, Animator.AnimatorListener listener) { mRootView = root; mScrimView = scrim; mContentView = content; mListener = listener; + mNoRecentAppsView = noRecentApps; } void createAnimation(boolean appearing) { @@ -81,8 +84,24 @@ import android.view.View; : new android.view.animation.DecelerateInterpolator(1.0f)); glowAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION); + Animator noRecentAppsFadeAnim = null; + if (mNoRecentAppsView != null && // doesn't exist on large devices + mNoRecentAppsView.getVisibility() == View.VISIBLE) { + noRecentAppsFadeAnim = ObjectAnimator.ofFloat(mNoRecentAppsView, "alpha", + mContentView.getAlpha(), appearing ? 1.0f : 0.0f); + noRecentAppsFadeAnim.setInterpolator(appearing + ? new android.view.animation.AccelerateInterpolator(1.0f) + : new android.view.animation.DecelerateInterpolator(1.0f)); + noRecentAppsFadeAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION); + } + mContentAnim = new AnimatorSet(); final Builder builder = mContentAnim.play(glowAnim).with(posAnim); + + if (noRecentAppsFadeAnim != null) { + builder.with(noRecentAppsFadeAnim); + } + Drawable background = mScrimView.getBackground(); if (background != null) { Animator bgAnim = ObjectAnimator.ofInt(background, diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index 0621b2203a80..6fdc534b782d 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -83,6 +83,7 @@ public class RecentsPanelView extends RelativeLayout private int mIconDpi; private View mRecentsScrim; private View mRecentsGlowView; + private View mRecentsNoApps; private ViewGroup mRecentsContainer; private Bitmap mDefaultThumbnailBackground; @@ -373,8 +374,9 @@ public class RecentsPanelView extends RelativeLayout mRecentsGlowView = findViewById(R.id.recents_glow); - mRecentsScrim = (View) findViewById(R.id.recents_bg_protect); - mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this); + mRecentsScrim = findViewById(R.id.recents_bg_protect); + mRecentsNoApps = findViewById(R.id.recents_no_apps); + mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, mRecentsNoApps, this); mRecentsDismissButton = findViewById(R.id.recents_dismiss_button); mRecentsDismissButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { @@ -581,6 +583,9 @@ public class RecentsPanelView extends RelativeLayout mThumbnailLoader.cancel(false); mThumbnailLoader = null; } + if (mRecentsNoApps != null) { // doesn't exist on large devices + mRecentsNoApps.setVisibility(View.INVISIBLE); + } mActivityDescriptions = getRecentTasks(); for (ActivityDescription ad : mActivityDescriptions) { ad.setThumbnail(mDefaultThumbnailBackground); @@ -647,7 +652,11 @@ public class RecentsPanelView extends RelativeLayout } else { // Immediately hide this panel if (DEBUG) Log.v(TAG, "Nothing to show"); - hide(false); + if (mRecentsNoApps != null) { // doesn't exist on large devices + mRecentsNoApps.setVisibility(View.VISIBLE); + } else { + hide(false); + } } } -- cgit v1.2.3-59-g8ed1b From b04fe4e82abb073b4e5d82563b0882cea0dcc139 Mon Sep 17 00:00:00 2001 From: Fred Quintana Date: Fri, 16 Sep 2011 21:17:21 -0700 Subject: Continuation of the unified account chooser flow. - made the UI match the spec - added ability to force the account chooser to appear - added ability to pass in a description that will override the stock one - added ability to pass in requiredFeatures for addAccount - added ability to pass in an authTokenType for addAccount Bug: 5293377 Change-Id: I243c0fd6598c943b1f65753e1f5d3c86629f64f5 --- api/14.txt | 2 +- api/current.txt | 2 +- core/java/android/accounts/AccountManager.java | 29 ++++++- .../accounts/ChooseAccountTypeActivity.java | 14 ++-- .../accounts/ChooseTypeAndAccountActivity.java | 85 +++++++++++++++++---- core/res/AndroidManifest.xml | 9 +-- .../res/drawable-hdpi/ic_checkmark_holo_light.png | Bin 0 -> 924 bytes .../res/drawable-mdpi/ic_checkmark_holo_light.png | Bin 0 -> 658 bytes .../res/drawable-xhdpi/ic_checkmark_holo_light.png | Bin 0 -> 1159 bytes core/res/res/layout/choose_account_type.xml | 49 ++++++++++++ .../res/res/layout/choose_selected_account_row.xml | 46 +++++++++++ core/res/res/layout/choose_type_and_account.xml | 37 +++++++-- core/res/res/values/strings.xml | 11 ++- 13 files changed, 245 insertions(+), 39 deletions(-) create mode 100644 core/res/res/drawable-hdpi/ic_checkmark_holo_light.png create mode 100644 core/res/res/drawable-mdpi/ic_checkmark_holo_light.png create mode 100644 core/res/res/drawable-xhdpi/ic_checkmark_holo_light.png create mode 100644 core/res/res/layout/choose_account_type.xml create mode 100644 core/res/res/layout/choose_selected_account_row.xml diff --git a/api/14.txt b/api/14.txt index 0aa94b2d2e71..45bb8823753f 100644 --- a/api/14.txt +++ b/api/14.txt @@ -2072,7 +2072,7 @@ package android.accounts { method public java.lang.String getUserData(android.accounts.Account, java.lang.String); method public android.accounts.AccountManagerFuture hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback, android.os.Handler); method public void invalidateAuthToken(java.lang.String, java.lang.String); - method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList, java.lang.String[], android.os.Bundle); + method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle); method public java.lang.String peekAuthToken(android.accounts.Account, java.lang.String); method public android.accounts.AccountManagerFuture removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback, android.os.Handler); method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener); diff --git a/api/current.txt b/api/current.txt index 0aa94b2d2e71..45bb8823753f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2072,7 +2072,7 @@ package android.accounts { method public java.lang.String getUserData(android.accounts.Account, java.lang.String); method public android.accounts.AccountManagerFuture hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback, android.os.Handler); method public void invalidateAuthToken(java.lang.String, java.lang.String); - method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList, java.lang.String[], android.os.Bundle); + method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle); method public java.lang.String peekAuthToken(android.accounts.Account, java.lang.String); method public android.accounts.AccountManagerFuture removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback, android.os.Handler); method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener); diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 530ecf17d980..3d3a37355a98 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -1790,22 +1790,43 @@ public class AccountManager { * @param allowableAccountTypes an optional string array of account types. These are used * both to filter the shown accounts and to filter the list of account types that are shown * when adding an account. - * @param addAccountOptions This {@link Bundle} is passed as the addAccount options - * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow. + * @param alwaysPromptForAccount if set the account chooser screen is always shown, otherwise + * it is only shown when there is more than one account from which to choose + * @param descriptionOverrideText if set, this string is used as the description in the + * accounts chooser screen rather than the default + * @param addAccountAuthTokenType This {@link Bundle} is passed as the {@link #addAccount} + * authTokenType + * @param addAccountRequiredFeatures This {@link Bundle} is passed as the {@link #addAccount} + * requiredFeatures + * @param addAccountOptions This {@link Bundle} is passed as the {@link #addAccount} options + * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow. */ static public Intent newChooseAccountIntent(Account selectedAccount, ArrayList allowableAccounts, String[] allowableAccountTypes, + boolean alwaysPromptForAccount, + String descriptionOverrideText, + String addAccountAuthTokenType, + String[] addAccountRequiredFeatures, Bundle addAccountOptions) { Intent intent = new Intent(); intent.setClassName("android", "android.accounts.ChooseTypeAndAccountActivity"); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST, allowableAccounts); - intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST, - allowableAccountTypes != null ? Lists.newArrayList(allowableAccountTypes) : 0); + intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, + allowableAccountTypes); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE, addAccountOptions); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_SELECTED_ACCOUNT, selectedAccount); + intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, + alwaysPromptForAccount); + intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_DESCRIPTION_TEXT_OVERRIDE, + descriptionOverrideText); + intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING, + addAccountAuthTokenType); + intent.putExtra( + ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY, + addAccountRequiredFeatures); return intent; } diff --git a/core/java/android/accounts/ChooseAccountTypeActivity.java b/core/java/android/accounts/ChooseAccountTypeActivity.java index 836164cbc784..f53e6f3733e3 100644 --- a/core/java/android/accounts/ChooseAccountTypeActivity.java +++ b/core/java/android/accounts/ChooseAccountTypeActivity.java @@ -52,12 +52,12 @@ public class ChooseAccountTypeActivity extends Activity implements AccountManage @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.choose_account); + setContentView(R.layout.choose_account_type); // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes Set setOfAllowableAccountTypes = null; ArrayList validAccountTypes = getIntent().getStringArrayListExtra( - ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST); + ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); if (validAccountTypes != null) { setOfAllowableAccountTypes = new HashSet(validAccountTypes.size()); for (String type : validAccountTypes) { @@ -138,10 +138,14 @@ public class ChooseAccountTypeActivity extends Activity implements AccountManage protected void runAddAccountForAuthenticator(AuthInfo authInfo) { Log.d(TAG, "selected account type " + authInfo.name); - Bundle options = getIntent().getBundleExtra( + final Bundle options = getIntent().getBundleExtra( ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE); - AccountManager.get(this).addAccount(authInfo.desc.type, null, null, options, - this, this, null); + final String[] requiredFeatures = getIntent().getStringArrayExtra( + ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY); + final String authTokenType = getIntent().getStringExtra( + ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING); + AccountManager.get(this).addAccount(authInfo.desc.type, authTokenType, requiredFeatures, + options, this, this, null /* Handler */); } public void run(final AccountManagerFuture accountManagerFuture) { diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index a903399c5fc8..b4030b9cbfae 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -57,25 +58,68 @@ public class ChooseTypeAndAccountActivity extends Activity { * that match the types in this list, if this parameter is supplied. This list is also * used to filter the allowable account types if add account is selected. */ - public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST = "allowableAccountTypes"; + public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes"; /** - * This is passed as the options bundle in AccountManager.addAccount() if it is called. + * This is passed as the addAccountOptions parameter in AccountManager.addAccount() + * if it is called. */ public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions"; + /** + * This is passed as the requiredFeatures parameter in AccountManager.addAccount() + * if it is called. + */ + public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY = + "addAccountRequiredFeatures"; + + /** + * This is passed as the authTokenType string in AccountManager.addAccount() + * if it is called. + */ + public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType"; + /** * If set then the specified account is already "selected". */ public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount"; + /** + * If true then display the account selection list even if there is just + * one account to choose from. boolean. + */ + public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT = + "alwaysPromptForAccount"; + + /** + * If set then this string willb e used as the description rather than + * the default. + */ + public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = + "descriptionTextOverride"; + private ArrayList mAccountInfos; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.choose_type_and_account); + + // save some items we use frequently final AccountManager accountManager = AccountManager.get(this); + final Intent intent = getIntent(); + + // override the description text if supplied + final String descriptionOverride = + intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); + if (!TextUtils.isEmpty(descriptionOverride)) { + ((TextView)findViewById(R.id.description)).setText(descriptionOverride); + } + + // If the selected account matches one in the list we will place a + // checkmark next to it. + final Account selectedAccount = + (Account)intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT); // build an efficiently queryable map of account types to authenticator descriptions final HashMap typeToAuthDescription = @@ -87,7 +131,7 @@ public class ChooseTypeAndAccountActivity extends Activity { // Read the validAccounts, if present, and add them to the setOfAllowableAccounts Set setOfAllowableAccounts = null; final ArrayList validAccounts = - getIntent().getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST); + intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST); if (validAccounts != null) { setOfAllowableAccounts = new HashSet(validAccounts.size()); for (Parcelable parcelable : validAccounts) { @@ -98,7 +142,7 @@ public class ChooseTypeAndAccountActivity extends Activity { // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes Set setOfAllowableAccountTypes = null; final ArrayList validAccountTypes = - getIntent().getStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST); + intent.getStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); if (validAccountTypes != null) { setOfAllowableAccountTypes = new HashSet(validAccountTypes.size()); for (String type : validAccountTypes) { @@ -121,7 +165,8 @@ public class ChooseTypeAndAccountActivity extends Activity { continue; } mAccountInfos.add(new AccountInfo(account, - getDrawableForType(typeToAuthDescription, account.type))); + getDrawableForType(typeToAuthDescription, account.type), + account.equals(selectedAccount))); } // If there are no allowable accounts go directly to add account @@ -131,7 +176,8 @@ public class ChooseTypeAndAccountActivity extends Activity { } // if there is only one allowable account return it - if (mAccountInfos.size() == 1) { + if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false) + && mAccountInfos.size() == 1) { Account account = mAccountInfos.get(0).account; setResultAndFinish(account.name, account.type); return; @@ -143,7 +189,6 @@ public class ChooseTypeAndAccountActivity extends Activity { list.setAdapter(new AccountArrayAdapter(this, android.R.layout.simple_list_item_1, mAccountInfos)); list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - list.setTextFilterEnabled(false); list.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView parent, View v, int position, long id) { onListItemClick((ListView)parent, v, position, id); @@ -173,10 +218,12 @@ public class ChooseTypeAndAccountActivity extends Activity { return; } } + Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled"); setResult(Activity.RESULT_CANCELED); finish(); } + private Drawable getDrawableForType( final HashMap typeToAuthDescription, String accountType) { @@ -212,31 +259,40 @@ public class ChooseTypeAndAccountActivity extends Activity { bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName); bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType); setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); + Log.d(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: " + + "selected account " + accountName + ", " + accountType); finish(); } private void startChooseAccountTypeActivity() { final Intent intent = new Intent(this, ChooseAccountTypeActivity.class); - intent.putStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST, - getIntent().getStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST)); + intent.putStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, + getIntent().getStringArrayListExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY)); intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE, - getIntent().getBundleExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_ARRAYLIST)); + getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE)); + intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY, + getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY)); + intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING, + getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING)); startActivityForResult(intent, 0); } private static class AccountInfo { final Account account; final Drawable drawable; + private final boolean checked; - AccountInfo(Account account, Drawable drawable) { + AccountInfo(Account account, Drawable drawable, boolean checked) { this.account = account; this.drawable = drawable; + this.checked = checked; } } private static class ViewHolder { ImageView icon; TextView text; + ImageView checkmark; } private static class AccountArrayAdapter extends ArrayAdapter { @@ -256,10 +312,11 @@ public class ChooseTypeAndAccountActivity extends Activity { ViewHolder holder; if (convertView == null) { - convertView = mLayoutInflater.inflate(R.layout.choose_account_row, null); + convertView = mLayoutInflater.inflate(R.layout.choose_selected_account_row, null); holder = new ViewHolder(); holder.text = (TextView) convertView.findViewById(R.id.account_row_text); holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon); + holder.checkmark = (ImageView) convertView.findViewById(R.id.account_row_checkmark); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); @@ -267,7 +324,9 @@ public class ChooseTypeAndAccountActivity extends Activity { holder.text.setText(mInfos.get(position).account.name); holder.icon.setImageDrawable(mInfos.get(position).drawable); - + final int displayCheckmark = + mInfos.get(position).checked ? View.VISIBLE : View.INVISIBLE; + holder.checkmark.setVisibility(displayCheckmark); return convertView; } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 80741eb05afa..9755f227a63f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1533,19 +1533,14 @@ - - - - diff --git a/core/res/res/drawable-hdpi/ic_checkmark_holo_light.png b/core/res/res/drawable-hdpi/ic_checkmark_holo_light.png new file mode 100644 index 000000000000..2c6719b796bc Binary files /dev/null and b/core/res/res/drawable-hdpi/ic_checkmark_holo_light.png differ diff --git a/core/res/res/drawable-mdpi/ic_checkmark_holo_light.png b/core/res/res/drawable-mdpi/ic_checkmark_holo_light.png new file mode 100644 index 000000000000..744e964477bd Binary files /dev/null and b/core/res/res/drawable-mdpi/ic_checkmark_holo_light.png differ diff --git a/core/res/res/drawable-xhdpi/ic_checkmark_holo_light.png b/core/res/res/drawable-xhdpi/ic_checkmark_holo_light.png new file mode 100644 index 000000000000..1607f35857df Binary files /dev/null and b/core/res/res/drawable-xhdpi/ic_checkmark_holo_light.png differ diff --git a/core/res/res/layout/choose_account_type.xml b/core/res/res/layout/choose_account_type.xml new file mode 100644 index 000000000000..db96dc118b3a --- /dev/null +++ b/core/res/res/layout/choose_account_type.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + diff --git a/core/res/res/layout/choose_selected_account_row.xml b/core/res/res/layout/choose_selected_account_row.xml new file mode 100644 index 000000000000..d88750dcc28b --- /dev/null +++ b/core/res/res/layout/choose_selected_account_row.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/core/res/res/layout/choose_type_and_account.xml b/core/res/res/layout/choose_type_and_account.xml index 8be01b42b788..5a0512633402 100644 --- a/core/res/res/layout/choose_type_and_account.xml +++ b/core/res/res/layout/choose_type_and_account.xml @@ -24,21 +24,44 @@ android:paddingLeft="16dip" android:paddingRight="16dip"> - + + + + + +