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

Android 调用系统服务接口获取屏幕投影(需要android.uid.system)

在这里插入图片描述

媒体投影

借助 Android 5(API 级别 21)中引入的 android.media.projection API,您可以将设备屏幕中的内容截取为可播放、录制或投屏到其他设备(如电视)的媒体流。

Android 14(API 级别 34)引入了应用屏幕共享功能,让用户能够分享单个应用窗口(而非整个设备屏幕),无论窗口模式如何。应用屏幕共享功能会将状态栏、导航栏、通知和其他系统界面元素从共享显示屏中排除,即使应用屏幕共享功能用于全屏截取应用也是如此。系统只会分享所选应用的内容。

应用屏幕共享功能可让用户运行多个应用,但仅限于与单个应用共享内容,从而确保用户隐私、提高用户工作效率并增强多任务处理能力。

权限

如果您的应用以 Android 14 或更高版本为目标平台,则应用清单必须包含 mediaProjection 前台服务类型的权限声明:

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
    <application ...>
        <service
            android:name=".MyMediaProjectionService"
            android:foregroundServiceType="mediaProjection"
            android:exported="false">
        </service>
    </application>
</manifest>

通过调用 startForeground() 启动媒体投影服务。

如果您未在调用中指定前台服务类型,则类型默认为清单中定义的前台服务类型的按位整数。如果清单未指定任何服务类型,系统会抛出 MissingForegroundServiceTypeException

获取MediaProjection示例(常规实现)

AndroidManifest.xml

    <!-- MediaProjection -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
	<application>
		<activity android:name=".MediaProjectionTest"/>
        <service android:name=".MediaProjectionService"
            android:foregroundServiceType="mediaProjection"/>
	</application>

Activity

    MediaProjectionManager projMgr;
	final int REQUEST_CODE = 0x101;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		projMgr = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

		startService(new Intent(this, ForgroundMediaProjectionService.class));
		startActivityForResult(projMgr.createScreenCaptureIntent(), REQUEST_CODE);

	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if(requestCode == REQUEST_CODE){
			MediaProjection mp = projMgr.getMediaProjection(resultCode, data);
			if(mp != null){
				//mp.stop();
				//获取到MediaProjection后可以通过MediaCodec编码生成图片/视频/H264流...
			}
		}
	}

Service

	@Override
	public void onCreate() {
		super.onCreate();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Notification notification = null;
		Intent activity = new Intent(this, MediaProjectionTest.class);
		activity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

		if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
			NotificationChannel channel = new NotificationChannel("ScreenRecorder", "Foreground notification",
					NotificationManager.IMPORTANCE_DEFAULT);
			NotificationManager manager = getSystemService(NotificationManager.class);
			manager.createNotificationChannel(channel);

			notification = new Notification.Builder(this, "ScreenRecorder")
					.setContentTitle("Test")
					.setContentText("Test Screencast...")
					.setContentIntent(PendingIntent.getActivity(this, 0x77,
							activity, PendingIntent.FLAG_UPDATE_CURRENT))
					.build();
		}
		startForeground(1, notification);
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

启动Acrtivity后会弹出授权提示
在这里插入图片描述
点击立即开始 Activity.onActivityResult 可以获取到MediaProjection.


如果App是系统应用(android.uid.systtem), 如何跳过授权窗?

  1. 申请MediaProjection过程拆解

涉及源码
frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
frameworks/base/media/java/android/media/projection/MediaProjectionManager.java
frameworks/base/core/res/res/values/config.xml
frameworks/base/packages/SystemUI/AndroidManifest.xml
frameworks/base/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java

函数createScreenCaptureIntent 返回的Intent 指向的是 SystemUI的一个组件:

frameworks/base/media/java/android/media/projection/MediaProjectionManager.java


    /**
     * Returns an Intent that <b>must</b> be passed to startActivityForResult()
     * in order to start screen capture. The activity will prompt
     * the user whether to allow screen capture.  The result of this
     * activity should be passed to getMediaProjection.
     */
    public Intent createScreenCaptureIntent() {
        Intent i = new Intent();
        final ComponentName mediaProjectionPermissionDialogComponent =
                ComponentName.unflattenFromString(mContext.getResources().getString(
                        com.android.internal.R.string
                        .config_mediaProjectionPermissionDialogComponent));
        i.setComponent(mediaProjectionPermissionDialogComponent);
        return i;
    }

frameworks/base/core/res/res/values/config.xml

<string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>

frameworks/base/packages/SystemUI/AndroidManifest.xml

            <!-- started from MediaProjectionManager -->
            <activity
                android:name=".media.MediaProjectionPermissionActivity"
                android:exported="true"
                android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog"
                android:finishOnCloseSystemDialogs="true"
                android:launchMode="singleTop"
                android:excludeFromRecents="true"
                android:visibleToInstantApps="true"/>

MediaProjectionPermissionActivity 就是弹窗的主体

