Help us understand the problem. What is going on with this article?

Android7世代で動く簡易MDMをねるねるねるねする

More than 1 year has passed since last update.

さよならgetRunningAppProcesses

いまさらかよって話なのですが、Android5.1.1から、getRunningAppProcessesが自分のアプリ名しか返さなくなりました。
5.1.1以前のgetRunningAppProcessesは実行中のアプリのパッケージ名を取得してくるすごいやつだったのですが、5.1.1以降は「俺(自アプリ)?元気元気~」と返事してくるやつになったわけです。

うん、まあそうだよね。わかるー。わかるわー。あぶないよね。実行中のパッケージのリスト取得するとか完全に悪用されるやつじゃんね。そりゃそうよ。この世にいかにいいやつばかりでも、悪いやつはいなくならないもんね。ジーザス…

いや!!!!!困るんですよね!!!!!

こう…簡易MDMみたいなことを清く正しく実装してきた我々。
聖者のごときユーザ様の情報を不正に使おうなんてこれっぽっちも思っていなかったので、これには困ってしまいました。本当です。
そもそもMDMの存在自体がユーザの不正行動を疑っているという前提は聖夜に免じて忘れてほしい。頼むよ。

ともあれ、代替手段を探してみました。

やりたいこと

やりたいことはこんな感じ。

  • ユーザが特定アプリを立ち上げたら、そのアプリの起動をブロックする。
  • MDMとかで会社指定のアプリしか使えないようにしてあるデバイスみたいな使い勝手にしたい(ゲームとか立ち上げると強制的にホームアプリに戻されるような感じ)。

今まではgetRunningAppProcessesとkillBackGroundProcessで実現してました。

代替機能を探せ

さて、今回さよならすることになったgetRunningAppProcessesの後継の機能ですが、UsageStatsManagerというクラスのようです。

候補①:UsageStatsManager

  • 期間指定した間のアプリの起動ログを取得できるクラス
  • ただし、最新履歴の取得は保証しておらず、個人情報保持のため実行時から数分間のアプリ起動ログは取れない(=直近のログが取れない)という制限がある

まあ正当な後継って感じですよね。ちゃんとユーザの使用履歴の不正利用に対する対処も考えられています。おそらく今リリースされている5.1.1以上のタスクキルアプリはこれで動いているんでしょう。

ただこれ、問題があって、

実行時から数分間のアプリ起動ログは取得できない

んですよね。
タスクキル系のアプリならこれでもいいかもしれませんが、MDMのようにユーザが不正なアプリを起動したときに即時ブロックしたい、といった仕様だとこれでは使えないんです。
ちょっとこれじゃ物足りませんね。いい人なんだけど付き合うには…って感じです。保留。保留です。

候補②:AccessibilityService

※注記:はじめに明記しておきますが、使い方として絶対に100%間違ってるので、この方法はたぶんそのうちふさがれると思います。(そもそもAndroid 8.0からバックグランドサービス自体がかなり制限厳しくなるみたいなのでアレなんですが。。。)
なのでバットノウハウ?的な?そんな感じで自己責任のもと動かしてみてください。当方責任は負えません。
なんでこんなこと書くかというと、割と想定通りに動くからなんですけど。

AccessibilityService。名前の通り、ユーザ補助のためのクラスです。
主に視覚障害者の方が利用する際に「どのアプリがフロントに今来ているか」を通知するために使われるようですね。…ん?どのアプリがフロントに今来ているかわかる…?
それってもしかして。

仕様を確認してみましょう。

  • AccessibilityServiceでは、フロントに置かれたWindow内が切り替わったタイミングをイベントとして検知できる。
  • また、その際に「なにがフロントに来ているのか」のパッケージIDが取得できる。
  • この機能を利用する際はAndroid設定>アクセシビリティからパーミッションを許可する必要がある。

ほーう。

練れば

flatAccessibilityService.java
//アクセシビリティイベント発生時のイベントリスナ
public void onAccessibilityEvent(AccessibilityEvent e) {
    int type = e.getEventType();
    String typeName =  "";
    Toast mToast = Toast.makeText(getApplicationContext(),"start",Toast.LENGTH_SHORT);
    AccessibilityNodeInfo src = e.getSource();
    CharSequence name = "";

    switch (type){
        //体感的にTYPE_WINDOW_CONTENT_CHANGEDイベントで監視するのが良さそう
        case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
            name = src.getPackageName();
            typeName = "TYPE_WINDOW_CONTENT_CHANGED + " + name;
            killProsess(name.toString());
            break;
        default:
            typeName = "UNKNOWN";
    }
    if(mToast == null){
        mToast = Toast.makeText(getApplicationContext(),typeName,Toast.LENGTH_SHORT);
    }else{
        mToast.setText(typeName);
    }
    mToast.show();
}

練るほど

flatAccessibilityService.java
//指定のプロセスをKILLする(今回はGoogleMapsをブロック)
public void killProsess(final String prosessName){

    //イベント発火時のフロントアプリのパッケージ名が指定のものと一致したらタスクキルする
    //今回はGoogleMapが立ち上がったらKill
    if(prosessName.equals("com.google.android.apps.maps")){
        //KillできるのはbackgroundProsessのみなので、自分のアプリ(MainActivity)をフロントに持ってくる
        Context context = getApplicationContext();
        Intent intent = new Intent(context, MainActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        try {
            //インテント発行
            contentIntent.send();

            //タスクキル前に一応遅延処理する(本当に必要かは謎、いらないかも)
            mTimer = new Timer(true);
            mTimer.schedule( new TimerTask(){
                @Override
                public void run() {
                    mHandler.post( new Runnable() {
                        public void run() {
                            //タスクキル処理
                            ActivityManager mActivityManager = (ActivityManager) flatAccessibilityService.this.getSystemService(Context.ACTIVITY_SERVICE);
                            mActivityManager.killBackgroundProcesses(prosessName);
                        }
                    });
                }
            }, 1000); //1秒後実行
        } catch (PendingIntent.CanceledException e) {
            e.printStackTrace();
        }
    }
 }

色が変わって…

_人人人人人_
> うまい <
 ̄Y^Y^Y^Y ̄

取り乱しました。

デキチャッターヨ

AccessibilityServiceとkillBackGroundProcessをねるねるねるねしていたら、特定アプリの起動をブロックする簡易MDMのようなものができちゃいました。

何度も言うようにそのうち消されそうな手段ではありますが、今回はまあ…チャレンジということでね…。
他にいいやり方があれば教えてください!随時募集中です。

flat-8-kiki
Python/AWS/機械学習(CNN)/Javascript/Obj-C/Swift/Android Java/C#/UWP/PHPらへんを覗くとき、上記(略)もまたお前を覗いているのだ… ※記載内容は個人に帰属し、所属組織を代表するものではありません。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away