diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 870c1b4a35582..486c0c25a7373 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -266,8 +266,12 @@ public final class MediaSession { * playback after the session has been stopped. If your app is started in * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via * the pending intent. + *

+ * The pending intent is recommended to be explicit to follow the security recommendation of + * {@link PendingIntent#getActivity}. * * @param mbr The {@link PendingIntent} to send the media button event to. + * @see PendingIntent#getActivity */ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { try { diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java new file mode 100644 index 0000000000000..58c2707a1f196 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -0,0 +1,360 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * Holds the media button receiver, and also provides helper methods around it. + */ +final class MediaButtonReceiverHolder { + public static final int COMPONENT_TYPE_INVALID = 0; + public static final int COMPONENT_TYPE_BROADCAST = 1; + public static final int COMPONENT_TYPE_ACTIVITY = 2; + public static final int COMPONENT_TYPE_SERVICE = 3; + + @IntDef(value = { + COMPONENT_TYPE_INVALID, + COMPONENT_TYPE_BROADCAST, + COMPONENT_TYPE_ACTIVITY, + COMPONENT_TYPE_SERVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComponentType {} + + private static final String TAG = "PendingIntentHolder"; + private static final boolean DEBUG_KEY_EVENT = MediaSessionService.DEBUG_KEY_EVENT; + private static final String COMPONENT_NAME_USER_ID_DELIM = ","; + + private final int mUserId; + private final PendingIntent mPendingIntent; + private final ComponentName mComponentName; + private final String mPackageName; + @ComponentType + private final int mComponentType; + + /** + * Unflatten from string which is previously flattened string via flattenToString(). + *

+ * It's used to store and restore media button receiver across the boot, by keeping the intent's + * component name to the persistent storage. + * + * @param mediaButtonReceiverInfo previously flattened string via flattenToString() + * @return new instance if the string was valid. {@code null} otherwise. + */ + public static MediaButtonReceiverHolder unflattenFromString( + Context context, String mediaButtonReceiverInfo) { + if (TextUtils.isEmpty(mediaButtonReceiverInfo)) { + return null; + } + String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); + if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { + return null; + } + ComponentName componentName = ComponentName.unflattenFromString(tokens[0]); + int userId = Integer.parseInt(tokens[1]); + // Guess component type if the OS version is updated from the older version. + int componentType = (tokens.length == 3) + ? Integer.parseInt(tokens[2]) + : getComponentType(context, componentName); + return new MediaButtonReceiverHolder(userId, null, componentName, componentType); + } + + /** + * Creates a new instance. + * + * @param context context + * @param userId userId + * @param pendingIntent pending intent + * @return Can be {@code null} if pending intent was null. + */ + public static MediaButtonReceiverHolder create(Context context, int userId, + PendingIntent pendingIntent) { + if (pendingIntent == null) { + return null; + } + ComponentName componentName = (pendingIntent != null && pendingIntent.getIntent() != null) + ? pendingIntent.getIntent().getComponent() : null; + if (componentName != null) { + // Explicit intent, where component name is in the PendingIntent. + return new MediaButtonReceiverHolder(userId, pendingIntent, componentName, + getComponentType(context, componentName)); + } + + // Implicit intent, where component name isn't in the PendingIntent. Try resolve. + PackageManager pm = context.getPackageManager(); + Intent intent = pendingIntent.getIntent(); + if ((componentName = resolveImplicitServiceIntent(pm, intent)) != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_SERVICE); + } else if ((componentName = resolveManifestDeclaredBroadcastReceiverIntent(pm, intent)) + != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_BROADCAST); + } else if ((componentName = resolveImplicitActivityIntent(pm, intent)) != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_ACTIVITY); + } + + // Failed to resolve target component for the pending intent. It's unlikely to be usable. + // However, the pending intent would be still used, just to follow the legacy behavior. + Log.w(TAG, "Unresolvable implicit intent is set, pi=" + pendingIntent); + String packageName = (pendingIntent != null && pendingIntent.getIntent() != null) + ? pendingIntent.getIntent().getPackage() : null; + return new MediaButtonReceiverHolder(userId, pendingIntent, + packageName != null ? packageName : ""); + } + + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, + ComponentName componentName, @ComponentType int componentType) { + mUserId = userId; + mPendingIntent = pendingIntent; + mComponentName = componentName; + mPackageName = componentName.getPackageName(); + mComponentType = componentType; + } + + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, String packageName) { + mUserId = userId; + mPendingIntent = pendingIntent; + mComponentName = null; + mPackageName = packageName; + mComponentType = COMPONENT_TYPE_INVALID; + } + + /** + * @return the user id + */ + public int getUserId() { + return mUserId; + } + + /** + * @return package name that the media button receiver would be sent to. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Sends the media key event to the media button receiver. + *

+ * This prioritizes using use pending intent for sending media key event. + * + * @param context context to be used to call PendingIntent#send + * @param keyEvent keyEvent to send + * @param resultCode result code to be used to call PendingIntent#send + * Ignored if there's no valid pending intent. + * @param onFinishedListener callback to be used to get result of PendingIntent#send. + * Ignored if there's no valid pending intent. + * @param handler handler to be used to call onFinishedListener + * Ignored if there's no valid pending intent. + * @see PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler) + */ + public boolean send(Context context, KeyEvent keyEvent, String callingPackageName, + int resultCode, PendingIntent.OnFinished onFinishedListener, Handler handler) { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + // TODO: Find a way to also send PID/UID in secure way. + mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName); + + if (mPendingIntent != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent " + + mPendingIntent); + } + try { + mPendingIntent.send( + context, resultCode, mediaButtonIntent, onFinishedListener, handler); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Error sending key event to media button receiver " + mPendingIntent, e); + return false; + } + } else if (mComponentName != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the restored intent " + + mComponentName + ", type=" + mComponentType); + } + mediaButtonIntent.setComponent(mComponentName); + UserHandle userHandle = UserHandle.of(mUserId); + try { + switch (mComponentType) { + case COMPONENT_TYPE_ACTIVITY: + context.startActivityAsUser(mediaButtonIntent, userHandle); + break; + case COMPONENT_TYPE_SERVICE: + context.startForegroundServiceAsUser(mediaButtonIntent, + userHandle); + break; + default: + // Legacy behavior for other cases. + context.sendBroadcastAsUser(mediaButtonIntent, userHandle); + } + } catch (Exception e) { + Log.w(TAG, "Error sending media button to the restored intent " + + mComponentName + ", type=" + mComponentType, e); + return false; + } + } else { + // Leave log, just in case. + Log.e(TAG, "Shouldn't be happen -- pending intent or component name must be set"); + return false; + } + return true; + } + + + @Override + public String toString() { + if (mPendingIntent != null) { + return "MBR {pi=" + mPendingIntent + ", type=" + mComponentType + "}"; + } + return "Restored MBR {component=" + mComponentName + ", type=" + mComponentType + "}"; + } + + /** + * @return flattened string. Can be empty string if the MBR is created with implicit intent. + */ + public String flattenToString() { + if (mComponentName == null) { + // We don't know which component would receive the key event. + return ""; + } + return String.join(COMPONENT_NAME_USER_ID_DELIM, + mComponentName.toString(), + String.valueOf(mUserId), + String.valueOf(mComponentType)); + } + + /** + * Gets the type of the component + * + * @param context context + * @param componentName component name + * @return A component type + */ + @ComponentType + private static int getComponentType(Context context, ComponentName componentName) { + if (componentName == null) { + return COMPONENT_TYPE_INVALID; + } + PackageManager pm = context.getPackageManager(); + try { + ActivityInfo activityInfo = pm.getActivityInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES); + if (activityInfo != null) { + return COMPONENT_TYPE_ACTIVITY; + } + } catch (PackageManager.NameNotFoundException e) { + } + try { + ServiceInfo serviceInfo = pm.getServiceInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES); + if (serviceInfo != null) { + return COMPONENT_TYPE_SERVICE; + } + } catch (PackageManager.NameNotFoundException e) { + } + // Pick legacy behavior for BroadcastReceiver or unknown. + return COMPONENT_TYPE_BROADCAST; + } + + private static ComponentName resolveImplicitServiceIntent(PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // filter apps regardless of the phone's locked/unlocked state. + // - GET_SERVICES: Return service + return createComponentName(pm.resolveService(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES)); + } + + private static ComponentName resolveManifestDeclaredBroadcastReceiverIntent( + PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // filter apps regardless of the phone's locked/unlocked state. + List resolveInfos = pm.queryBroadcastReceivers(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + return (resolveInfos != null && !resolveInfos.isEmpty()) + ? createComponentName(resolveInfos.get(0)) : null; + } + + private static ComponentName resolveImplicitActivityIntent(PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // Filter apps regardless of the phone's locked/unlocked state. + // - MATCH_DEFAULT_ONLY: + // Implicit intent receiver should be set as default. Only needed for activity. + // - GET_ACTIVITIES: Return activity + return createComponentName(pm.resolveActivity(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DEFAULT_ONLY + | PackageManager.GET_ACTIVITIES)); + } + + private static ComponentName createComponentName(ResolveInfo resolveInfo) { + if (resolveInfo == null) { + return null; + } + ComponentInfo componentInfo; + // Code borrowed from ResolveInfo#getComponentInfo(). + if (resolveInfo.activityInfo != null) { + componentInfo = resolveInfo.activityInfo; + } else if (resolveInfo.serviceInfo != null) { + componentInfo = resolveInfo.serviceInfo; + } else { + // We're not interested in content provider. + return null; + } + // Code borrowed from ComponentInfo#getComponentName(). + try { + return new ComponentName(componentInfo.packageName, componentInfo.name); + } catch (IllegalArgumentException | NullPointerException e) { + // This may be happen if resolveActivity() end up with matching multiple activities. + // see PackageManager#resolveActivity(). + return null; + } + } +} diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 7bcbcd4a3d09e..9f47b349d3fc0 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -127,7 +127,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR new ArrayList<>(); private long mFlags; - private PendingIntent mMediaButtonReceiver; + private MediaButtonReceiverHolder mMediaButtonReceiverHolder; private PendingIntent mLaunchIntent; // TransportPerformer fields @@ -220,8 +220,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR * * @return The pending intent set by the app or null. */ - public PendingIntent getMediaButtonReceiver() { - return mMediaButtonReceiver; + public MediaButtonReceiverHolder getMediaButtonReceiver() { + return mMediaButtonReceiverHolder; } /** @@ -471,7 +471,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR + ", userId=" + mUserId); pw.println(indent + "package=" + mPackageName); pw.println(indent + "launchIntent=" + mLaunchIntent); - pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver); + pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiverHolder); pw.println(indent + "active=" + mIsActive); pw.println(indent + "flags=" + mFlags); pw.println(indent + "rating type=" + mRatingType); @@ -833,12 +833,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { - if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) == 1) { - return; - } - mMediaButtonReceiver = pi; final long token = Binder.clearCallingIdentity(); try { + if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) + != 0) { + return; + } + mMediaButtonReceiverHolder = + MediaButtonReceiverHolder.create(mContext, mUserId, pi); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); } finally { Binder.restoreCallingIdentity(token); @@ -1529,5 +1531,4 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR msg.sendToTarget(); } } - } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 88b884efce83a..7ffac062b7f59 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -18,22 +18,17 @@ package com.android.server.media; import static android.os.UserHandle.USER_ALL; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.INotificationManager; import android.app.KeyguardManager; import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; -import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.media.AudioManager; @@ -99,7 +94,7 @@ public class MediaSessionService extends SystemService implements Monitor { private static final String TAG = "MediaSessionService"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); // Leave log for key event always. - private static final boolean DEBUG_KEY_EVENT = true; + static final boolean DEBUG_KEY_EVENT = true; private static final int WAKELOCK_TIMEOUT = 5000; private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000; @@ -760,12 +755,6 @@ public class MediaSessionService extends SystemService implements Monitor { *

The contents of this object is guarded by {@link #mLock}. */ final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener { - public static final int COMPONENT_TYPE_INVALID = 0; - public static final int COMPONENT_TYPE_BROADCAST = 1; - public static final int COMPONENT_TYPE_ACTIVITY = 2; - public static final int COMPONENT_TYPE_SERVICE = 3; - private static final String COMPONENT_NAME_USER_ID_DELIM = ","; - private final int mFullUserId; private final MediaSessionStack mPriorityStack; private final HashMap @@ -774,10 +763,7 @@ public class MediaSessionService extends SystemService implements Monitor { mOnMediaKeyEventSessionChangedListeners = new HashMap<>(); private final SparseIntArray mUidToSessionCount = new SparseIntArray(); - private PendingIntent mLastMediaButtonReceiver; - private ComponentName mRestoredMediaButtonReceiver; - private int mRestoredMediaButtonReceiverComponentType; - private int mRestoredMediaButtonReceiverUserId; + private MediaButtonReceiverHolder mLastMediaButtonReceiverHolder; private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener; private int mOnVolumeKeyLongPressListenerUid; @@ -794,21 +780,9 @@ public class MediaSessionService extends SystemService implements Monitor { // Restore the remembered media button receiver before the boot. String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId); - if (mediaButtonReceiverInfo == null) { - return; - } - String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); - if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { - return; - } - mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]); - mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]); - if (tokens.length == 3) { - mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]); - } else { - mRestoredMediaButtonReceiverComponentType = - getComponentType(mRestoredMediaButtonReceiver); - } + mLastMediaButtonReceiverHolder = + MediaButtonReceiverHolder.unflattenFromString( + mContext, mediaButtonReceiverInfo); } public void destroySessionsForUserLocked(int userId) { @@ -892,10 +866,7 @@ public class MediaSessionService extends SystemService implements Monitor { : mOnMediaKeyEventSessionChangedListeners.values()) { pw.println(indent + " from " + getCallingPackageName(cr.uid)); } - pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver); - pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver); - pw.println(indent + "Restored MediaButtonReceiverComponentType: " - + mRestoredMediaButtonReceiverComponentType); + pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiverHolder); mPriorityStack.dump(pw, indent); } @@ -924,25 +895,12 @@ public class MediaSessionService extends SystemService implements Monitor { return; } MediaSessionRecord sessionRecord = (MediaSessionRecord) record; - PendingIntent receiver = sessionRecord.getMediaButtonReceiver(); - mLastMediaButtonReceiver = receiver; - mRestoredMediaButtonReceiver = null; - mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID; - - String mediaButtonReceiverInfo = ""; - if (receiver != null) { - ComponentName component = receiver.getIntent().getComponent(); - if (component != null - && record.getPackageName().equals(component.getPackageName())) { - String componentName = component.flattenToString(); - int componentType = getComponentType(component); - mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM, - componentName, String.valueOf(record.getUserId()), - String.valueOf(componentType)); - } - } + mLastMediaButtonReceiverHolder = sessionRecord.getMediaButtonReceiver(); + String mediaButtonReceiverInfo = (mLastMediaButtonReceiverHolder == null) + ? "" : mLastMediaButtonReceiverHolder.flattenToString(); Settings.Secure.putStringForUser(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo, + Settings.System.MEDIA_BUTTON_RECEIVER, + mediaButtonReceiverInfo, mFullUserId); } @@ -958,15 +916,9 @@ public class MediaSessionService extends SystemService implements Monitor { } else { // TODO(jaewan): Implement } - } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { - callback.onMediaKeyEventSessionChanged( - mCurrentFullUserRecord.mLastMediaButtonReceiver - .getIntent().getComponent().getPackageName(), - null); - } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { - callback.onMediaKeyEventSessionChanged( - mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(), - null); + } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) { + String packageName = mLastMediaButtonReceiverHolder.getPackageName(); + callback.onMediaKeyEventSessionChanged(packageName, null); } } catch (RemoteException e) { Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e); @@ -985,35 +937,6 @@ public class MediaSessionService extends SystemService implements Monitor { ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession(); } - private int getComponentType(@Nullable ComponentName componentName) { - if (componentName == null) { - return COMPONENT_TYPE_INVALID; - } - PackageManager pm = mContext.getPackageManager(); - try { - ActivityInfo activityInfo = pm.getActivityInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_ACTIVITIES); - if (activityInfo != null) { - return COMPONENT_TYPE_ACTIVITY; - } - } catch (NameNotFoundException e) { - } - try { - ServiceInfo serviceInfo = pm.getServiceInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_SERVICES); - if (serviceInfo != null) { - return COMPONENT_TYPE_SERVICE; - } - } catch (NameNotFoundException e) { - } - // Pick legacy behavior for BroadcastReceiver or unknown. - return COMPONENT_TYPE_BROADCAST; - } - final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient { public final IOnMediaKeyEventDispatchedListener callback; public final int uid; @@ -2166,79 +2089,31 @@ public class MediaSessionService extends SystemService implements Monitor { } catch (RemoteException e) { Log.w(TAG, "Failed to send callback", e); } - } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null - || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { + } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) { if (needWakeLock) { mKeyEventReceiver.acquireWakeLockLocked(); } - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - // TODO: Find a way to also send PID/UID in secure way. - String callerPackageName = + String callingPackageName = (asSystemService) ? mContext.getPackageName() : packageName; - mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName); - try { - if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { - PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver; - if (DEBUG_KEY_EVENT) { - Log.d(TAG, "Sending " + keyEvent - + " to the last known PendingIntent " + receiver); - } - receiver.send(mContext, - needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, - mediaButtonIntent, mKeyEventReceiver, mHandler); - ComponentName componentName = mCurrentFullUserRecord - .mLastMediaButtonReceiver.getIntent().getComponent(); - if (componentName != null) { - for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr - : mCurrentFullUserRecord - .mOnMediaKeyEventDispatchedListeners.values()) { - cr.callback.onMediaKeyEventDispatched(keyEvent, - componentName.getPackageName(), null); - } - } - } else { - ComponentName receiver = - mCurrentFullUserRecord.mRestoredMediaButtonReceiver; - int componentType = mCurrentFullUserRecord - .mRestoredMediaButtonReceiverComponentType; - UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord - .mRestoredMediaButtonReceiverUserId); - if (DEBUG_KEY_EVENT) { - Log.d(TAG, "Sending " + keyEvent + " to the restored intent " - + receiver + ", type=" + componentType); - } - mediaButtonIntent.setComponent(receiver); + + MediaButtonReceiverHolder mediaButtonReceiverHolder = + mCurrentFullUserRecord.mLastMediaButtonReceiverHolder; + + boolean sent = mediaButtonReceiverHolder.send( + mContext, keyEvent, callingPackageName, + needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver, + mHandler); + if (sent) { + String pkgName = mediaButtonReceiverHolder.getPackageName(); + for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr + : mCurrentFullUserRecord + .mOnMediaKeyEventDispatchedListeners.values()) { try { - switch (componentType) { - case FullUserRecord.COMPONENT_TYPE_ACTIVITY: - mContext.startActivityAsUser(mediaButtonIntent, userHandle); - break; - case FullUserRecord.COMPONENT_TYPE_SERVICE: - mContext.startForegroundServiceAsUser(mediaButtonIntent, - userHandle); - break; - default: - // Legacy behavior for other cases. - mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle); - } - } catch (Exception e) { - Log.w(TAG, "Error sending media button to the restored intent " - + receiver + ", type=" + componentType, e); - } - for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr - : mCurrentFullUserRecord - .mOnMediaKeyEventDispatchedListeners.values()) { - cr.callback.onMediaKeyEventDispatched(keyEvent, - receiver.getPackageName(), null); + cr.callback.onMediaKeyEventDispatched(keyEvent, pkgName, null); + } catch (RemoteException e) { + Log.w(TAG, "Failed notify key event dispatch, uid=" + cr.uid, e); } } - } catch (CanceledException e) { - Log.i(TAG, "Error sending key event to media button receiver " - + mCurrentFullUserRecord.mLastMediaButtonReceiver, e); - } catch (RemoteException e) { - Log.w(TAG, "Failed to send callback", e); } } }