ACTION_OUTSIDEが切り開くAndroidアプリ間連携の可能性:実業務でちゃんと使えるAndroidアプリ開発入門(1)(4/4 ページ)
本連載では、バージョンの違いに左右されないスタンダードなアーキテクチャで、セキュリティやパーミッション、テストのしやすさ、開発効率の向上などを考慮した、実業務で使えるAndroidアプリ開発のノウハウを提供していきます。初回は、連載の今後を紹介し、アプリ間連携でさまざまなことができるACTION_OUTSIDEイベントの使い方を解説します。
今回の肝、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;
}
【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を組み合わせて、他のアプリのタッチイベントを監視して動作する常駐アプリを作りながら、必要な機能について説明していきました。タッチイベントから取得可能なプロパティがほとんどないのがつらいところですが、アイデア次第で利用シーンは広がると思います。筆者はユーザー名とパスワードなどのスニペットをクリップボードにコピーするツールを作ってみようと思います。
最後に、本稿を執筆するに当たり以下の記事を参考にさせていただいたことをお礼申し上げます。
- 【Android】getRunningTasksが使えなくなったLollipopでアプリ使用状況を取得する - Qiita
- MarshmallowでSYSTEM_ALERT_WINDOWの権限の扱いが変わった - Qiita
次回は「Activity、Fragment、Viewおよびアプリのライフサイクルとコールバック」を予定しています。
筆者紹介
緒方聡
株式会社ゆめみ所属のエンジニア。Applet、デスクトップJava、サーバサイドJavaの業務開発を経て、ケータイJava、組み込みJavaから現在はAndroidを中心にJavaに関わる。他の執筆記事は「Androidで動く携帯Javaアプリ開発入門」「携帯アプリを作って学ぶJava文法の基礎」など。iOS開発もたしなみ、Java以外ではHaskellやC/C++、Luaを好む。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
「Android N」の開発者向けプレビューが公開
米グーグルは、Androidの次期版「Nリリース」の開発者向けプレビューを公開した。
JVMとAndroid用のオープンソースプログラミング言語「Kotlin 1.0」がリリース
JetBrainsが主導して開発を進めるJVMとAndroid用のオープンソースプログラミング言語の正式版がリリース。既存のコードやインフラとの相互運用性を重視した実用的なプログラミング言語だという。
Androidアプリ向け統合開発環境「Android Studio 2.0」β版が公開
米グーグルが、Androidアプリ向け統合開発環境(IDE)の最新版「Android Studio 2.0」のβ版を発表した。
