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

打印 上一主题 下一主题

主题 1116|帖子 1116|积分 3348


媒体投影

借助 Android 5(API 级别 21)中引入的 android.media.projection API,您可以将设备屏幕中的内容截取为可播放、录制或投屏到其他设备(如电视)的媒体流。
Android 14(API 级别 34)引入了应用屏幕共享功能,让用户可以或许分享单个应用窗口(而非整个设备屏幕),无论窗口模式怎样。应用屏幕共享功能会将状态栏、导航栏、通知和其他系统界面元素从共享表现屏中排除,纵然应用屏幕共享功能用于全屏截取应用也是云云。系统只会分享所选应用的内容。
应用屏幕共享功能可让用户运行多个应用,但仅限于与单个应用共享内容,从而确保用户隐私、提高用户工作服从并增强多使命处置惩罚本领。
权限

如果您的应用以 Android 14 或更高版本为目标平台,则应用清单必须包含 mediaProjection 前台服务类型的权限声明:
  1. <manifest ...>
  2.     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  3.     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
  4.     <application ...>
  5.         <service
  6.             android:name=".MyMediaProjectionService"
  7.             android:foregroundServiceType="mediaProjection"
  8.             android:exported="false">
  9.         </service>
  10.     </application>
  11. </manifest>
复制代码
通过调用 startForeground() 启动媒体投影服务。
如果您未在调用中指定前台服务类型,则类型默认为清单中定义的前台服务类型的按位整数。如果清单未指定任何服务类型,系统会抛出 MissingForegroundServiceTypeException。
获取MediaProjection示例(通例实现)

   AndroidManifest.xml
  1.     <!-- MediaProjection -->
  2.     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  3.     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
  4.         <application>
  5.                 <activity android:name=".MediaProjectionTest"/>
  6.         <service android:name=".MediaProjectionService"
  7.             android:foregroundServiceType="mediaProjection"/>
  8.         </application>
复制代码
  Activity
  1.     MediaProjectionManager projMgr;
  2.         final int REQUEST_CODE = 0x101;
  3.         @Override
  4.         protected void onCreate(Bundle savedInstanceState) {
  5.                 super.onCreate(savedInstanceState);
  6.                 projMgr = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
  7.                 startService(new Intent(this, ForgroundMediaProjectionService.class));
  8.                 startActivityForResult(projMgr.createScreenCaptureIntent(), REQUEST_CODE);
  9.         }
  10.         @Override
  11.         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  12.                 if(requestCode == REQUEST_CODE){
  13.                         MediaProjection mp = projMgr.getMediaProjection(resultCode, data);
  14.                         if(mp != null){
  15.                                 //mp.stop();
  16.                                 //获取到MediaProjection后可以通过MediaCodec编码生成图片/视频/H264流...
  17.                         }
  18.                 }
  19.         }
复制代码
  Service
  1.         @Override
  2.         public void onCreate() {
  3.                 super.onCreate();
  4.         }
  5.         @Override
  6.         public int onStartCommand(Intent intent, int flags, int startId) {
  7.                 Notification notification = null;
  8.                 Intent activity = new Intent(this, MediaProjectionTest.class);
  9.                 activity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  10.                 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
  11.                         NotificationChannel channel = new NotificationChannel("ScreenRecorder", "Foreground notification",
  12.                                         NotificationManager.IMPORTANCE_DEFAULT);
  13.                         NotificationManager manager = getSystemService(NotificationManager.class);
  14.                         manager.createNotificationChannel(channel);
  15.                         notification = new Notification.Builder(this, "ScreenRecorder")
  16.                                         .setContentTitle("Test")
  17.                                         .setContentText("Test Screencast...")
  18.                                         .setContentIntent(PendingIntent.getActivity(this, 0x77,
  19.                                                         activity, PendingIntent.FLAG_UPDATE_CURRENT))
  20.                                         .build();
  21.                 }
  22.                 startForeground(1, notification);
  23.                 return super.onStartCommand(intent, flags, startId);
  24.         }
  25.         @Override
  26.         public IBinder onBind(Intent intent) {
  27.                 return null;
  28.         }
复制代码
启动Acrtivity后会弹出授权提示