frameworks/base/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java

   @Override
       public void onCreate(Bundle icicle) {
           super.onCreate(icicle);
   
           mPackageName = getCallingPackage();
           IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
           mService = IMediaProjectionManager.Stub.asInterface(b);
   
           if (mPackageName == null) {
               finish();
               return;
           }
   
           PackageManager packageManager = getPackageManager();
           ApplicationInfo aInfo;
           try {
               aInfo = packageManager.getApplicationInfo(mPackageName, 0);
               mUid = aInfo.uid;
           } catch (PackageManager.NameNotFoundException e) {
               Log.e(TAG, "unable to look up package name", e);
               finish();
               return;
           }
   
           try {
               if (mService.hasProjectionPermission(mUid, mPackageName)) {
                   setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
                   finish();
                   return;
               }
           } catch (RemoteException e) {
               Log.e(TAG, "Error checking projection permissions", e);
               finish();
               return;
           }
   
           TextPaint paint = new TextPaint();
           paint.setTextSize(42);
   
           CharSequence dialogText = null;
           CharSequence dialogTitle = null;
           if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
               dialogText = getString(R.string.media_projection_dialog_service_text);
               dialogTitle = getString(R.string.media_projection_dialog_service_title);
           } else {
               String label = aInfo.loadLabel(packageManager).toString();
   
               // If the label contains new line characters it may push the security
               // message below the fold of the dialog. Labels shouldn't have new line
               // characters anyways, so just truncate the message the first time one
               // is seen.
               final int labelLength = label.length();
               int offset = 0;
               while (offset < labelLength) {
                   final int codePoint = label.codePointAt(offset);
                   final int type = Character.getType(codePoint);
                   if (type == Character.LINE_SEPARATOR
                           || type == Character.CONTROL
                           || type == Character.PARAGRAPH_SEPARATOR) {
                       label = label.substring(0, offset) + ELLIPSIS;
                       break;
                   }
                   offset += Character.charCount(codePoint);
               }
   
               if (label.isEmpty()) {
                   label = mPackageName;
               }
   
               String unsanitizedAppName = TextUtils.ellipsize(label,
                       paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
               String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
   
               String actionText = getString(R.string.media_projection_dialog_text, appName);
               SpannableString message = new SpannableString(actionText);
   
               int appNameIndex = actionText.indexOf(appName);
               if (appNameIndex >= 0) {
                   message.setSpan(new StyleSpan(Typeface.BOLD),
                           appNameIndex, appNameIndex + appName.length(), 0);
               }
               dialogText = message;
               dialogTitle = getString(R.string.media_projection_dialog_title, appName);
           }
   
           View dialogTitleView = View.inflate(this, R.layout.media_projection_dialog_title, null);
           TextView titleText = (TextView) dialogTitleView.findViewById(R.id.dialog_title);
           titleText.setText(dialogTitle);
   
           mDialog = new AlertDialog.Builder(this)
                   .setCustomTitle(dialogTitleView)
                   .setMessage(dialogText)
                   .setPositiveButton(R.string.media_projection_action_text, this)
                   .setNegativeButton(android.R.string.cancel, this)
                   .setOnCancelListener(this)
                   .create();
   
           mDialog.create();
           mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
   
           final Window w = mDialog.getWindow();
           w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
           w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
   
           mDialog.show();
       }
       
       private Intent getMediaProjectionIntent(int uid, String packageName)
               throws RemoteException {
           IMediaProjection projection = mService.createProjection(uid, packageName,
                    MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
           Intent intent = new Intent();
           intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
           return intent;
       }
  1. 申请成功后返回结果给到申请的Activity:
    getMediaProjectionIntent函数中, 创建了IMediaProjection并通过Intent返回给了调用的App

    setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
    
    
     IMediaProjection projection = mService.createProjection(uid, packageName,
                  MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
    
     Intent intent = new Intent();
     intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
    
     getMediaProjection(resultCode, data)
    
  2. Activity 调用 getMediaProjection 获取MediaProjection

frameworks/base/media/java/android/media/projection/MediaProjectionManager.java

  public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
          if (resultCode != Activity.RESULT_OK || resultData == null) {
              return null;
          }
          IBinder projection = resultData.getIBinderExtra(EXTRA_MEDIA_PROJECTION);
          if (projection == null) {
              return null;
          }
          return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
      }

总的来说, 这个流程稍微绕了一点路:

App MediaProjectionManager SystemUI MediaProjectionService createScreenCaptureIntent MediaProjectionPermissionActivity createProjection onActivityResult getMediaProjection App MediaProjectionManager SystemUI MediaProjectionService

createProjection的实现

frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java

         @Override // Binder call
         public IMediaProjection createProjection(int uid, String packageName, int type,
                 boolean isPermanentGrant) {
             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;
         }
         

通过反射, 调用MediaProjectionService的createProjection


注意: 此方法需要有系统权限(android.uid.system)

    //android.os.ServiceManager;

    static Object getService(String name){
		try {
			Class ServiceManager = Class.forName("android.os.ServiceManager");
            Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
            return getService.invoke(null, name);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
            e.printStackTrace();
		}
		return null;
    }

	@SuppressLint("SoonBlockedPrivateApi")
	static Object asInterface(Object binder){
		try {
			Class IMediaProjectionManager_Stub = Class.forName("android.media.projection.IMediaProjectionManager$Stub");
            Method asInterface = IMediaProjectionManager_Stub.getDeclaredMethod("asInterface", IBinder.class);
            return asInterface.invoke(null, binder);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
            e.printStackTrace();
		}
		return null;
	}

    //    private IMediaProjectionManager mService;
    //android.media.projection.IMediaProjectionManager
	@SuppressLint("SoonBlockedPrivateApi")
	public static MediaProjection createProjection(){
        //Context.java public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
        //IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
        //        mService = IMediaProjectionManager.Stub.asInterface(b);
        IBinder b = (IBinder) getService("media_projection");
        Object mService = asInterface(b) ;

        //IMediaProjection projection = mService.createProjection(uid, packageName,
        //MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
		//public static final int TYPE_SCREEN_CAPTURE = 0;
		try {
			Logger.i("createProjection", "createProjection");
			Class IMediaProjectionManager = Class.forName("android.media.projection.IMediaProjectionManager");
			// public IMediaProjection createProjection(int uid, String packageName, int type, boolean isPermanentGrant)
			Method createProjection = IMediaProjectionManager.getDeclaredMethod("createProjection", Integer.TYPE, String.class, Integer.TYPE, Boolean.TYPE);
			Object projection = createProjection.invoke(mService, android.os.Process.myUid(), App.getApp().getPackageName(),
					0, false);
			Logger.i("createProjection", "projection created!");

			//android.media.projection.IMediaProjection;
			Class IMediaProjection = IInterface.class;//Class.forName("android.media.projection.IMediaProjection");
			Method asBinder = IMediaProjection.getDeclaredMethod("asBinder");
			Logger.i("createProjection", "asBinder found");

			Intent intent = new Intent();
			//    public static final String EXTRA_MEDIA_PROJECTION =
			//            "android.media.projection.extra.EXTRA_MEDIA_PROJECTION";
			//Bundle extra = new Bundle();
			//extra.putBinder("android.media.projection.extra.EXTRA_MEDIA_PROJECTION",  (IBinder)asBinder.invoke(projection));
			//intent.putExtra("android.media.projection.extra.EXTRA_MEDIA_PROJECTION",  (IBinder)asBinder.invoke(projection));
			intent.putExtra(Intent.EXTRA_RETURN_RESULT, Activity.RESULT_OK);
			Object projBinder = asBinder.invoke(projection);
			Logger.i("createProjection", "asBinder invoke success.");

			//intent.getExtras().putBinder("android.media.projection.extra.EXTRA_MEDIA_PROJECTION",  (IBinder)projBinder);
			Method putExtra = Intent.class.getDeclaredMethod("putExtra", String.class, IBinder.class);
			putExtra.invoke(intent, "android.media.projection.extra.EXTRA_MEDIA_PROJECTION",  (IBinder)projBinder);
			Logger.i("createProjection", "putExtra with IBinder success.");

			MediaProjectionManager projMgr = App.getApp().getMediaProjectionManager();
			MediaProjection mp = projMgr.getMediaProjection(Activity.RESULT_OK, intent);
			Logger.i("createProjection", "getMediaProjection " + (mp == null ? " Failed" : "Success"));

			//new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));

			//if(mp != null)mp.stop();
			return  mp;

		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

参考

Android截屏录屏MediaProjection分享
Android录屏的三种方案
媒体投影
[Android] 使用MediaProjection截屏
android设备间实现无线投屏


http://www.kler.cn/a/503976.html

相关文章:

  • unity——Preject3——面板基类
  • mapbox进阶,添加绘图控件
  • WeakAuras NES Script(lua)
  • 数仓建模(五)选择数仓技术栈:Hive ClickHouse 其它
  • 项目概述、开发环境搭建(day01)
  • 【Redis】初识Redis
  • 数据结构之顺序结构二叉树(超详解)
  • Codeforces Round 976 (Div. 2) and Divide By Zero 9.0(A-E)
  • 接口测试总结(http与rpc)
  • 【漏洞复现】孚盟云 MailAjax.ashx SQL注入漏洞复现
  • VSCode 搜索 搜不到
  • 【Linux】设备驱动中的ioctl详解
  • 初学stm32 --- II2C_AT24C02,向EEPROM中读写数据
  • 如何通过 Nginx 配置防盗链保护静态资源(详细配置)
  • 抢十八游戏
  • web服务器+selinux实验
  • SQL面试题2:留存率问题
  • 1.14学习
  • 用 Python 从零开始创建神经网络(二十):模型评估
  • 《C++11》nullptr介绍:从NULL说起
  • 【前端】自学基础算法 -- 25.动态规划-01背包问题
  • CloudCompare视图透视问题与裁剪平面设置详解
  • RPC 源码解析~Apache Dubbo
  • 图像模糊度(清晰度)检测 EsFFT 算法详细分析
  • 测试模型安全的一些高级手段
  • Swagger学习⑲——@Webhook注解