Android11实现能同时开多个录屏应用(或者共享屏幕或投屏时录屏)
1.概述
Android原生对MediaProjection的管理逻辑,是如果服务端已经保存有MediaProjection的实例,那么再次创建的时候,之前的MediaProjection实例就会被暂停,并且引用指向新的实例,也就导致了当开启后一个录屏应用时,前一个录屏应用会被暂停。为了能实现多个录屏应用同时录屏(或者共享屏幕或投屏的时候录屏),我们需要对framework进行修改,只需对一个文件进行修改:frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
2.实现能同时开启多个MediaProjection
大致的思路就是修改MediaProjectionManagerService.java,使得MediaProjectionManagerService服务端能够保存多个MediaProjeciton实例。
/** Copyright (C) 2014 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.projection;import android.Manifest;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IProcessObserver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionManager;
import android.media.projection.IMediaProjectionWatcherCallback;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** Manages MediaProjection sessions.** The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,* as well as the capabilities they grant. Any service using MediaProjection tokens as permission* grants <b>must</b> validate the token before use by calling {@link* IMediaProjectionService#isValidMediaProjection}.*/
public final class MediaProjectionManagerService extends SystemServiceimplements Watchdog.Monitor {private static final boolean REQUIRE_FG_SERVICE_FOR_PROJECTION = true;private static final String TAG = "MediaProjectionManagerService";private static boolean DEBUG = false;private final Object mLock = new Object(); // Protects the list of media projectionsprivate final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;private final CallbackDelegate mCallbackDelegate;private final Context mContext;private final AppOpsManager mAppOps;private final ActivityManagerInternal mActivityManagerInternal;private final PackageManager mPackageManager;private final MediaRouter mMediaRouter;private final MediaRouterCallback mMediaRouterCallback;private MediaRouter.RouteInfo mMediaRouteInfo;private Map<IBinder, MediaProjectionEntry> mEntryMap;public MediaProjectionManagerService(Context context) {super(context);mContext = context;mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();mCallbackDelegate = new CallbackDelegate();mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);mPackageManager = mContext.getPackageManager();mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);mMediaRouterCallback = new MediaRouterCallback();mEntryMap = new ArrayMap<IBinder, MediaProjectionEntry>();if ("userdebug".equals(SystemProperties.get("ro.build.type", "user"))) {DEBUG = true;}Watchdog.getInstance().addMonitor(this);}@Overridepublic void onStart() {logd("onStart");publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),false /*allowIsolated*/);mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);if (REQUIRE_FG_SERVICE_FOR_PROJECTION) {mActivityManagerInternal.registerProcessObserver(new IProcessObserver.Stub() {@Overridepublic void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {}@Overridepublic void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,serviceTypes);}@Overridepublic void onProcessDied(int pid, int uid) {}});}}@Overridepublic void onSwitchUser(int userId) {logd("onSwitchUser: userId=" + userId);mMediaRouter.rebindAsUser(userId);synchronized (mLock) {if (mEntryMap != null && !mEntryMap.isEmpty()) {for (MediaProjectionEntry entry : mEntryMap.values()) {if (entry != null) {entry.getProjectionGrant().stop();}}}}}@Overridepublic void monitor() {synchronized (mLock) { /* check for deadlock */ }}/*** Called when the set of active foreground service types for a given {@code uid / pid} changes.* We will stop the active projection grant if its owner targets {@code Q} or higher and has no* started foreground services of type {@code FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.*/private void handleForegroundServicesChanged(int pid, int uid, int serviceTypes) {synchronized (mLock) {logd("handleForegroundServicesChanged: pid=" + pid + ", uid=" + uid+ ", serviceTypes=" + serviceTypes);if (mEntryMap == null || mEntryMap.isEmpty()) {return;}String uidpid = String.format("%s_%s", uid, pid);MediaProjectionEntry entry = getMediaProjectionEntryByUidPid(uidpid);if (entry == null || entry.getProjectionGrant() == null) {return;}MediaProjection mediaProjection = entry.getProjectionGrant();if (!mediaProjection.requiresForegroundService()) {return;}if ((serviceTypes & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION) != 0) {return;}mediaProjection.stop();logd("handleForegroundServicesChanged: MediaProjection="+ mediaProjection.getProjectionInfo() + " hashCode "+ mediaProjection.asBinder().hashCode() + " stoped");}}private void startProjectionLocked(final String uidpid, final MediaProjection projection) {logd("startProjectionLocked: calling uidpid " + uidpid+ ", MediaProjection=" + projection.getProjectionInfo()+ " hashCode=" + projection.asBinder().hashCode()+ ", current MediaProjection count " + mEntryMap.size());if (mMediaRouteInfo != null) {mMediaRouter.getFallbackRoute().select();}List<IBinder> removedMediaProjections = new ArrayList<>();logd(">>> MediaProjections:");if (mEntryMap.isEmpty()) {logd(">>> null");} else {for (MediaProjectionEntry entry : mEntryMap.values()) {if (entry != null) {logd(">>> " + entry.getProjectionGrant().getProjectionInfo()+ " hashCode " + entry.getProjectionToken().hashCode());MediaProjection mp = entry.getProjectionGrant();if (mp.packageName.equals(projection.packageName)) {removedMediaProjections.add(entry.getProjectionToken());}} else {logd(">>> null");}}}for (IBinder token : removedMediaProjections) {MediaProjectionEntry entry = mEntryMap.get(token);if (entry != null) {MediaProjection mp = entry.getProjectionGrant();logd(mp.packageName + " already existing MediaProjection="+ mp.getProjectionInfo() + " hashCode " + mp.asBinder().hashCode()+ ", remove it");mp.stop();}}MediaProjectionEntry entry = new MediaProjectionEntry(uidpid, projection.asBinder(),projection);mEntryMap.put(projection.asBinder(), entry);dispatchStart(projection);logd("startProjectionLocked: start success, "+ "current MediaProjection count " + mEntryMap.size());}private void stopProjectionLocked(final MediaProjection projection) {logd("stopProjectionLocked: MediaProjection=" + projection.getProjectionInfo()+ " hashCode " + projection.asBinder().hashCode());mEntryMap.remove(projection.asBinder());dispatchStop(projection);logd("stopProjectionLocked: stop success, "+ "remaining MediaProjection count " + mEntryMap.size());}private void addCallback(final IMediaProjectionWatcherCallback callback) {logd("addCallback: callback=" + callback);IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {@Overridepublic void binderDied() {removeCallback(callback);}};synchronized (mLock) {mCallbackDelegate.add(callback);linkDeathRecipientLocked(callback, deathRecipient);}}private void removeCallback(IMediaProjectionWatcherCallback callback) {synchronized (mLock) {logd("removeCallback: callback=" + callback);unlinkDeathRecipientLocked(callback);mCallbackDelegate.remove(callback);}}private void linkDeathRecipientLocked(IMediaProjectionWatcherCallback callback,IBinder.DeathRecipient deathRecipient) {try {logd("linkDeathRecipientLocked: callback=" + callback+ ", deathRecipient=" + deathRecipient);final IBinder token = callback.asBinder();token.linkToDeath(deathRecipient, 0);mDeathEaters.put(token, deathRecipient);} catch (RemoteException e) {Slog.e(TAG, "Unable to link to death for media projection monitoring callback", e);}}private void unlinkDeathRecipientLocked(IMediaProjectionWatcherCallback callback) {logd("unlinkDeathRecipientLocked: callback=" + callback);final IBinder token = callback.asBinder();IBinder.DeathRecipient deathRecipient = mDeathEaters.remove(token);if (deathRecipient != null) {token.unlinkToDeath(deathRecipient, 0);}}private void dispatchStart(MediaProjection projection) {logd("dispatchStart: MediaProjection=" + projection.getProjectionInfo()+ " hashCode " + projection.asBinder().hashCode());mCallbackDelegate.dispatchStart(projection);}private void dispatchStop(MediaProjection projection) {logd("dispatchStop: MediaProjection=" + projection.getProjectionInfo()+ " hashCode " + projection.asBinder().hashCode());mCallbackDelegate.dispatchStop(projection);}private boolean isValidMediaProjection(IBinder token) {synchronized (mLock) {logd("isValidMediaProjection: MediaProjection hashCode " + token.hashCode());if (mEntryMap == null && mEntryMap.isEmpty()) {return false;}for (MediaProjectionEntry entry : mEntryMap.values()) {if (entry != null && entry.getProjectionToken().equals(token)) {logd("isValidMediaProjection: MediaProjection hashCode "+ token.hashCode() + " is valid");return true;}}logd("isValidMediaProjection: MediaProjection hashCode "+ token.hashCode() + " is invalid");return false;}}private MediaProjectionInfo getActiveProjectionInfo(String uidpid) {synchronized (mLock) {if (mEntryMap == null || mEntryMap.isEmpty()) {return null;}MediaProjectionEntry entry = getMediaProjectionEntryByUidPid(uidpid);if (entry == null) {return null;}logd("getActiveProjectionInfo: MediaProjection="+ entry.getProjectionGrant().getProjectionInfo()+ " hashCode " + entry.getProjectionToken().hashCode());return entry.getProjectionGrant().getProjectionInfo();}}private void dump(final PrintWriter pw) {pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");synchronized (mLock) {pw.println("Media Projection: ");if (mEntryMap != null && !mEntryMap.isEmpty()) {for (MediaProjectionEntry entry : mEntryMap.values()) {if (entry != null) {entry.dump(pw);}}} else {pw.println("null");}}}private final class BinderService extends IMediaProjectionManager.Stub {@Override // Binder callpublic boolean hasProjectionPermission(int uid, String packageName) {logd("BinderService hasProjectionPermission: uid=" + uid+ ", packageName=" + packageName);long token = Binder.clearCallingIdentity();boolean hasPermission = false;try {hasPermission |= checkPermission(packageName,android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)|| mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)== AppOpsManager.MODE_ALLOWED;} finally {Binder.restoreCallingIdentity(token);}return hasPermission;}@Override // Binder callpublic IMediaProjection createProjection(int uid, String packageName, int type,boolean isPermanentGrant) {final String uidpid = getCallingUidPid();logd("BinderService createProjection: uid=" + uid+ ", packageName=" + packageName + ", " + "type=" + type+ ", isPermanentGrant=" + isPermanentGrant + ", calling uidpid " + uidpid);if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)!= PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "+ "projection permission");}if (packageName == null || packageName.isEmpty()) {throw new IllegalArgumentException("package name must not be empty");}final UserHandle callingUser = Binder.getCallingUserHandle();long callingToken = Binder.clearCallingIdentity();MediaProjection projection;try {ApplicationInfo ai;try {ai = mPackageManager.getApplicationInfoAsUser(packageName, 0, callingUser);} catch (NameNotFoundException e) {throw new IllegalArgumentException("No package matching :" + packageName);}projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,ai.isPrivilegedApp());if (isPermanentGrant) {mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);}} finally {Binder.restoreCallingIdentity(callingToken);}return projection;}@Override // Binder callpublic boolean isValidMediaProjection(IMediaProjection projection) {return MediaProjectionManagerService.this.isValidMediaProjection(projection.asBinder());}@Override // Binder callpublic MediaProjectionInfo getActiveProjectionInfo() {final String uidpid = getCallingUidPid();logd("BinderService getActiveProjectionInfo: calling uidpid " + uidpid);if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)!= PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "+ "projection callbacks");}final long token = Binder.clearCallingIdentity();try {return MediaProjectionManagerService.this.getActiveProjectionInfo(uidpid);} finally {Binder.restoreCallingIdentity(token);}}@Override // Binder callpublic void stopActiveProjection() {final String uidpid = getCallingUidPid();logd("BinderService stopActiveProjection: calling uidpid " + uidpid);if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)!= PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "+ "projection callbacks");}final long token = Binder.clearCallingIdentity();try {MediaProjectionEntry entry = getMediaProjectionEntryByUidPid(uidpid);if (entry != null) {logd("BinderService stopActiveProjection: MediaProjection="+ entry.getProjectionGrant().getProjectionInfo()+ " hashCode " + entry.getProjectionToken().hashCode());entry.getProjectionGrant().stop();}} finally {Binder.restoreCallingIdentity(token);}}@Override //Binder callpublic void addCallback(final IMediaProjectionWatcherCallback callback) {logd("BinderService addCallback: callback=" + callback);if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)!= PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "+ "projection callbacks");}final long token = Binder.clearCallingIdentity();try {MediaProjectionManagerService.this.addCallback(callback);} finally {Binder.restoreCallingIdentity(token);}}@Overridepublic void removeCallback(IMediaProjectionWatcherCallback callback) {logd("BinderService removeCallback: callback=" + callback);if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)!= PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove "+ "projection callbacks");}final long token = Binder.clearCallingIdentity();try {MediaProjectionManagerService.this.removeCallback(callback);} finally {Binder.restoreCallingIdentity(token);}}@Override // Binder callpublic void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;final long token = Binder.clearCallingIdentity();try {MediaProjectionManagerService.this.dump(pw);} finally {Binder.restoreCallingIdentity(token);}}private boolean checkPermission(String packageName, String permission) {return mContext.getPackageManager().checkPermission(permission, packageName)== PackageManager.PERMISSION_GRANTED;}}private final class MediaProjection extends IMediaProjection.Stub {public final int uid;public final String packageName;public final UserHandle userHandle;private final int mTargetSdkVersion;private final boolean mIsPrivileged;private final int mType;private IMediaProjectionCallback mCallback;private IBinder mToken;private IBinder.DeathRecipient mDeathEater;private boolean mRestoreSystemAlertWindow;MediaProjection(int type, int uid, String packageName, int targetSdkVersion,boolean isPrivileged) {mType = type;this.uid = uid;this.packageName = packageName;userHandle = new UserHandle(UserHandle.getUserId(uid));mTargetSdkVersion = targetSdkVersion;mIsPrivileged = isPrivileged;}@Override // Binder callpublic boolean canProjectVideo() {return mType == MediaProjectionManager.TYPE_MIRRORING ||mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;}@Override // Binder callpublic boolean canProjectSecureVideo() {return false;}@Override // Binder callpublic boolean canProjectAudio() {return mType == MediaProjectionManager.TYPE_MIRRORING|| mType == MediaProjectionManager.TYPE_PRESENTATION|| mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;}@Override // Binder callpublic int applyVirtualDisplayFlags(int flags) {if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR| DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;return flags;} else if (mType == MediaProjectionManager.TYPE_MIRRORING) {flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;return flags;} else if (mType == MediaProjectionManager.TYPE_PRESENTATION) {flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;return flags;} else {throw new RuntimeException("Unknown MediaProjection type");}}@Override // Binder callpublic void start(final IMediaProjectionCallback callback) {if (callback == null) {throw new IllegalArgumentException("callback must not be null");}synchronized (mLock) {final String uidpid = getCallingUidPid();logd("MediaProjection start: calling uidpid " + uidpid+ ", callback=" + callback);if (isValidMediaProjection(asBinder())) {Slog.w(TAG, "UID " + Binder.getCallingUid()+ " attempted to start already started MediaProjection");return;}if (REQUIRE_FG_SERVICE_FOR_PROJECTION&& requiresForegroundService()&& !mActivityManagerInternal.hasRunningForegroundService(uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)) {throw new SecurityException("Media projections require a foreground service"+ " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION");}mCallback = callback;registerCallback(mCallback);try {mToken = callback.asBinder();mDeathEater = new IBinder.DeathRecipient() {@Overridepublic void binderDied() {mCallbackDelegate.remove(asBinder(), callback);stop();}};mToken.linkToDeath(mDeathEater, 0);} catch (RemoteException e) {Slog.w(TAG, "MediaProjectionCallbacks must be valid, aborting " +"MediaProjection", e);return;}if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {final long token = Binder.clearCallingIdentity();try {// We allow an app running a current screen capture session to use// SYSTEM_ALERT_WINDOW for the duration of the session, to enable// them to overlay their UX on top of what is being captured.// We only do this if the app requests the permission, and the appop// is in its default state (the user has neither explicitly allowed nor// disallowed it).final PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS,UserHandle.getUserId(uid));if (ArrayUtils.contains(packageInfo.requestedPermissions,Manifest.permission.SYSTEM_ALERT_WINDOW)) {final int currentMode = mAppOps.unsafeCheckOpRawNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName);if (currentMode == AppOpsManager.MODE_DEFAULT) {mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid,packageName, AppOpsManager.MODE_ALLOWED);mRestoreSystemAlertWindow = true;}}} catch (PackageManager.NameNotFoundException e) {Slog.w(TAG, "Package not found, aborting MediaProjection", e);return;} finally {Binder.restoreCallingIdentity(token);}}startProjectionLocked(uidpid, this);}}@Override // Binder callpublic void stop() {synchronized (mLock) {logd("MediaProjection stop: calling uidpid " + getCallingUidPid());if (!isValidMediaProjection(asBinder())) {Slog.w(TAG, "Attempted to stop inactive MediaProjection "+ "(uid=" + Binder.getCallingUid() + ", "+ "pid=" + Binder.getCallingPid() + ")");return;}if (mRestoreSystemAlertWindow) {final long token = Binder.clearCallingIdentity();try {// Put the appop back how it was, unless it has been changed from what// we set it to.// Note that WindowManager takes care of removing any existing overlay// windows when we do this.final int currentMode = mAppOps.unsafeCheckOpRawNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName);if (currentMode == AppOpsManager.MODE_ALLOWED) {mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName,AppOpsManager.MODE_DEFAULT);}mRestoreSystemAlertWindow = false;} finally {Binder.restoreCallingIdentity(token);}}stopProjectionLocked(this);mToken.unlinkToDeath(mDeathEater, 0);mToken = null;unregisterCallbacks();mCallback = null;}}@Overridepublic void registerCallback(IMediaProjectionCallback callback) {if (callback == null) {throw new IllegalArgumentException("callback must not be null");}logd("MediaProjection registerCallback: MediaProjection(hashCode "+ asBinder().hashCode() + ") callback=" + callback);mCallbackDelegate.add(asBinder(), callback);}@Overridepublic void unregisterCallback(IMediaProjectionCallback callback) {if (callback == null) {throw new IllegalArgumentException("callback must not be null");}logd("MediaProjection unregisterCallback: MediaProjection(hashCode "+ asBinder().hashCode() + ") callback=" + callback);mCallbackDelegate.remove(asBinder(), callback);}private void unregisterCallbacks() {logd("MediaProjection unregister callbacks for MediaProjection(hashCode "+ asBinder().hashCode() + ") because media projection stopped");mCallbackDelegate.remove(asBinder());}public MediaProjectionInfo getProjectionInfo() {return new MediaProjectionInfo(packageName, userHandle);}boolean requiresForegroundService() {return mTargetSdkVersion >= Build.VERSION_CODES.Q && !mIsPrivileged;}public void dump(PrintWriter pw) {pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType));}}private class MediaRouterCallback extends MediaRouter.SimpleCallback {@Overridepublic void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) {synchronized (mLock) {logd("MediaRouterCallback onRouteSelected: router=" + router + ", type="+ type + ", info=" + info + ", calling uidpid " + getCallingUidPid());if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {mMediaRouteInfo = info;for (MediaProjectionEntry entry : mEntryMap.values()) {if (entry != null) {entry.getProjectionGrant().stop();}}}}}@Overridepublic void onRouteUnselected(MediaRouter route, int type, MediaRouter.RouteInfo info) {logd("MediaRouterCallback onRouteUnselected: router=" + route+ ", type=" + type + ", info=" + info);if (mMediaRouteInfo == info) {mMediaRouteInfo = null;}}}private static class CallbackDelegate {private Map<IBinder, Map<IBinder, IMediaProjectionCallback>> mClientCallbacks;private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks;private Handler mHandler;private Object mLock = new Object();public CallbackDelegate() {mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);mClientCallbacks = new ArrayMap<IBinder, Map<IBinder, IMediaProjectionCallback>>();mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();}public void add(IBinder token, IMediaProjectionCallback callback) {synchronized (mLock) {if (mClientCallbacks.get(token) == null) {mClientCallbacks.put(token, new ArrayMap<IBinder, IMediaProjectionCallback>());logd("CallbackDelegate add: create callback container for "+ "MediaProjection(hashCode " + token.hashCode() + ")");}Map<IBinder, IMediaProjectionCallback> callbacks = mClientCallbacks.get(token);callbacks.put(callback.asBinder(), callback);logd("CallbackDelegate add callback: MediaProjection(hashCode "+ token.hashCode() + ") callback=" + callback+ ", remaining IMediaProjectionCallback for MediaProjection(hashCode "+ token.hashCode() + ") count " + callbacks.size()+ ", remaining IMediaProjectionCallback count "+ mClientCallbacks.size());}}public void add(IMediaProjectionWatcherCallback callback) {synchronized (mLock) {mWatcherCallbacks.put(callback.asBinder(), callback);logd("CallbackDelegate add callback " + callback.hashCode()+ ", remaining IMediaProjectionWatcherCallback count "+ mWatcherCallbacks.size());}}public void remove(IBinder token, IMediaProjectionCallback callback) {synchronized (mLock) {Map<IBinder, IMediaProjectionCallback> callbacks = mClientCallbacks.get(token);if (callbacks != null) {callbacks.remove(callback.asBinder());logd("CallbackDelegate remove callback: MediaProjection(hashCode "+ token.hashCode() + ") callback=" + callback+ ", remaining IMediaProjectionCallback for MediaProjection(hashCode "+ token.hashCode() + ") count " + callbacks.size()+ ", remaining IMediaProjectionCallback count "+ mClientCallbacks.size());if (callbacks.isEmpty()) {mClientCallbacks.remove(token);}}}}public void remove(IBinder token) {synchronized (mLock) {mClientCallbacks.remove(token);logd("CallbackDelegate remove all callbacks for MediaProjection(hashCode "+ token.hashCode() + ")");}}public void remove(IMediaProjectionWatcherCallback callback) {synchronized (mLock) {mWatcherCallbacks.remove(callback.asBinder());logd("CallbackDelegate remove callback " + callback.hashCode()+ ", remaining IMediaProjectionWatcherCallback count "+ mWatcherCallbacks.size());}}public void dispatchStart(MediaProjection projection) {if (projection == null) {Slog.e(TAG, "Tried to dispatch start notification for a null media projection."+ " Ignoring!");return;}synchronized (mLock) {logd("CallbackDelegate dispatchStart: projection="+ projection.getProjectionInfo() + " hashCode "+ projection.asBinder().hashCode()+ ", IMediaProjectionWatcherCallback count " + mWatcherCallbacks.size());for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {MediaProjectionInfo info = projection.getProjectionInfo();mHandler.post(new WatcherStartCallback(info, callback));}}}public void dispatchStop(MediaProjection projection) {if (projection == null) {Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."+ " Ignoring!");return;}synchronized (mLock) {logd("CallbackDelegate dispatchStop: projection="+ projection.getProjectionInfo() + " hashCode "+ projection.asBinder().hashCode()+ ", IMediaProjectionWatcherCallback count " + mWatcherCallbacks.size()+ ", IMediaProjectionCallback count " + mClientCallbacks.size());Map<IBinder, IMediaProjectionCallback> callbacks =mClientCallbacks.get(projection.asBinder());if (callbacks != null) {for (IMediaProjectionCallback cb : callbacks.values()) {logd("CallbackDelegate dispatchStop: post IMediaProjectionCallback=" + cb);mHandler.post(new ClientStopCallback(cb));}}for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {MediaProjectionInfo info = projection.getProjectionInfo();mHandler.post(new WatcherStopCallback(info, callback));}}}}private static final class WatcherStartCallback implements Runnable {private IMediaProjectionWatcherCallback mCallback;private MediaProjectionInfo mInfo;public WatcherStartCallback(MediaProjectionInfo info,IMediaProjectionWatcherCallback callback) {mInfo = info;mCallback = callback;}@Overridepublic void run() {try {mCallback.onStart(mInfo);} catch (RemoteException e) {Slog.w(TAG, "Failed to notify media projection has stopped", e);}}}private static final class WatcherStopCallback implements Runnable {private IMediaProjectionWatcherCallback mCallback;private MediaProjectionInfo mInfo;public WatcherStopCallback(MediaProjectionInfo info,IMediaProjectionWatcherCallback callback) {mInfo = info;mCallback = callback;}@Overridepublic void run() {try {mCallback.onStop(mInfo);} catch (RemoteException e) {Slog.w(TAG, "Failed to notify media projection has stopped", e);}}}private static final class ClientStopCallback implements Runnable {private IMediaProjectionCallback mCallback;public ClientStopCallback(IMediaProjectionCallback callback) {mCallback = callback;}@Overridepublic void run() {try {mCallback.onStop();} catch (RemoteException e) {Slog.w(TAG, "Failed to notify media projection has stopped", e);}}}private static String typeToString(int type) {switch (type) {case MediaProjectionManager.TYPE_SCREEN_CAPTURE:return "TYPE_SCREEN_CAPTURE";case MediaProjectionManager.TYPE_MIRRORING:return "TYPE_MIRRORING";case MediaProjectionManager.TYPE_PRESENTATION:return "TYPE_PRESENTATION";}return Integer.toString(type);}private static final class MediaProjectionEntry {private String mUidPid;private IBinder mProjectionToken;private MediaProjection mProjectionGrant;public MediaProjectionEntry(String uidpid, IBinder token, MediaProjection mp) {this.mUidPid = uidpid;this.mProjectionToken = token;this.mProjectionGrant = mp;}public String getUidPid() {return mUidPid;}public IBinder getProjectionToken() {return mProjectionToken;}public MediaProjection getProjectionGrant() {return mProjectionGrant;}public void dump(PrintWriter pw) {pw.println("(" + mProjectionGrant.packageName + ", uid_pid=" + mUidPid+ ", token=" + mProjectionToken.hashCode() + "): "+ typeToString(mProjectionGrant.mType));}}private MediaProjectionEntry getMediaProjectionEntryByUidPid(String uidpid) {if (mEntryMap == null || mEntryMap.isEmpty() || uidpid == null || uidpid.length() <= 0) {return null;}for (MediaProjectionEntry entry : mEntryMap.values()) {if (entry != null && uidpid.equals(entry.getUidPid())) {return entry;}}return null;}private String getCallingUidPid() {int pid = Binder.getCallingPid();int uid = Binder.getCallingUid();return String.format("%s_%s", uid, pid);}private static void logd(String msg) {logd(TAG, msg);}private static void loge(String msg) {loge(TAG, msg);}private static void logd(String tag, String msg) {if (DEBUG) {Slog.d(tag, msg);}}private static void loge(String tag, String msg) {Slog.e(tag, msg);}}
相关文章:
Android11实现能同时开多个录屏应用(或者共享屏幕或投屏时录屏)
1.概述 Android原生对MediaProjection的管理逻辑,是如果服务端已经保存有MediaProjection的实例,那么再次创建的时候,之前的MediaProjection实例就会被暂停,并且引用指向新的实例,也就导致了当开启后一个录屏应用时&a…...
音视频实战---音频重采样
1、使用swr_alloc()创建重采样实例 2、使用av_opt_set_int函数设置重采样输入输出参数 3、使用swr_init函数初始化重采样器 4、使用av_get_channel_layout_nb_channels函数计算输入源的通道数 5、给输入源分配内存空间–av_samples_alloc_array_and_samples 6、计算输出采…...

主存中存储单元地址的分配
主存中存储单元地址的分配 为什么写这篇文章? 因为我看书中这部分时,看到下面的计算一下子没反应过来: 知识回顾(第1章) 计算机系统中,字节是最小的可寻址的存储单位,通常由8个比特(bit&…...

Python和R的区别是什么,Python与R的应用场景是什么?
如果你这么问,那么你可能正站在数据科学的起点。对于志在成为数据专业人员的你来说,学习编程是无疑的。我想行你早就听过Python 与R的比较之声,并在选择中感到困惑。在此,我想说,也算是一种安慰吧:对于语言…...
azure databricks 常用的JDBC连接
做个笔记常用的spark-jdbc连接 1、mysql 的连接 def query_mysql(database,sqlstr):jdbcUsernamejdbcHostname " "jdbcDatabase ""jdbcPort 3306mysql_df spark.read \.format("jdbc") \.option("driver","com.mysql.cj.jdb…...

功能齐全的免费 IDE Visual Studio 2022 社区版
面向学生、开放源代码和单个开发人员的功能齐全的免费 IDE 下载地址 Visual Studio 2022 社区版 - 下载最新的免费版本 Visual Studio 2022 Community Edition – Download Latest Free Version 准备安装 选择需要安装的程序 安装进行中 使用C学习程序设计相关知识并培养编程…...
FreeRTOS入门基础
RTOS是为了更好地在嵌入式系统上实现多任务处理和时间敏感任务而设计的系统。它能确保任务在指定或预期的时间内得到处理。FreeRTOS是一款免费开源的RTOS,它广泛用于需要小型、预测性强、灵活系统的嵌入式设备。 创建第一个任务 任务函数:任务是通过函数…...

蓝桥杯-24点-搜索
题目 思路 --暴力递归全组合的方法。只有4个数,4种计算方式,共有4 * 3 * 2 * 1 * 4种不同的情况,可以写递归来实现。 --每次计算都是两个数之间的运算,因此4个数需要3次计算,第一次计算前有4个数,第二次有…...

【附下载】3Ds Max从安装、配置到入门提高和高级用法
#3Ds Max 一、安装 1.1 安装说明 地址:链接:https://pan.baidu.com/s/1lwKMbgbE32wCL6PpMv706A?pwddll8 提取码:dll8 –来自百度网盘超级会员V2的分享 安装说明:文件夹里有安装说明 安装解压即可 关键就是将crack文件放到自己…...

开源堡垒机Jumpserver
开源堡垒机Jumpserver 文章目录 开源堡垒机Jumpserver1 Jumpserver介绍2 Jumpserver部署用户管理资产创建账号管理模板添加 用户组管理权限管理远程连接免密连接 1 Jumpserver介绍 Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是…...
PyTorch学习笔记之基础函数篇(十五)
文章目录 数值比较运算8.1 torch.equal()函数8.2 torch.ge()函数8.3 torch.gt()函数8.4 torch.le()函数8.5 torch.lt()函数8.6 torch.ne()函数8.7 torch.sort()函数8.8 torch.topk()函数 数值比较运算 8.1 torch.equal()函数 torch.equal(tensor1, tensor2) -> bool这个函…...

Latex插入pdf图片,去除空白部分
目录 参考链接: 流程: 参考链接: 科研锦囊之Latex-如何插入图片、表格、参考文献 http://t.csdnimg.cn/vpSJ3 流程: Latex的图片插入支持PDF文件,这里笔者建议都使用PDF文件进行图片的插入,因为PDF作…...

微服务:高并发带来的问题的容错方案
1.相关脚本(陈天狼) 启动nacos客户端: startup.cmd -m standalone 启动sentinel控制台: # 直接使⽤jar命令启动项⽬(控制台本身是⼀个SpringBoot项⽬) java -Dserver.port8080 -Dcsp.sentinel.dashboard.serverlocalhost:808…...

sqllab第35-45关通关笔记
35关知识点: 宽字节注入数值型注入错误注入 payload:id1andextractvalue(1,concat(0x7e,database(),0x7e))0--联合注入 payload:id0unionselect1,database(),version()-- 36关知识点: 字符型注入宽字节注入错误注入 payload:id1%df%27andextractvalue(…...

Jenkins流水线将制品发布到Nexus存储库
1、安装jenkins(建议别用docker安装,坑太多) docker run -d -p 8089:8080 -p 10241:50000 -v /var/jenkins_workspace:/var/jenkins_home -v /etc/localtime:/etc/localtime --name my_jenkins --userroot jenkins/jenkins:2.449 坑1 打开x…...

信息学奥赛一本通之MAC端VSCode C++环境配置
前提 安装 Visual Studio CodeVSCode 中安装 C/C扩展确保 Clang 已经安装(在终端中输入命令:clang --version 来确认是否安装)未安装,在命令行执行xcode-select --install 命令,会自行安装,安装文件有点大…...

MPIKGC:大语言模型改进知识图谱补全
MPIKGC:大语言模型改进知识图谱补全 提出背景MPIKGC框架 论文:https://arxiv.org/pdf/2403.01972.pdf 代码:https://github.com/quqxui/MPIKGC 提出背景 知识图谱就像一个大数据库,里面有很多关于不同事物的信息,这…...

Flutter-自定义图片3D画廊
效果 需求 3D画廊效果 设计内容 StackGestureDetectorTransformPositioned数学三角函数 代码实现 具体代码大概300行 import dart:math;import package:flutter/material.dart; import package:flutter_xy/widgets/xy_app_bar.dart;import ../../r.dart;class ImageSwitc…...

python中如何解析Html
在最近需要的需求中,需要 python 获取网页内容,并从html中获取到想要的内容。这里记录一下两个比较常用的python库对html的解析。 1. BeautifulSoup 它是一个非常流行的python脚本库,用于解析HTML和XML文档。如果你对 java 很熟悉ÿ…...
Hystrix的原理及应用:构建微服务容错体系的利器(一)
本系列文章简介: 本系列文章旨在深入剖析Hystrix的原理及应用,帮助大家理解其如何在微服务容错体系中发挥关键作用。我们将从Hystrix的核心原理出发,探讨其隔离、熔断、降级等机制的实现原理;接着,我们将结合实际应用场…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...