連載
» 2016年02月04日 05時00分 公開

実業務でちゃんと使えるAndroidアプリ開発入門(1):ACTION_OUTSIDEが切り開くAndroidアプリ間連携の可能性 (4/4)

[緒方聡,株式会社ゆめみ]
前のページへ 1|2|3|4       

今回の肝、ACTION_OUTSIDE時の挙動などを「onTouch()」メソッドで設定

 次にonTouch()を見てみます。

@Override
public boolean onTouch(View v, MotionEvent event) {
    if (event.getAction() != MotionEvent.ACTION_OUTSIDE) {  // 【1】
        return false;
    }
    String packageName = getTopActivityPackageName();  // 【2】
    boolean matched = packageName.equals("com.android.chrome") || packageName.equals("com.android.browser");  // 【3】
    if (!matched) {
        return false;
    }
    // 【4】
    mTimestamp[0] = event.getEventTime();
    Arrays.sort(mTimestamp);
    long diff = 0;
    for (int i = 0; i < mTimestamp.length - 1; i++) {
        diff += mTimestamp[i + 1] - mTimestamp[i];
    }
    if (diff < 500) {  // 【5】
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
        Toast.makeText(getApplicationContext(), "Launch from " + packageName, Toast.LENGTH_SHORT).show();
    }
    return false;
}
onTouch()メソッド

 【1】でタッチイベントがACTION_OUTSIZEかどうかをチェックし、そうでなければ何もせずにメソッドを抜けます。今回のアプリの作りでは、ACTION_OUTSIDE以外が通知されてくることはないのですが、デバッグで一時的にViewを表示させることも考慮して、このチェックを入れています。

 【2】で独自メソッドgetTopActivityPackageName()を呼び出して、タッチイベント発生時に表示されている画面のパッケージ名を取得します。

 そのパッケージ名がブラウザに当たるかどうかを【3】で判定して、ブラウザではなければメソッドを抜けます。サンプルアプリでは「Android標準ブラウザ」「Chrome」の両方と比較しています。ここを変更することで、任意のアプリのタッチイベントにフックさせて処理を行えるようになります。

 【4】から【5】の間で、所定のアクションが行われたかを判定しています。サンプルアプリでは500ms未満で3回タップされたことをトリガーとすることにしています。ここを任意のアクションに変更することで、任意のアクションに任意の処理を関連付けることが可能になります。

コラム「ACTION_OUTSIDEの制限」

 ただし、ACTION_OUTSIDEは通常のタッチイベントと異なり、以下のような制限があります。

  • 自身のアプリ以外では座標が通知されない(常にx=0.0、y=0.0で通知される)
  • ACTION_DOWN相当のタイミングで1回だけACTION_OUTSIDEが通知される
  • ACTION_UP、ACTION_MOVE相当のイベントを取得できない
  • id、pointerCount、historySizeなどのプロパティが全て0

 イベントを識別可能なプロパティはeventTimeとdownTimeしかありません(ACTION_OUTSIDEでは両者は同じ値です)。つまり、トリガーとなるアクションはシングルタップ、ダブルタップ、トリプルタップ、あるいは三三七拍子などのイベント発生間隔でしか定義できません。


 【5】以降では、トリガーとなるアクションから実行される処理を記述します。このサンプルでは自身のActivityを起動していますが、ここで今度は「操作可能なViewを同様の仕組みで表示し、具体的な処理をユーザーに選択させる」というのが実用的かもしれません。

使用履歴を取得する「getTopActivityPackageName()」メソッド

 最後にgetTopActivityPackageName()を見てみます。

@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
private String getTopActivityPackageName() {
    String packageName = "";
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {  // 【1】
        ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> list = am.getRunningAppProcesses();
        packageName = list.get(0).processName;
    } else { // 【2】
        UsageStatsManager usm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);  // 【3】
        long endTime = System.currentTimeMillis();
        long beginTime = endTime - 7 * 24 * 60 * 60 * 1000;
        List<UsageStats> list = usm.queryUsageStats(UsageStatsManager.INTERVAL_BEST, beginTime, endTime);
  // 【4】
        if (list != null && list.size() > 0) {
            SortedMap<Long, UsageStats> map = new TreeMap<>();
            for (UsageStats usageStats : list) {
                map.put(usageStats.getLastTimeUsed(), usageStats);
            }
            if (!map.isEmpty()) {
                packageName = map.get(map.lastKey()).getPackageName();  // 【5】
            }
        }
    }
 
    return packageName;
}

 【1】でAndroid 5未満であるかどうかを判定し、そうであればActivityManagerで現在表示されているプロセスのパッケージ名を取得します。そうでなければ【2】で使用履歴から現在表示されているプロセスのパッケージ名取得を試みます。

 【3】でUsageStatsManagerを取得し、【4】のようにして使用履歴のリストを取得します。使用履歴の期間は1週間前から現在としています。結構長めに設定しているのには理由があり、実際に動作させて試してみたところ、使用履歴はタスクを切り替えるなどを行わなければ取得できなくなるようです。

 例えば1週間前からずっとブラウザだけを使用していて、一度もタスクを切り替えていない場合、「UsageStatsManager#queryUsageStats(int, long, long)」は、なぜか使用履歴を返してくれなくなります。タスクを切り替えた場合は再び1週間前からの使用履歴を取得してくれるようです。

 「UsageStatsManager#queryUsageStats(int, long, long)」のJavadocにある以下の例の通り、「queryUsageStats(INTERVAL_YEARLY, 2013, 2016)」で使用履歴を取得しようとしたのですが、できませんでした。

intervalType = INTERVAL_YEARLY
beginTime = 2013
endTime = 2015 (exclusive)
 
Results:
2013 - com.example.alpha
2013 - com.example.beta
2014 - com.example.alpha
2014 - com.example.beta
2014 - com.example.charlie 

 今のところ、INTERVAL_BESTと、インターバルを長めに設定したミリ秒を渡すのが良さそうです。

 【5】で一番新しい使用履歴を現在表示されている画面のアプリとしています。

次回は「Activity、Fragment、Viewおよびアプリのライフサイクルとコールバック」

 今回はACTION_OUTSIDEとSYSTEM_ALERT_WINDOWを組み合わせて、他のアプリのタッチイベントを監視して動作する常駐アプリを作りながら、必要な機能について説明していきました。タッチイベントから取得可能なプロパティがほとんどないのがつらいところですが、アイデア次第で利用シーンは広がると思います。筆者はユーザー名とパスワードなどのスニペットをクリップボードにコピーするツールを作ってみようと思います。

 最後に、本稿を執筆するに当たり以下の記事を参考にさせていただいたことをお礼申し上げます。

 次回は「Activity、Fragment、Viewおよびアプリのライフサイクルとコールバック」を予定しています。

筆者紹介

緒方聡

株式会社ゆめみ所属のエンジニア。Applet、デスクトップJava、サーバサイドJavaの業務開発を経て、ケータイJava、組み込みJavaから現在はAndroidを中心にJavaに関わる。他の執筆記事は「Androidで動く携帯Javaアプリ開発入門」「携帯アプリを作って学ぶJava文法の基礎」など。iOS開発もたしなみ、Java以外ではHaskellやC/C++、Luaを好む。


前のページへ 1|2|3|4       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。