当前位置: 首页 > news >正文

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的管理逻辑&#xff0c;是如果服务端已经保存有MediaProjection的实例&#xff0c;那么再次创建的时候&#xff0c;之前的MediaProjection实例就会被暂停&#xff0c;并且引用指向新的实例&#xff0c;也就导致了当开启后一个录屏应用时&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、计算输出采…...

主存中存储单元地址的分配

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

Python和R的区别是什么,Python与R的应用场景是什么?

如果你这么问&#xff0c;那么你可能正站在数据科学的起点。对于志在成为数据专业人员的你来说&#xff0c;学习编程是无疑的。我想行你早就听过Python 与R的比较之声&#xff0c;并在选择中感到困惑。在此&#xff0c;我想说&#xff0c;也算是一种安慰吧&#xff1a;对于语言…...

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&#xff0c;它广泛用于需要小型、预测性强、灵活系统的嵌入式设备。 创建第一个任务 任务函数&#xff1a;任务是通过函数…...

蓝桥杯-24点-搜索

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

【附下载】3Ds Max从安装、配置到入门提高和高级用法

#3Ds Max 一、安装 1.1 安装说明 地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1lwKMbgbE32wCL6PpMv706A?pwddll8 提取码&#xff1a;dll8 –来自百度网盘超级会员V2的分享 安装说明&#xff1a;文件夹里有安装说明 安装解压即可 关键就是将crack文件放到自己…...

开源堡垒机Jumpserver

开源堡垒机Jumpserver 文章目录 开源堡垒机Jumpserver1 Jumpserver介绍2 Jumpserver部署用户管理资产创建账号管理模板添加 用户组管理权限管理远程连接免密连接 1 Jumpserver介绍 Jumpserver 是全球首款完全开源的堡垒机&#xff0c;使用 GNU GPL v2.0 开源协议&#xff0c;是…...

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图片,去除空白部分

目录 参考链接&#xff1a; 流程&#xff1a; 参考链接&#xff1a; ​科研锦囊之Latex-如何插入图片、表格、参考文献 http://t.csdnimg.cn/vpSJ3 流程&#xff1a; Latex的图片插入支持PDF文件&#xff0c;这里笔者建议都使用PDF文件进行图片的插入&#xff0c;因为PDF作…...

微服务:高并发带来的问题的容错方案

1.相关脚本&#xff08;陈天狼&#xff09; 启动nacos客户端&#xff1a; startup.cmd -m standalone 启动sentinel控制台&#xff1a; # 直接使⽤jar命令启动项⽬(控制台本身是⼀个SpringBoot项⽬) java -Dserver.port8080 -Dcsp.sentinel.dashboard.serverlocalhost:808…...

sqllab第35-45关通关笔记

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

Jenkins流水线将制品发布到Nexus存储库

1、安装jenkins&#xff08;建议别用docker安装&#xff0c;坑太多&#xff09; 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 已经安装&#xff08;在终端中输入命令&#xff1a;clang --version 来确认是否安装&#xff09;未安装&#xff0c;在命令行执行xcode-select --install 命令&#xff0c;会自行安装&#xff0c;安装文件有点大…...

MPIKGC:大语言模型改进知识图谱补全

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

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

在最近需要的需求中&#xff0c;需要 python 获取网页内容&#xff0c;并从html中获取到想要的内容。这里记录一下两个比较常用的python库对html的解析。 1. BeautifulSoup 它是一个非常流行的python脚本库&#xff0c;用于解析HTML和XML文档。如果你对 java 很熟悉&#xff…...

Hystrix的原理及应用:构建微服务容错体系的利器(一)

本系列文章简介&#xff1a; 本系列文章旨在深入剖析Hystrix的原理及应用&#xff0c;帮助大家理解其如何在微服务容错体系中发挥关键作用。我们将从Hystrix的核心原理出发&#xff0c;探讨其隔离、熔断、降级等机制的实现原理&#xff1b;接着&#xff0c;我们将结合实际应用场…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

[USACO23FEB] Bakery S

题目描述 Bessie 开了一家面包店! 在她的面包店里&#xff0c;Bessie 有一个烤箱&#xff0c;可以在 t C t_C tC​ 的时间内生产一块饼干或在 t M t_M tM​ 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC​,tM​≤109)。由于空间…...

拟合问题处理

在机器学习中&#xff0c;核心任务通常围绕模型训练和性能提升展开&#xff0c;但你提到的 “优化训练数据解决过拟合” 和 “提升泛化性能解决欠拟合” 需要结合更准确的概念进行梳理。以下是对机器学习核心任务的系统复习和修正&#xff1a; 一、机器学习的核心任务框架 机…...

js 设置3秒后执行

如何在JavaScript中延迟3秒执行操作 在JavaScript中&#xff0c;要设置一个操作在指定延迟后&#xff08;例如3秒&#xff09;执行&#xff0c;可以使用 setTimeout 函数。setTimeout 是JavaScript的核心计时器方法&#xff0c;它接受两个参数&#xff1a; 要执行的函数&…...

GeoServer发布PostgreSQL图层后WFS查询无主键字段

在使用 GeoServer&#xff08;版本 2.22.2&#xff09; 发布 PostgreSQL&#xff08;PostGIS&#xff09;中的表为地图服务时&#xff0c;常常会遇到一个小问题&#xff1a; WFS 查询中&#xff0c;主键字段&#xff08;如 id&#xff09;莫名其妙地消失了&#xff01; 即使你在…...