探究Exported属性对startActivity的影响

缘由

这几天想做一个点击跳转到TIM的扫一扫的Activity的功能,代码是这样的,就是普通的跳转

1
2
3
4
5
6
7
8
9
10
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ComponentName component = new ComponentName("com.tencent.tim", "com.tencent.biz.qrcode.activity.ScannerActivity");
intent.setComponent(component);
intent.setAction("android.intent.action.VIEW");
try {
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}

为什么我后面要加try/catch呢,因为不加的话会报异常,然后闪退,报的异常内容如下:

1
2
3
4
5
java.lang.SecurityException: Permission Denial: 
starting Intent { act=android.intent.action.VIEW flg=0x10000000
cmp=com.tencent.tim/com.tencent.biz.qrcode.activity.ScannerActivity }
from ProcessRecord{e0031ac 25553:top.jowanxu.xposedtest/u0a175}
(pid=25553, uid=10175) not exported from uid 10151

Exported属性

wtf?没有权限?然后呢,百度了下,发现是Activity的属性exported的值为false,然后别的app就打不开这个Activity了,如果要打开的话,就必须和这个Activity在同一Application下或者uid相同才行。

1
2
3
4
5
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
android:sharedUserId="com.example.categorytest">
...
</manifest>

同一不能打开的还有在没有设置exported属性的时候,也没有设置intent-filter属性的话,也是打不开这个Activity的。

1
2
3
4
5
6
7
8
9
10
11
12
<activity
android:name=".ScannerActivity"
android:label="@string/app_name"
android:exported="false"/> <!-- 设置了exported属性值为false -->

<!-- 如果Activity里面至少有一个filter的话,意味着这个Activity可以被其它应用从外部唤起,这个时候它的默认值是true -->
<activity
android:name=".SecondActivity"
android:label="@string/app_name">
<intent-filter>
</intent-filter>
</activity>

然后我们Analyze APK一下我们的TIM的apk,打开它的AndroidManifest.xml文件,然后搜索ScannerActivity,发现ScannerActivity里面的exported的值果然是false。

既然如此的话,那就看一下当Activity的exported属性值为false的时候,为什么不能调起这个Activity,而且还会报异常。
startActivity的源码看起,既然我们一开始的问题是Permission Denial,那么我们查看的关键词就必须包含permission,这样看起源码来就方便许多。

源码

Activity类

首先是Activity里面的startActivity,发现他是调用自己的另一个同名不同参数的方法。

1
2
3
4
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}

跳到startActivity(Intent intent, @Nullable Bundle options)方法后,因为options参数为null,所以是调用startActivityForResult(@RequiresPermission Intent intent, int requestCode)这个方法。

1
2
3
4
5
6
7
8
9
10
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}

跳到startActivityForResult方法后,发现又是调用同名不同参数的方法startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)

1
2
3
4
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
// requestCode = -1
startActivityForResult(intent, requestCode, null);
}

接着看mParent == null条件里面的代码,关键词startActivity,然后找到execStartActivity(),是Instrumentation类里面的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
....
} else {
....
}
}

Instrumentation类

跳转到execStartActivity方法里,同样关键词startActivity,可以看到是ActivityManagerNative.getDefault().startActivity()方法和checkStartActivityResult()方法,我们先来看checkStartActivityResult()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
....
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target, requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}

点进去之后发现,这里面就是我们经常startActivity之后,在类没找到或者没有在AndroidManifest中注册等等之后会报出的异常的判断方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static void checkStartActivityResult(int res, Object intent) {
if (res >= ActivityManager.START_SUCCESS) {
return;
}

switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
case ActivityManager.START_PERMISSION_DENIED:
throw new SecurityException("Not allowed to start activity "
+ intent);
case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
throw new AndroidRuntimeException(
"FORWARD_RESULT_FLAG used while also requesting a result");
case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
case ActivityManager.START_NOT_VOICE_COMPATIBLE:
throw new SecurityException(
"Starting under voice control not allowed for: " + intent);
case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:
throw new IllegalStateException(
"Session calling startVoiceActivity does not match active session");
case ActivityManager.START_VOICE_HIDDEN_SESSION:
throw new IllegalStateException(
"Cannot start voice activity on a hidden session");
case ActivityManager.START_CANCELED:
throw new AndroidRuntimeException("Activity could not be started for "
+ intent);
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
}
}

IActivityManager接口

点击startActivity()之后,跳转到IActivityManager接口里面来了,这个接口就是管理Activity的,然后我们从ActivityManagerNative.getDefault().startActivity()看出调用者是在ActivityManangerNative类里面。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* System private API for talking with the activity manager service. This
* provides calls from the application back to the activity manager.
*
* {@hide}
*/
public interface IActivityManager extends IInterface {
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
....
}

ActivityManagerNative类

这时候到了ActivityManagerNative类里面,实现了IActivityManager接口,同时ActivityManagerNative还是一个抽象类,说明ActivityManagerNative.getDefault().startActivity()调用startActivity调用的对象是该类的子类。

1
2
3
4
5
6
7
8
9

public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
....
static public IActivityManager getDefault() {
return gDefault.get();
}
....
}

