background

These days, there is a requirement to take a photo. Simply call the camera to take a photo, which can be done directly with the intent. However, if the phone is equipped with more than one camera, the popup window will prompt the user to choose which camera to use. To this point, Party A said four words

With the system of

All right, let’s do it. After searching, we learned that we can add the package name to restrict the response to our intent, so we just need to get the package name of the system camera. Let’s take a look at the process.

Call the camera to take a picture

Here we can create an intent, create a temporary file, and provide it as a URI. After the system camera takes the image, it will save it to the URI we provide, and then we can use it directly. Here, if other camera apps are installed on the user’s phone, a pop-up window will prompt the user to choose a camera to use. However, in the test, some third-party cameras can take photos, but the photo data is not saved in the URL we passed in according to the official rules. In addition to party A’s requirements, only the system camera is allowed to be called, and the package name of the system camera is passed in through intent.setPackage. Here’s the code.

val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
val cameraPackage = getSystemCameraPackageName(this)
if (cameraPackage.isNullOrEmpty()) {
    ToastUtils.showShort("No System Camera")
    return@setOnClickListener
}
intent.setPackage(cameraPackage)
val saveFileDir = File(this.externalCacheDir, "PhotoUtils")
saveFileDir.mkdirs()
val fileName = fileDateFormat.format(Date()) + ".jpg"
val file = File(saveFileDir, fileName)
val uri = FileProvider.getUriForFile(this."$packageName.provider", file)
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
startActivityForResult(intent, 2000)
Copy the code

Restrict calls to system cameras

Next, we need to get the package name of the system camera, first introduce the method I used in the first version. Firstly, all installation package information is obtained, and then the package name of the system camera is obtained by name judgment. It’s a little clunky, but it works. The specific codes are as follows and are not recommended.

        val packages = context.packageManager.getInstalledPackages(0)
        for (i in packages.indices) {
            val packageInfo = packages[i]
            val strLabel = packageInfo.applicationInfo.loadLabel(context.packageManager).toString()
            // The name of the camera software in a common mobile phone system
            if ("Camera, Camera, take a picture, take a picture,Camera, Camera, Camera.".contains(strLabel)) {
                systemCameraPackageName = packageInfo.packageName
                if(packageInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM ! =0) {
                    break}}}Copy the code

When I looked at this code, I decided to see if there was a better way to do this, and finally saw an answer on StackOverflow that looked like this.

public static ResolveInfo  getCameraPackageName(Context context, PackageManager pm) {
    Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    ResolveInfo cameraInfo = null;
    List<ResolveInfo> pkgList = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
    if(pkgList ! =null && pkgList.size() > 0) {
        cameraInfo = pkgList.get(0);
    }
    return(cameraInfo);
}
Copy the code

After testing, it is found that this method also works. Of course, if we have multiple cameras on the phone, pkgList will return multiple data. I can just take the first one of these. At this point we can call the camera to take a picture, and we can restrict calls to the system camera.

Is it feasible to install multiple cameras?

conclusion

CameraInfo = pkgList. Get (0); Is it possible to just take the first one when there are multiple of them? According to my analysis, it works. (Please correct any mistakes.) Let’s go straight to the source code.

The source code

We construct an intent to take a photo in our code, and then we search, so that all the application information that can respond to our intent is returned. We use getPackageManager(); To get the PackageManager the method here is in the Context, we find the method in the implementation class ContextImpl.

    @Override
    public PackageManager getPackageManager(a) {
        if(mPackageManager ! =null) {
            return mPackageManager;
        }

        IPackageManager pm = ActivityThread.getPackageManager();
        if(pm ! =null) {
            / / you can see we get ApplicationPackageManager
            // Doesn't matter if we make more than one instance.
            return (mPackageManager = new ApplicationPackageManager(this, pm));
        }

        return null;
    }
Copy the code

That we call is ApplicationPackageManager queryIntentActivities method, move on. Then call the queryIntentActivitiesAsUser method. Continue to mPM queryIntentActivities method, can be found by PackageManagerService service returns information, we find the corresponding code in the PMS. Here is a snippet of the code.

List<ResolveInfo> result = mActivities.queryIntent(intent, resolvedType, flags, userId);
if(resolveInfo ! =null) {
    result.add(resolveInfo);
    Collections.sort(result, mResolvePrioritySorter);
}
Copy the code

MActivities is an ActivityIntentResolver class that calls its parent IntentResolver’s queryIntent method. Here, because we didn’t set any other conditions, we get the statement firstTypeCut = mactionTofilter.get (intent.getAction()); .

if (resolvedType == null && scheme == null&& intent.getAction() ! =null) {
  firstTypeCut = mActionToFilter.get(intent.getAction());
  if (debug) Slog.v(TAG, "Action list: " + Arrays.toString(firstTypeCut));
}

/ /...
if(firstTypeCut ! =null) {
  buildResolveList(intent, categories, debug, 
  defaultOnly,resolvedType, scheme, firstTypeCut, finalList, userId);
}
Copy the code

Let’s see what mActionToFilter is, it’s a collection that holds all the registered actions so that we can match the ACTION_IMAGE_CAPTURE that we need and return, Further processing results in the List

pkgList we obtained above.

/**
* All of the actions that have been registered, but only those that did
* not specify data.
*/
private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>();
Copy the code

This must store all types of data in the system, so it should be assigned in the PMS, because the PMS is responsible for parsing the data, The order should also be Frawework app-> System app->user app, so there seems to be no problem in getting the first one in the final result list.

PMS parsing process

The above conclusion is our conjecture, let’s continue to look at the PMS part of the code to find out. Start with the PMS constructor, which calls the scanDirLI method to scan all installation packages. The order is from Framework, System, OEM and APP. The first scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) method is called in the scanDirLI method. ScanPackageLI (packageParser. Package PKG, int parseFlags, int scanFlags, long currentTime, UserHandle user) notice that scanPackageLI is called twice and is an overloaded function with different arguments. The second call to scanPackageLI will call the scanPackageDirtyLI() method. There is a code in this function that needs our attention: mActivities.addActivity(a, “activity”); Here the mActivities are of type ActivityIntentResolver and the parent type is IntentResolver. The addActivity method goes to the parent class’s addFilter(Intent); Methods. Inside this method, our intent is saved to mActionToFilter, which is the collection we retrieved above. So we can retrieve the data directly when we use it, which is one of the things that PMS does for us. There is a lot of logic in this section of code, but since we are focusing on the mActionToFilter, we will skip over much of it.

    public void addFilter(F f) {
        / /...
        if (numS == 0 && numT == 0) {
            register_intent_filter(f, f.actionsIterator(), mActionToFilter, " Action: ");
        }
        / /...
    }
Copy the code

conclusion

Seeing this, our theory should be working. Then our requirements function will be complete. While the functionality is fairly generic, the validation method for tracing the source code is worth looking at a few more times, and it will give you a new understanding of the parsing process.

All right, looks like we’re done for the day.