MediaSessionService: Fix crash with implicit media button receiver
PendingIntent can be built with implicit intent (i.e. intent without ComponentName), while using it cause exception. However, apps can still create and set media button receiver with implicit intent. Prevent crash when MediaSessionService is notify about such app's media button receiver. Bug: 147536687 Test: Build and run Change-Id: Icfdd45edaa8e25dbdeca3c2180bd234718eef202
This commit is contained in:
@@ -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.
|
||||
* <p>
|
||||
* 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 {
|
||||
|
||||
@@ -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().
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<ResolveInfo> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
* <p>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<IBinder, OnMediaKeyEventDispatchedListenerRecord>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user