Android——锁定launch - 原生Browser启动 -引导provision

     前段时间做了一个功能,就是锁定主launch,机器上只能跑我们定义的launch,当时没注意影响,

最近发现就是因为在AMS中加了这个锁定过滤条件导致原生Browser无法启动了,

把我郁闷的,当时怎么想都觉得奇怪,这完全不相关的两件事怎么会影响到~ 这里记录一下



                                                                              撰写不易,转载请注明出处:http://blog.csdn.net/jscese/article/details/41015941



锁定主launch

    启动android系统launch的过程原理可参考Android——启动过程详解 中的HOME启动,

 这个网上的方法比较多,最常见的就是修改原生的 CATEGORY_HOME 变量或者添加一个新的变量来做筛选条件,需要修改源码中出现CATEGORY_HOME的地方.

比如我在/frameworks/base/core/java/android/content/Intent.java 中把CATEGORY_HOME 改为:

public static final String CATEGORY_HOME = "android.intent.category.JSCESE_HOME";

这样的话在源码中其它地方使用的 CATEGORY_HOME 变量都可以不动,整体编译需要使用make update-api更新api.

然后只需要把我们想要当作launch的apk的AndroidManifest.xml文件中:

<category android:name="android.intent.category.HOME" />

//改为

<category android:name="android.intent.category.JSCESE_HOME" />

这样一来只有定义了andorid.intent.category.JSCESE_HOME 这个category的launch 才能被系统当作HOME launch启动起来!

貌似一看没有任何问题~





原生Browser启动

    像上面介绍的那样可以正常锁定住我们指定的launch,但是问题来了~,当启动Browser的时候直接就退出来了,启动其它的apk,或者使用其它的浏览器都能正常运行,

我一查发现退出原因是在BrowserActivity.java中的:

private boolean shouldIgnoreIntents() {

...

  ignore |= mKeyguardManager.inKeyguardRestrictedInputMode();   //这里为 true !代表键盘锁定 导致程序finish

return ignore;

}

最后一路跟踪调试,WindowManagerService——>PhoneWindowManager——>KeyguardViewMediator中的:

    /**
     * Given the state of the keyguard, is the input restricted?
     * Input is restricted when the keyguard is showing, or when the keyguard
     * was suppressed by an app that disabled the keyguard or we haven't been provisioned yet.
     */
    public boolean isInputRestricted() {
    	Log.d(TAG,"jscese display mShowing =="+mShowing+"  "+mNeedToReshowWhenReenabled+"  "+!mUpdateMonitor.isDeviceProvisioned());
        return mShowing || mNeedToReshowWhenReenabled || !mUpdateMonitor.isDeviceProvisioned();
    }

我发现 !mUpdateMonitor.isDeviceProvisioned()==true

追根溯源到KeyguardUpdateMonitor.java中的:

mDeviceProvisioned = Settings.Global.getInt(
                mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;

问题就出在这里,这里得到的是 0 ~

我果断的到setting.db中去查了下global表中的device_provisioned:

select * from global where name='device_provisioned';

果然在db中的值为 0 ! 查询方法可参考:Android——sqlite3 基本命令操作

源码全局一搜,发现在/packages/apps/Provision/src/com/android/provision/DefaultActivity.java 中有这么一行:

 Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);

马蛋,我瞬间明白了前因后果,我看这个DefaultActivity.java 也眼熟... 原来是漏掉了这个真正的 第一 个 apk





引导provision

 这个引导apk一般被很多人忽视,这次我也忽视掉了,最开始接触android的时候还知道这东西,久没接触给忘掉了~

这个apk是作为android第一引导apk的,AndroidManifest.xml中和一般的launch一样定义的:

            <intent-filter android:priority="1">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

所以其实在AMS中的 startHomeActivityLocked 启动HOME activity的时候,这个 provision的DefaultActivity也是被查询出来的,

而且因为优先级=1 高于 一般的launch,而被直接启动,不算作多HOME launch.

看下这个 DefaultActivity.java:

/**
 * Application that sets the provisioned bit, like SetupWizard does.
 */
public class DefaultActivity extends Activity {

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        // Add a persistent setting to allow other apps to know the device has been provisioned.
        Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);   // 这里设置了一个状态值 ,也就是上面说到的 device_provisioned,代表升级完成,设备准备好了~

        // remove this activity from the package manager.
        PackageManager pm = getPackageManager();
        ComponentName name = new ComponentName(this, DefaultActivity.class);
        pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,   //这个是直接从packagemanager中 把自己剔除,也就是说 这个activity 只启动这么一次
                PackageManager.DONT_KILL_APP);

        // terminate the activity.
        finish();
    }
}

