安卓系统:完全控制手机(展示模式),是否可能?如何实现?

33

我们有一个程序,安装在手机上并将手机借给用户使用一段时间。我们希望这些手机只用于运行我们的应用程序(无电话、无游戏等)。这些手机将被root(超级管理员权限)。

所以我们需要以下功能:

  • 全屏运行,不显示其他任何内容
  • 主页按钮和其他设备按钮都不能使用
  • 我们的应用程序会在启动时自动运行

它不必是“防黑客级别”,但应足以防止普通用户对设备进行操作。

这是否可行?我已经在Symbian和Windows Mobile上做过类似的事情,但在Android上我没有太多经验。如何实现这个目标?

更新2015年: 如果您不介意限制您的应用程序只针对单独的手机厂商,三星已经推出了KNOX SDK,可以轻松实现展示模式和更多功能而无需root手机。详情请访问:https://seap.samsung.com/developer/sdk/knox-standard-android

4个回答

50

是可以的,但你无法控制 Home 键结束通话键 的行为。

要全屏,请在清单文件中的活动标签添加 android:theme="@android:style/Theme.NoTitleBar.Fullscreen"

要禁用来电,你需要监听电话:

import android.app.Service;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;

public class MyPhoneStateListener extends Service{

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
            StateListener phoneStateListener = new StateListener();
            TelephonyManager telephonymanager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
            telephonymanager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

    }

    class StateListener extends PhoneStateListener{
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            super.onCallStateChanged(state, incomingNumber);
            switch(state){
                case TelephonyManager.CALL_STATE_RINGING:
                    //Disconnect the call here...
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    break;
                case TelephonyManager.CALL_STATE_IDLE:
                    break;
            }
        }
    };

    @Override
    public void onDestroy() {

    }
}

注意:在停止服务时,不要忘记移除监听器并将这些权限添加到您的清单文件中:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

并且以编程方式断开通话:

try{
    TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
    Class c = Class.forName(manager.getClass().getName());
    Method m = c.getDeclaredMethod("getITelephony");
    m.setAccessible(true);
    ITelephony telephony = (ITelephony)m.invoke(manager);
    telephony.endCall();
} catch(Exception e){
    Log.d("",e.getMessage());
}

注意:添加此文件以断开通话: http://dl.dropbox.com/u/31740476/ITelephony.aidl

要禁用按键,您需要覆盖:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if(KeyEvent.KEYCODE_MENU == event.getKeyCode() || KeyEvent.KEYCODE_DPAD_LEFT==event.getKeyCode()
            || KeyEvent.KEYCODE_DPAD_DOWN==event.getKeyCode() || KeyEvent.KEYCODE_DPAD_RIGHT==event.getKeyCode()
            || KeyEvent.KEYCODE_DPAD_UP==event.getKeyCode() || KeyEvent.KEYCODE_DPAD_CENTER==event.getKeyCode()
            || KeyEvent.KEYCODE_BACK==event.getKeyCode())
    {
        return false;
    }
    return true;
}

按下Home键后,将回到主屏幕,为了克服这个问题,您需要实现一个服务,并在其中实现一个无限线程来重新启动您的应用程序,就象这样:

public class AppTrackingService extends Service {

    private RunnableThread thread;
    private Context ctx;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void onCreate(){
        super.onCreate();
        ctx = AppTrackingService.this;
        thread = new RunnableThread();
    }

    public void onStart(Intent intent, int startid) {
        try{
            if(thread==null) thread = new RunnableThread();
            thread.startThread();
        }catch(Exception e){  }
    }

    class RunnableThread extends Thread {

        Handler back_handler = new Handler();
        boolean isContinue = false;

        public RunnableThread(){
            isContinue = false;
        }

        public void setIsContinue(boolean val){
            this.isContinue = val;
        }

        public void startThread(){
            isContinue = true;
            start();
        }

        public void run(){
            ActivityManager actMngr = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
            while(isContinue){
                try{
                //Maintain a boolean "isyourapprunning" to know if your app was running or not....
                    if(isyourapprunning){
                    String runningPkg = actMngr.getRunningTasks(1).get(0).topActivity.getPackageName();
                        if (!runningPkg.equals(ctx.getPackageName())){
                                launchApp(ctx.getPackageName());
                            }
                        Thread.sleep(2500);  //2.5 secs
                    }else{
                        isContinue = false;
                        stopSelf();
                    }

                }catch(Exception e){ }
            }//end of while loop
        }

        protected void launchApp(String packageName) {
            Intent mIntent = getPackageManager().getLaunchIntentForPackage(packageName);
            mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            mIntent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
            if (null != mIntent) {
                try {
                    startActivity(mIntent);
                } catch(Exception e) { }
            }
        }
    }
}

编辑

你需要添加以下权限才能结束通话:

<uses-permission android:name="android.permission.CALL_PHONE" />

你可以使用以下的AIDL文件:

package com.android.internal.telephony;

/**
 * Interface used to interact with the phone.  Mostly this is used by the
 * TelephonyManager class.  A few places are still using this directly.
 * Please clean them up if possible and use TelephonyManager instead.
 *
 * {@hide}
 */
interface ITelephony {
    /**
     * End call if there is a call in progress, otherwise does nothing.
     *
     * @return whether it hung up
     */
    boolean endCall();

    /**
     * Silence the ringer if an incoming call is currently ringing.
     * (If vibrating, stop the vibrator also.)
     *
     * It's safe to call this if the ringer has already been silenced, or
     * even if there's no incoming call.  (If so, this method will do nothing.)
     *
     * TODO: this should be a oneway call too (see above).
     *       (Actually *all* the methods here that return void can
     *       probably be oneway.)
     */
    void silenceRinger();
}

我无法使用.aidl文件。有什么问题吗? - Pankaj Kumar
3
我将该文件添加到我的源代码项目中,但在ITelephony telephony = (ITelephony)m.invoke(manager)这一行出现错误,其中ITelephony未定义。 - Pankaj Kumar
@Vineet Shukla 先生,解释得很好,加一分给您。 - Nikhil Agrawal
在代码Method m = c.getDeclaredMethod("getITelephony");处出现了NoSuchMethodException异常,如何避免它? - uniruddh
Lollipop设备上的来电通知怎么办?之后呢?我该如何禁用或隐藏它...有解决方案吗? - tsiro
显示剩余6条评论

5
Vineet的解决方案可行。但是我认为还需要两个权限,这些权限可以从这里找到。
所需的权限如下:
android.permission.READ_PHONE_STATE、android.permission.MODIFY_PHONE_STATE、android.permission.CALL_PHONE
尽管我只添加了以下两个权限,它仍然可以正常工作: <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.CALL_PHONE"/>

1
@Vineet Shukla:仅拥有READ_PHONE_STATE权限是不够的,您还需要CALL_PHONE权限才能结束通话。
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>

“MODIFY_PHONE_STATE”(如Zaki Choudhury所说)是一个系统权限,只能在已经root的设备上使用,并且代码的任何部分都不需要它。”

1

1
+1. 对我来说有些帮助!谢谢你的贡献。请继续分享这样有用的信息。像你这样的开发者使开源成为它所应该的样子。 - skygeek
我有一个问题,假设我屏蔽了谷歌市场,但有人正在玩赛车游戏并到达谷歌市场。您编写的代码可以正常工作并退出到主启动器,但当我重新打开游戏时,它会显示我上次离开时的最后一个应用内购买屏幕,而不是从头开始。您能帮我解决这个问题吗? - skygeek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接