点击立即开始 Activity.onActivityResult 可以获取到MediaProjection.

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


  • 申请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
  1.     /**
  2.      * Returns an Intent that <b>must</b> be passed to startActivityForResult()
  3.      * in order to start screen capture. The activity will prompt
  4.      * the user whether to allow screen capture.  The result of this
  5.      * activity should be passed to getMediaProjection.
  6.      */
  7.     public Intent createScreenCaptureIntent() {
  8.         Intent i = new Intent();
  9.         final ComponentName mediaProjectionPermissionDialogComponent =
  10.                 ComponentName.unflattenFromString(mContext.getResources().getString(
  11.                         com.android.internal.R.string
  12.                         .config_mediaProjectionPermissionDialogComponent));
  13.         i.setComponent(mediaProjectionPermissionDialogComponent);
  14.         return i;
  15.     }
复制代码
  frameworks/base/core/res/res/values/config.xml
  1. <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
复制代码
  frameworks/base/packages/SystemUI/AndroidManifest.xml
  1.             <!-- started from MediaProjectionManager -->
  2.             <activity
  3.                 android:name=".media.MediaProjectionPermissionActivity"
  4.                 android:exported="true"
  5.                 android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog"
  6.                 android:finishOnCloseSystemDialogs="true"
  7.                 android:launchMode="singleTop"
  8.                 android:excludeFromRecents="true"
  9.                 android:visibleToInstantApps="true"/>
