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 SystemService
        implements 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 projections
    private 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);
    }

    @Override
    public 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() {
                @Override
                public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
                }

                @Override
                public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
                    MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
                            serviceTypes);
                }

                @Override
                public void onProcessDied(int pid, int uid) {
                }
            });
        }
    }

    @Override
    public 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();
                    }
                }
            }
        }
    }

    @Override
    public 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() {
            @Override
            public 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 call
        public 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 call
        public 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 call
        public boolean isValidMediaProjection(IMediaProjection projection) {
            return MediaProjectionManagerService.this.isValidMediaProjection(
                    projection.asBinder());
        }

        @Override // Binder call
        public 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 call
        public 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 call
        public 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);
            }
        }

        @Override
        public 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 call
        public 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 call
        public boolean canProjectVideo() {
            return mType == MediaProjectionManager.TYPE_MIRRORING ||
                    mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
        }

        @Override // Binder call
        public boolean canProjectSecureVideo() {
            return false;
        }

        @Override // Binder call
        public boolean canProjectAudio() {
            return mType == MediaProjectionManager.TYPE_MIRRORING
                    || mType == MediaProjectionManager.TYPE_PRESENTATION
                    || mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
        }

        @Override // Binder call
        public 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 call
        public 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() {
                        @Override
                        public 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 call
        public 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;
            }
        }

        @Override
        public 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);
        }

        @Override
        public 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 {
        @Override
        public 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();
                        }
                    }
                }
            }
        }

        @Override
        public 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;
        }

        @Override
        public 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;
        }

        @Override
        public 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;
        }

        @Override
        public 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);
    }

}

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值