Android版Prey源码分析

简单点说,Prey的作用就是当你的设备被偷了之后,可以通过安装的Prey应用找到你的设备,网上有成功先例

这里只简要分析下android版Prey的源码

 

各个Activity之间的流程图

我想要知道的重点是怎么激活手机发报告。

 

手机上一共有两种激活模式,短信激活和控制面板激活。

短信

App注册了个Receiver用来拦截短信,如果信息包含GO PREY(可设定文字),就会启动Prey进行一系列操作。

<receiver android:name="com.prey.receivers.SmsReceiver">
    <intent-filter android:priority="1000"><!--最高优先级-->
        <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>

SmsReceiver的关键代码如下

    private void executeActionsBasedOnSMSMessage(Context ctx, String SMSMessage) {
        PreyConfig preyConfig = PreyConfig.getPreyConfig(ctx);
        PreyLogger.i("SMS received: " + SMSMessage);
        boolean shouldPerform = SMSMessage.indexOf(preyConfig.getSmsToRun()) >= 0;
        boolean shouldStop = SMSMessage.indexOf(preyConfig.getSmsToStop()) >= 0;
        if (shouldPerform) {
            PreyLogger.i("SMS Match!, waking up Prey right now!");
            abortBroadcast(); //To remove the SMS from the inbox
            PreyController.startPrey(ctx);
        } else if (shouldStop) {
            PreyLogger.i("SMS Match!, stopping Prey!");
            abortBroadcast(); //To remove the SMS from the inbox
            PreyController.stopPrey(ctx);
        }
    }

PreyController.startPrey是后续动作的开始,如下所示:

Job的结构图如下:

注意到ActionsRunnner.run()的时候会调用Prey的web service拿到一些配置,比如:

<device>
    <status>
        <missing>true</missing>
    </status>
    <configuration>
        <current_release>0.5.9</current_release>
        <delay>25</delay>
        <post_url>http://control.preyproject.com/devices/ur_device_id/reports.xml</post_url>
        <auto_update>false</auto_update>
    </configuration>
    <modules>
        <module type="action" active="true" name="system" version="1.5"/>
        <module type="report" active="true" name="geo" version="1.7"/>
        <module type="report" active="true" name="network" version="1.5"/>
    </modules>
</device>

结合Job的结构图和上述XML来说,每个module都会对应一个ActionJob,而每个ActionJob都持有一个PreyAction,这个PreyAction是干实事的。

比如上面的geo对应的是LocationNotifierAction,这个action的作用就是获取设备当前的经纬度。

Job的详细运行过程如下:

PreyController.startPrey会循环检查手机网络是否连通

            boolean isPhoneConnected = false;
            final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);
            final ConnectivityManager connectivityManager = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
    
            try {
                isPhoneConnected = (tm.getDataState() == TelephonyManager.DATA_CONNECTED) || activeNetInfo.isConnected();
                while (!isPhoneConnected) {
                    isPhoneConnected = (tm.getDataState() == TelephonyManager.DATA_CONNECTED) || activeNetInfo.isConnected();
                    PreyLogger.d("Phone doesn't have internet connection now. Waiting 10 secs for it");
                    Thread.sleep(10000);
                }
            }

如果连通就会start PreyRunnerService(包装Prey的job运行),这个service创建成功之后会调用ActionsRunner.run(),在这个方法里会循环发report,发report是通过调用getInstructionsAndRun实现的。

                    while (isMissing) {
                        try {
                            isMissing = getInstructionsAndRun(waitNotify, false);
                            preyConfig.setMissing(isMissing);
                            if (isMissing){
                                PreyRunnerService.interval = preyControlStatus.getDelay();
                                PreyRunnerService.pausedAt = System.currentTimeMillis();
                                PreyLogger.d( "Now waiting [" + preyControlStatus.getDelay() + "] minutes before next execution");
                                Thread.sleep(preyControlStatus.getDelay() * PreyConfig.DELAY_MULTIPLIER);
                            } else
                                PreyLogger.d( "!! Device not marked as missing anymore. Stopping interval execution.");
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        } catch (PreyException e) {
                            //PreyWebServices.getInstance().setMissing(ctx, false);
                            //preyConfig.setMissing(false);
                            //break;
                            PreyLogger.e("Error while running on interval: ",e);
                        }
                    }

getInstructionsAndRun里会访问Prey的web service得到一些配置(即上面的XML),根据这些配置会创建一些job,并调用ActionsController.getInstance(ctx).runActionGroup(actions,waitNotify,isMissing);来跑这些job。