复制代码
MediaProjectionPermissionActivity 就是弹窗的主体
   frameworks/base/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
  1.    @Override
  2.        public void onCreate(Bundle icicle) {
  3.            super.onCreate(icicle);
  4.    
  5.            mPackageName = getCallingPackage();
  6.            IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
  7.            mService = IMediaProjectionManager.Stub.asInterface(b);
  8.    
  9.            if (mPackageName == null) {
  10.                finish();
  11.                return;
  12.            }
  13.    
  14.            PackageManager packageManager = getPackageManager();
  15.            ApplicationInfo aInfo;
  16.            try {
  17.                aInfo = packageManager.getApplicationInfo(mPackageName, 0);
  18.                mUid = aInfo.uid;
  19.            } catch (PackageManager.NameNotFoundException e) {
  20.                Log.e(TAG, "unable to look up package name", e);
  21.                finish();
  22.                return;
  23.            }
  24.    
  25.            try {
  26.                if (mService.hasProjectionPermission(mUid, mPackageName)) {
  27.                    setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
  28.                    finish();
  29.                    return;
  30.                }
  31.            } catch (RemoteException e) {
  32.                Log.e(TAG, "Error checking projection permissions", e);
  33.                finish();
  34.                return;
  35.            }
  36.    
  37.            TextPaint paint = new TextPaint();
  38.            paint.setTextSize(42);
  39.    
  40.            CharSequence dialogText = null;
  41.            CharSequence dialogTitle = null;
  42.            if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
  43.                dialogText = getString(R.string.media_projection_dialog_service_text);
  44.                dialogTitle = getString(R.string.media_projection_dialog_service_title);
  45.            } else {
  46.                String label = aInfo.loadLabel(packageManager).toString();
  47.    
  48.                // If the label contains new line characters it may push the security
  49.                // message below the fold of the dialog. Labels shouldn't have new line
  50.                // characters anyways, so just truncate the message the first time one
  51.                // is seen.
  52.                final int labelLength = label.length();
  53.                int offset = 0;
  54.                while (offset < labelLength) {
  55.                    final int codePoint = label.codePointAt(offset);
  56.                    final int type = Character.getType(codePoint);
  57.                    if (type == Character.LINE_SEPARATOR
  58.                            || type == Character.CONTROL
  59.                            || type == Character.PARAGRAPH_SEPARATOR) {
  60.                        label = label.substring(0, offset) + ELLIPSIS;
  61.                        break;
  62.                    }
  63.                    offset += Character.charCount(codePoint);
  64.                }
  65.    
  66.                if (label.isEmpty()) {
  67.                    label = mPackageName;
  68.                }
  69.    
  70.                String unsanitizedAppName = TextUtils.ellipsize(label,
  71.                        paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
  72.                String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
  73.    
  74.                String actionText = getString(R.string.media_projection_dialog_text, appName);
  75.                SpannableString message = new SpannableString(actionText);
  76.    
  77.                int appNameIndex = actionText.indexOf(appName);
  78.                if (appNameIndex >= 0) {
  79.                    message.setSpan(new StyleSpan(Typeface.BOLD),
  80.                            appNameIndex, appNameIndex + appName.length(), 0);
  81.                }
  82.                dialogText = message;
  83.                dialogTitle = getString(R.string.media_projection_dialog_title, appName);
  84.            }
  85.    
  86.            View dialogTitleView = View.inflate(this, R.layout.media_projection_dialog_title, null);
  87.            TextView titleText = (TextView) dialogTitleView.findViewById(R.id.dialog_title);
  88.            titleText.setText(dialogTitle);
  89.    
  90.            mDialog = new AlertDialog.Builder(this)
  91.                    .setCustomTitle(dialogTitleView)
  92.                    .setMessage(dialogText)
  93.                    .setPositiveButton(R.string.media_projection_action_text, this)
  94.                    .setNegativeButton(android.R.string.cancel, this)
  95.                    .setOnCancelListener(this)
  96.                    .create();
  97.    
  98.            mDialog.create();
  99.            mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
  100.    
  101.            final Window w = mDialog.getWindow();
  102.            w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
  103.            w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
  104.    
  105.            mDialog.show();
  106.        }
  107.       
  108.        private Intent getMediaProjectionIntent(int uid, String packageName)
  109.                throws RemoteException {
  110.            IMediaProjection projection = mService.createProjection(uid, packageName,
  111.                     MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
  112.            Intent intent = new Intent();
  113.            intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
  114.            return intent;
  115.        }
复制代码

  • 申请成功后返回结果给到申请的Activity:
    在getMediaProjectionIntent函数中, 创建了IMediaProjection并通过Intent返回给了调用的App
    1. setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
    复制代码
    1. IMediaProjection projection = mService.createProjection(uid, packageName,
    2.               MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
    3. Intent intent = new Intent();
    4. intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
    5. getMediaProjection(resultCode, data)
    复制代码
  • Activity 调用 getMediaProjection 获取MediaProjection
   frameworks/base/media/java/android/media/projection/MediaProjectionManager.java
  1.   public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
  2.           if (resultCode != Activity.RESULT_OK || resultData == null) {
  3.               return null;
  4.           }
  5.           IBinder projection = resultData.getIBinderExtra(EXTRA_MEDIA_PROJECTION);
  6.           if (projection == null) {
  7.               return null;
  8.           }
  9.           return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
  10.       }
复制代码
总的来说, 这个流程稍微绕了一点路:
     
createProjection的实现
   frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
  1.          @Override // Binder call
  2.          public IMediaProjection createProjection(int uid, String packageName, int type,
  3.                  boolean isPermanentGrant) {
  4.              if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
  5.                          != PackageManager.PERMISSION_GRANTED) {
  6.                  throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
  7.                          + "projection permission");
  8.              }
  9.              if (packageName == null || packageName.isEmpty()) {
  10.                  throw new IllegalArgumentException("package name must not be empty");
  11.              }
  12.              final UserHandle callingUser = Binder.getCallingUserHandle();
  13.              long callingToken = Binder.clearCallingIdentity();
  14.              MediaProjection projection;
  15.              try {
  16.                  ApplicationInfo ai;
  17.                  try {
  18.                      ai = mPackageManager.getApplicationInfoAsUser(packageName, 0, callingUser);
  19.                  } catch (NameNotFoundException e) {
  20.                      throw new IllegalArgumentException("No package matching :" + packageName);
  21.                  }
  22.                  projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
  23.                          ai.isPrivilegedApp());
  24.                  if (isPermanentGrant) {
  25.                      mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
  26.                              projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
  27.                  }
  28.              } finally {
  29.                  Binder.restoreCallingIdentity(callingToken);
  30.              }
  31.              return projection;
  32.          }
  33.          