这个引导除了设置准备完成标志,把自己屏蔽掉之外没做什么其它操作,

DEVICE_PROVISIONED:

上面有说道设置进了setting.db的global表里面,这个标志很重要,像上面就是因为键盘检测这个标志还为 0 ,导致键盘是锁定的状态,无法使用Browser,

另外还有 锁屏程序不会锁屏;对HOME key的处理也不同;电话也是打不进来的


另外从PackageManager中剔除的操作保存在 /data/system目录下的packages.xml中


另外注释上有看到 这个activity 是用来做设置向导的~所以一些第一次起机需要做的一些操作可以加在这里让其启动:

Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.xxx.xxx", "com.xxx.xxx.yourAcitvity");
intent.setComponent(componentName);
startactivity(intent); 

也有直接在这里直接查询HOME的 ,然后跳转指定的 launch~


像我上面说的那种锁定launch,就是因为启动时漏掉了这个引导provision,所以无法启动Browser,而且还有其它的功能隐患!

解决办法就是把这个Provision的AndroidManifest.xml 也:

<category android:name="android.intent.category.HOME" />

//改为

<category android:name="android.intent.category.JSCESE_HOME" />





Intent隐式启动,Activity启动选择框

  当使用intent隐式启动activity时,都是通过PackageManager 查询满足条件的activity,如果有不只一项满足,那么就会弹出一个dialog,让用户选择!

上面说到的多个HOME launch状态下,想要锁定我自己的launch也是出于这个原因!

大体记录一下流程:

 一般启动一个activity时都是context.startActivity(intent)之类的,初步调用到/frameworks/base/core/java/android/app/ContextImpl.java:

    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity)null, intent, -1, options);
    }

调用到同目录下的Instrumentation.java中的execStartActivity,再调用ActivityManagerNative.getDefault().startActivity(*);

很明显接下来就是调用到AMS中的startAcitivity

    public final int startActivity(IApplicationThread caller,
            Intent intent, String resolvedType, IBinder resultTo,
            String resultWho, int requestCode, int startFlags,
            String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
        return startActivityAsUser(caller, intent, resolvedType, resultTo, resultWho, requestCode,
                startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());
    }

    public final int startActivityAsUser(IApplicationThread caller,
            Intent intent, String resolvedType, IBinder resultTo,
            String resultWho, int requestCode, int startFlags,
            String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
        enforceNotIsolatedCaller("startActivity");
        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
                false, true, "startActivity", null);
        return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
                resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
                null, null, options, userId);
    }

调用到/frameworks/base/services/java/com/android/server/am/ActivityStack.java中:

final int startActivityMayWait(IApplicationThread caller, int callingUid,
            Intent intent, String resolvedType, IBinder resultTo,
            String resultWho, int requestCode, int startFlags, String profileFile,
            ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
            Bundle options, int userId) {

...

   newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
                                aInfo.packageName);
                        newIntent.setFlags(intent.getFlags());
                        newIntent.setClassName("android",
                                HeavyWeightSwitcherActivity.class.getName());  //可以看到符合上面的一系列判定条件之后,发现如果是多个activity满足条件,在这里就先启动了一个选择activity
                        intent = newIntent;

...

   int res = startActivityLocked(caller, intent, resolvedType,
                    aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,
                    startFlags, options, componentSpecified, null);

...

return res;

}

可以看下/frameworks/base/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java:

/**
 * This activity is displayed when the system attempts to start an Intent for
 * which there is more than one matching activity, allowing the user to decide
 * which to go to.  It is not normally used directly by application developers.
 */
public class HeavyWeightSwitcherActivity extends Activity {
    /** The PendingIntent of the new activity being launched. */
    public static final String KEY_INTENT = "intent";
    /** Set if the caller is requesting a result. */
    public static final String KEY_HAS_RESULT = "has_result";
    /** Package of current heavy-weight app. */
    public static final String KEY_CUR_APP = "cur_app";
    /** Task that current heavy-weight activity is running in. */
    public static final String KEY_CUR_TASK = "cur_task";
    /** Package of newly requested heavy-weight app. */
    public static final String KEY_NEW_APP = "new_app";
    


注释写的很明白~





郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。