在这个里面创建了JobsGroup,并将这个JobsGroup加到JobsQueue里,JobsQueue里会去跑JobsGroup。

    /**
     * Adds a JobsGroup to the queue, and starts immediately to execute jobs on
     * that group. If there was a previously group running, this method finishes that execution first.
     * 
     * @param jobsGroup
     *            group to add to the queue
     */
    public void addAndRunJobGroup(JobsGroup jobsGroup, Context ctx, boolean isMissing) {
        this.finishRunningJobs(ctx);
        this.jobs.put(Long.valueOf(jobsGroup.getId()), jobsGroup);
        jobsGroup.run(this, isMissing);
    }

JobsGroup里会去跑每个ActionJob,每个ActionJob持有对一个PreyAction的引用,ActionJob.run()调用PreyAction的execute()。

    public void run() {
        try {
            this.startedAt = System.currentTimeMillis();
            this.action.execute(this, this.ctx);
        } catch (PreyException e) {
            PreyLogger.e("Error while running job [" + id + "] :" + e.getMessage(), e);
        }
    }

在PreyAction execute的代码里,当这个job跑完之后会调用actionJob.finish(result);继而调用this.jobsGroup.jobFinished(this);在这个方法里会检查这个JobsGroup里每个Job是不是都跑完了。

    public void jobFinished(ActionJob job) {
        this.syncResults.add(job.getResult());
        this.runningJobs.remove(Long.valueOf(job.getId()));
        if (this.runningJobs.isEmpty())
            this.queue.groupFinished(this, this.ctx);
    }

如果跑完会调用this.queue.groupFinished(this, this.ctx);继而调用ActionsController.jobGroupFinished。

    public void jobGroupFinished(ArrayList<ActionResult> results, Context ctx) {
        ArrayList<HttpDataService> dataToBeSent = new ArrayList<HttpDataService>();
        if (results.size() > 0) {
            for (ActionResult aResult : results) {
                dataToBeSent.add(aResult.getDataToSend());
            }
            PreyWebServices.getInstance().sendPreyHttpReport(ctx, dataToBeSent);
        }
        waitNotify.doNotify();
    }

在这个方法里会综合每个job的数据并调用Prey的web service发report。

控制面板

控制面板上设置手机为Missing状态,怎么能让手机知道呢? 答案是通过Google的C2DM(不过现在C2DM已经被废弃了。替代品是GCM),简单讲就是设备需要一个Google账号,在Prey启动的时候,会注册到Google的C2DM server(PreyConfig.getPreyConfig(getApplicationContext()).registerC2dm();),这时Google会返回一个注册的ID,APP拿到之后会访问Prey的web service,web service会将这个ID记下来,当在控制面板上设置手机为Missing状态时,web service会发一个message到Google的service,Google的service接收到之后会根据之前注册的ID,定位到你的手机,并会发intent com.google.android.c2dm.intent.RECEIVE到你的手机。

同样APP端会有一个Receiver处理这个intent。

<!-- Only C2DM servers can send messages for the app. If permission is not set - any other app can generate it -->
<receiver android:name="com.prey.receivers.C2DMReceiver" android:permission="com.google.android.c2dm.permission.SEND">
    <!-- Receive the actual message -->
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
        <category android:name="com.prey"/>
    </intent-filter>
    <!-- Receive the registration id -->
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
        <category android:name="com.prey"/>
    </intent-filter>
</receiver>

C2DMReceiver的关键代码如下:

private void handleMessage(Context context, Intent intent) {
        String pushedMessage = intent.getExtras().getString(PreyConfig.getPreyConfig(context).getc2dmAction());
            if (pushedMessage != null) {
                PreyLogger.i("Push message received " + pushedMessage);
                try {
                    PushMessage pMessage = new PushMessage(pushedMessage);
                    PreyConfig.getPreyConfig(context).setRunOnce(pMessage.getBody().indexOf("run_once") >= 0);
                    boolean shouldPerform = pushedMessage.indexOf("run") >= 0;
                    boolean shouldStop = pushedMessage.indexOf("stop") >= 0;
                        
                    if (shouldPerform) {
                        PreyLogger.i("Push notification received, waking up Prey right now!");
                        PreyController.startPrey(context);
                    } else if (shouldStop) {
                        PreyLogger.i("Push notification received, stopping Prey!");
                        PreyController.stopPrey(context);
                    }
                } catch (PreyException e) {
                    PreyLogger.e("Push execution failed to run", e);
                }
            }
    }

可以看到除了触发的来源不一样,后续的处理都是一样的。

posted @ 2013-01-24 21:40  robbietree  阅读(208)  评论(0编辑  收藏  举报