复制代码
通过反射, 调用MediaProjectionService的createProjection

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

  1.     //android.os.ServiceManager;
  2.     static Object getService(String name){
  3.                 try {
  4.                         Class ServiceManager = Class.forName("android.os.ServiceManager");
  5.             Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
  6.             return getService.invoke(null, name);
  7.                 } catch (ClassNotFoundException e) {
  8.                         e.printStackTrace();
  9.                 } catch (NoSuchMethodException e) {
  10.                         e.printStackTrace();
  11.                 } catch (InvocationTargetException e) {
  12.                         e.printStackTrace();
  13.                 } catch (IllegalAccessException e) {
  14.             e.printStackTrace();
  15.                 }
  16.                 return null;
  17.     }
  18.         @SuppressLint("SoonBlockedPrivateApi")
  19.         static Object asInterface(Object binder){
  20.                 try {
  21.                         Class IMediaProjectionManager_Stub = Class.forName("android.media.projection.IMediaProjectionManager$Stub");
  22.             Method asInterface = IMediaProjectionManager_Stub.getDeclaredMethod("asInterface", IBinder.class);
  23.             return asInterface.invoke(null, binder);
  24.                 } catch (ClassNotFoundException e) {
  25.                         e.printStackTrace();
  26.                 } catch (NoSuchMethodException e) {
  27.                         e.printStackTrace();
  28.                 } catch (InvocationTargetException e) {
  29.                         e.printStackTrace();
  30.                 } catch (IllegalAccessException e) {
  31.             e.printStackTrace();
  32.                 }
  33.                 return null;
  34.         }
  35.     //    private IMediaProjectionManager mService;
  36.     //android.media.projection.IMediaProjectionManager
  37.         @SuppressLint("SoonBlockedPrivateApi")
  38.         public static MediaProjection createProjection(){
  39.         //Context.java public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
  40.         //IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
  41.         //        mService = IMediaProjectionManager.Stub.asInterface(b);
  42.         IBinder b = (IBinder) getService("media_projection");
  43.         Object mService = asInterface(b) ;
  44.         //IMediaProjection projection = mService.createProjection(uid, packageName,
  45.         //MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
  46.                 //public static final int TYPE_SCREEN_CAPTURE = 0;
  47.                 try {
  48.                         Logger.i("createProjection", "createProjection");
  49.                         Class IMediaProjectionManager = Class.forName("android.media.projection.IMediaProjectionManager");
  50.                         // public IMediaProjection createProjection(int uid, String packageName, int type, boolean isPermanentGrant)
  51.                         Method createProjection = IMediaProjectionManager.getDeclaredMethod("createProjection", Integer.TYPE, String.class, Integer.TYPE, Boolean.TYPE);
  52.                         Object projection = createProjection.invoke(mService, android.os.Process.myUid(), App.getApp().getPackageName(),
  53.                                         0, false);
  54.                         Logger.i("createProjection", "projection created!");
  55.                         //android.media.projection.IMediaProjection;
  56.                         Class IMediaProjection = IInterface.class;//Class.forName("android.media.projection.IMediaProjection");
  57.                         Method asBinder = IMediaProjection.getDeclaredMethod("asBinder");
  58.                         Logger.i("createProjection", "asBinder found");
  59.                         Intent intent = new Intent();
  60.                         //    public static final String EXTRA_MEDIA_PROJECTION =
  61.                         //            "android.media.projection.extra.EXTRA_MEDIA_PROJECTION";
  62.                         //Bundle extra = new Bundle();
  63.                         //extra.putBinder("android.media.projection.extra.EXTRA_MEDIA_PROJECTION",  (IBinder)asBinder.invoke(projection));
  64.                         //intent.putExtra("android.media.projection.extra.EXTRA_MEDIA_PROJECTION",  (IBinder)asBinder.invoke(projection));
  65.                         intent.putExtra(Intent.EXTRA_RETURN_RESULT, Activity.RESULT_OK);
  66.                         Object projBinder = asBinder.invoke(projection);
  67.                         Logger.i("createProjection", "asBinder invoke success.");
  68.                         //intent.getExtras().putBinder("android.media.projection.extra.EXTRA_MEDIA_PROJECTION",  (IBinder)projBinder);
  69.                         Method putExtra = Intent.class.getDeclaredMethod("putExtra", String.class, IBinder.class);
  70.                         putExtra.invoke(intent, "android.media.projection.extra.EXTRA_MEDIA_PROJECTION",  (IBinder)projBinder);
  71.                         Logger.i("createProjection", "putExtra with IBinder success.");
  72.                         MediaProjectionManager projMgr = App.getApp().getMediaProjectionManager();
  73.                         MediaProjection mp = projMgr.getMediaProjection(Activity.RESULT_OK, intent);
  74.                         Logger.i("createProjection", "getMediaProjection " + (mp == null ? " Failed" : "Success"));
  75.                         //new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
  76.                         //if(mp != null)mp.stop();
  77.                         return  mp;
  78.                 } catch (ClassNotFoundException e) {
  79.                         e.printStackTrace();
  80.                 } catch (NoSuchMethodException e) {
  81.                         e.printStackTrace();
  82.                 } catch (InvocationTargetException e) {
  83.                         e.printStackTrace();
  84.                 } catch (IllegalAccessException e) {
  85.                         e.printStackTrace();
  86.                 }
  87.                 return null;
  88.         }
复制代码
参考

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表