Android development - the role of "android:exported" attribute in Activity, and "Permission Denial: starting Intent" error resolution

How to start another application in one application? Recently, there is such a demand, and I also stepped on a small pit. This section describes the use of the "android:exported" attribute in the Activity to achieve this access.

Description of "android:exported" attribute in Activity:

In the program list AndroidMenifest.xml file, you can set this property.

The "android:exported" attribute in the Activity in Android is set to true, which means that the external component is allowed to start the Activity; otherwise, the external component is not allowed to start the Activity;

If false is set and an external attempt is made to start this Activity, the program will crash and an exception will be reported, for example:

java.lang.SecurityException: Permission Denial: starting Intent

Entry guide:

The function I want to achieve is to start App2 in App1. In App1, use startActivity to start the activity of App2, so as to realize the requirement. As long as you set exported to false, you are in the pit. The main code is as follows:

In app1,

            //start other app
            Intent intent = mContext.getPackageManager().getLaunchIntentForPackage("com.test.app2");
            if (intent != null) {
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                mContext.startActivity(intent);
            }

 This will start the entry Activity in App2.

In App2, the AndroidManifest.xml file,

<application android:icon="@drawable/icon"
        android:allowBackup="false"
        android:name=".MyApp2"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Light">
        <activity android:name="com.test.app2.activity.MainActivity"
            
            android:exported="true" //如果为false,就会发生异常
            android:finishOnTaskLaunch="false"
            android:launchMode="singleInstance"
 
            >
 
        </activity>
...
/>

 If exported is set to false, an exception will occur. Congratulations, you have successfully entered the pit. The error message is as follows:

03-09 19:53:10.077 20340-20340/com.test.app1 D/AndroidRuntime: Shutting down VM
    
    --------- beginning of crash
03-09 19:53:10.078 20340-20340/com.test.app1 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.test.app1, PID: 20340
    java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.test.app2 cmp=com.test.app2/com.test.app2.activity.MainActivity } from ProcessRecord{3d6aa2d 20340:com.test.app1/u0a83} (pid=20340, uid=10083) not exported from uid 10117
        at android.os.Parcel.readException(Parcel.java:1620)
        at android.os.Parcel.readException(Parcel.java:1573)
        at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:2658)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1507)
        at android.app.Activity.startActivityForResult(Activity.java:3930)
        at android.app.Activity.startActivityForResult(Activity.java:3890)
        at android.app.Activity.startActivity(Activity.java:4213)
        at android.app.Activity.startActivity(Activity.java:4181)
        at com.test.app1.ui.DemoMainActivity.onClick(DemoMainActivity.java:70)
        at android.view.View.performClick(View.java:5204)
        at android.view.View$PerformClick.run(View.java:21153)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

 Of course, according to the prompt, it can also be seen that it is "not exported", so it is easy to fill in the hole:

android:exported="true"

Source code reading:

Tracing back to the source, let's take a look at the source code. Search for "not exported from uid", you can see that there are several places that can be found, and the code file can be known by looking at the name, which is the code related to the four major components of Android , as shown in the figure:

 Combined with our error message prompts, the code to locate the error prompts is in ActivityStack.java

startActivityLocked方法中:
    final int startAnyPerm = mService.checkPermission(
                START_ANY_ACTIVITY, callingPid, callingUid);
    final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid,
                callingUid, aInfo.applicationInfo.uid, aInfo.exported);
    if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) {
 
        String msg;
            if (!aInfo.exported) {
                msg = "Permission Denial: starting " + intent.toString()
                        + " from " + callerApp + " (pid=" + callingPid
                        + ", uid=" + callingUid + ")"
                        + " not exported from uid " + aInfo.applicationInfo.uid;
            } else {
                msg = "Permission Denial: starting " + intent.toString()
                        + " from " + callerApp + " (pid=" + callingPid
                        + ", uid=" + callingUid + ")"
                        + " requires " + aInfo.permission;
            }
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }

Oh, it turned out to be an exception thrown in startActivityLocked. Remember the startup process of the activity, a series of calls to startActivity related functions.

Get permission by calling mService.checkPermission and mService.checkComponentPermission. Then judge whether there is permission according to the return value. If there is no permission, it will

throw new SecurityException(msg);

This msg is the error message we mentioned above.

So, who is mService? The answer is an instance of the famous AMS (ActivityManagerService).

When checking permissions, it will judge the process id, user id, whether it is the same application, whether it is a system user, and whether exported is true...etc.

Let's directly see who checked the exported. The answer is: ActivityManager. Call chain:

ActivityStack.startActivityLocked  -->
    ActivityManagerService.checkComponentPermission -->
        ActivityManager.checkComponentPermission    -->

The code for ActivityManager.checkComponentPermission:

/** @hide */
    public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
        // Root, system server get to do everything.
        if (uid == 0 || uid == Process.SYSTEM_UID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // Isolated processes don't get any permissions.
        if (UserId.isIsolated(uid)) {
            return PackageManager.PERMISSION_DENIED;
        }
        // If there is a uid that owns whatever is being accessed, it has
        // blanket access to it regardless of the permissions it requires.
        if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // If the target is not exported, then nobody else can get to it.
        if (!exported) {
            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid);
            return PackageManager.PERMISSION_DENIED;
        }
        if (permission == null) {
            return PackageManager.PERMISSION_GRANTED;
        }
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            // Should never happen, but if it does... deny!
            Slog.e(TAG, "PackageManager is dead?!?", e);
        }
        return PackageManager.PERMISSION_DENIED;
    }

 can be seen,

// If the target is not exported, then nobody else can get to it.
        if (!exported) {
            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid);
            return PackageManager.PERMISSION_DENIED;
        }

The exported judgment is made here. If exported is false, it will

return PackageManager.PERMISSION_DENIED;

Finally, I know how this exception was thrown out.


android:exported="true" 和 android:exported="false",弄明白了么?

---------------------
Author: liranke
Source: CSDN
Original: https://blog.csdn.net/liranke/article/details/123437721
Copyright statement: this article Original article for the author, please attach the blog post link for reprint!
Content analysis By: CSDN, CNBLOG blog post one-click reprint plug-in

Guess you like

Origin blog.csdn.net/xiaowang_lj/article/details/130868674