然后我们通过ctrl + shift + F打开搜索,关键词是extends ActivityManagerNative,scope选择custom,然后Find。

然后就找到了ActivityManagerService

ActivityManagerService类

ActivityManagerService类类是final类型,不能被继承,然后我们来看一下他的startActivity方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
....

@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
}
}

startActivity方法是调用了startActivityAsUser方法,我们继续走下去,来到了startActivityAsUser方法后发现,是调用了ActivityStarter类里面的startActivityMayWait方法。

1
2
3
4
5
6
7
8
9
10
11
12
13

@Override
public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
enforceNotIsolatedCaller("startActivity");
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
profilerInfo, null, null, bOptions, false, userId, null, null);
}

ActivityStarter类

startActivityMayWait方法内容很多,挑重点看,关键词startActivity,同时看permission相关的有没有,然后我们找到了startActivityLocked方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final int startActivityMayWait(IApplicationThread caller, int callingUid,
String callingPackage, Intent intent, String resolvedType,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config,
Bundle bOptions, boolean ignoreTargetSecurity, int userId,
IActivityContainer iContainer, TaskRecord inTask) {

....
ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
....
ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
....

final ActivityRecord[] outRecord = new ActivityRecord[1];
int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
aInfo, rInfo, voiceSession, voiceInteractor,
resultTo, resultWho, requestCode, callingPid,
callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
options, ignoreTargetSecurity, componentSpecified, outRecord, container,
inTask);
....
return res;
}
}

继续,走到startActivityLocked方法里面,内容特别多,同样挑关键词startActivitypermission看,结果我们找到了mSupervisor.checkStartAnyActivityPermission方法和startActivityUnchecked方法,既然我们的目的是找跟permission相关的,那么我们就只看checkStartAnyActivityPermission方法内容吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
TaskRecord inTask) {
int err = ActivityManager.START_SUCCESS;
....
final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
....
boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
resultRecord, resultStack, options);
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);
....
try {
mService.mWindowManager.deferSurfaceLayout();
err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
true, options, inTask);
} finally {
mService.mWindowManager.continueSurfaceLayout();
}
postStartActivityUncheckedProcessing(r, err, stack.mStackId, mSourceRecord, mTargetStack);
return err;
}

ActivityStackSupervisor类

根据mSupervisor.checkStartAnyActivityPermission我们来到了ActivityStackSupervisor类的checkStartAnyActivityPermission方法,方法内容不长,直接往下看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
String resultWho, int requestCode, int callingPid, int callingUid,
String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp,
ActivityRecord resultRecord, ActivityStack resultStack, ActivityOptions options) {
// 判断权限
final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
callingUid);
// 如果startAnyPerm的值为0,也就是PERMISSION_GRANTED的话,直接返回true
if (startAnyPerm == PERMISSION_GRANTED) {
return true;
}
final int componentRestriction = getComponentRestrictionForCallingPackage(
aInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity);
final int actionRestriction = getActionRestrictionForCallingPackage(
intent.getAction(), callingPackage, callingPid, callingUid);
if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION
|| actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
if (resultRecord != null) {
resultStack.sendActivityResultLocked(-1,
resultRecord, resultWho, requestCode,
Activity.RESULT_CANCELED, null);
}
final String msg;
// 重点就是这里了
if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
msg = "Permission Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ")" + " with revoked permission "
+ ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
} else 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);
}

if (actionRestriction == ACTIVITY_RESTRICTION_APPOP) {
final String message = "Appop Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ")"
+ " requires " + AppOpsManager.permissionToOp(
ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction()));
Slog.w(TAG, message);
return false;
} else if (componentRestriction == ACTIVITY_RESTRICTION_APPOP) {
final String message = "Appop Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ")"
+ " requires appop " + AppOpsManager.permissionToOp(aInfo.permission);
Slog.w(TAG, message);
return false;
}
if (options != null && options.getLaunchTaskId() != -1) {
final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
callingPid, callingUid);
if (startInTaskPerm != PERMISSION_GRANTED) {
final String msg = "Permission Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ") with launchTaskId="
+ options.getLaunchTaskId();
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}

return true;
}

找了那么久,终于找到了,开头提出的问题,就是下面这段代码里面的!aInfo.exported出现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
msg = "Permission Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ")" + " with revoked permission "
+ ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
} else 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);

aInfo的类型是ActivityInfo,里面有个exported的属性,就是我们在AndroidManifest.xml里面设置的值。

1
2
3
4
5
6
7
8
9
/**
* Information you can retrieve about a particular application
* activity or receiver. This corresponds to information collected
* from the AndroidManifest.xml's &lt;activity&gt; and
* &lt;receiver&gt; tags.
*/
public class ActivityInfo extends ComponentInfo
implements Parcelable {
}

总结

翻了那么多的源码,看到这里我们的疑惑就解除了,这里报异常是还没进行到调起要跳转的Activity的时候就已经报SecurityException异常了,也就是在checkStartAnyActivityPermission方法里面报异常,没有再往下面走startActivityUnchecked这里面启动Activity的代码。

所以,在exported属性为false的时候,别人是调用不了这个Activity的,那么我的一开始的想法是实现不了的,那就只能通过命令行来调起Activity了,当然这操作是需要root的。