2012年7月4日水曜日

Android 3rd party のランチャーで直接 AppWidget を bind する

Jelly Bean で AppWidget 関係にいろいろ手が入りました。

これまで 3rd party のランチャーに AppWidget を bind するには、AppWidgetManager.ACTION_APPWIDGET_PICK を呼び出して、AppWidget を選択する標準のダイアログを表示するしかありませんでした。 private void pickAppWidget() { int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); startActivityForResult(intent, 1); }


ちなみに Jelly Bean でこの Intent を呼ぶとこうなります。
ちょっとどうなのよ。。。



一方で、ICS 以降のデフォルトの Launcher アプリでは、Launcher 内に AppWidget のプレビューを表示し、そこからドラッグ&ドロップして選択されたものを直接 bind しています(つまり、上記のダイアログを呼び出さない)。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/packages/apps/Launcher2/src/com/android/launcher2/Launcher.java#1509 1520 int appWidgetId = getAppWidgetHost().allocateAppWidgetId(); 1521 AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName); 1522 addAppWidgetImpl(appWidgetId, info);
この bindAppWidgetId() メソッドは ICS までは 3rd party からも呼べました(@hide ではなかった)。 呼べますが、android.permission.BIND_APPWIDGET パーミッションが必要で、かつこのパーミッションは system レベルなので 3rd party から呼ぶと SecurityException で落ちます。 (でも BIND_APPWIDGET のドキュメントにはその情報がないので、ちょっと不親切。まぁ、ちょっとぐぐればでるけどね http://stackoverflow.com/questions/3520564/security-exception-while-calling-bindappwidgetid とか。)

例えば、以下のコードは上記のパーミッションを宣言していても SecurityException で落ちます。

private void bindAppWidget() { // this will be fail because of SecurityException ComponentName componentName = new ComponentName( "com.example.android.mybatterywidget", "MyBatteryWidgetProvider"); int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); AppWidgetManager.getInstance(self).bindAppWidgetId(appWidgetId, componentName); }

Jelly Bean では新しくメソッドが追加され、3rd party のランチャーでも直接 AppWidget を bind することができるようになっています。それに伴って bindAppWidgetId() メソッドはなくなり(たぶん @hide になったのだと思われるが、現状2012/7/4ではコードがまだ公開されていないのでわからない。)、bindAppWidgetIfAllowed() メソッドが追加されました。

このメソッドを呼んで bind が成功した場合は true が返ってきます。これまでの標準 Launcher のように android.permission.BIND_APPWIDGET パーミッションがあるアプリは成功します。3rd party アプリから呼んだ場合は、ユーザーがこのコンポーネントに対して常に AppWidget の bind を有効にしている場合は成功します。有効になっていない場合は失敗するので false が返ってきます。この場合はユーザーに有効にしてくださいとお願いするための Intent を呼びます。それが AppWidgetManager.ACTION_APPWIDGET_BIND です。

こんな感じで使います。

private void bindAppWidget() { ComponentName componentName = new ComponentName( "com.example.android.mybatterywidget", "com.example.android.mybatterywidget.MyBatteryWidgetProvider"); int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); boolean allowed = AppWidgetManager.getInstance(self) .bindAppWidgetIdIfAllowed(appWidgetId, componentName); if (!allowed) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, componentName); startActivityForResult(intent, 2); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 2) { AppWidgetManager mAppWidgetManager = AppWidgetManager .getInstance(this); if (resultCode == RESULT_OK) { int appWidgetId = data.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { AppWidgetProviderInfo appWidget = mAppWidgetManager .getAppWidgetInfo(appWidgetId); // 必要であれば AppWidget の configure を呼びだしたり、ビューを自分のアプリに追加したりする Log.d("appWidgetId", appWidgetId + ""); } } else if (resultCode == RESULT_CANCELED) { int appWidgetId = data.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { // キャンセルされたので削除 mAppWidgetHost.deleteAppWidgetId(appWidgetId); } } return; } super.onActivityResult(requestCode, resultCode, data); }

AppWidgetManager.ACTION_APPWIDGET_BIND を呼ぶとこんな感じの確認ダイアログがでます。



ここでチェックボックスにチェックを入れて Create にすると、この 3rd party ランチャーに常に AppWidget を bind する許可を与えることになります(つまり bindAppWidgetIfAllowed() が成功します)。このデフォルト設定は設定アプリから Clear defaults で外すことができます。

注意!
AppWidgetManager.ACTION_APPWIDGET_BIND のドキュメントには

The system will respond with an onActivityResult call with the following extras in the intent:

EXTRA_APPWIDGET_ID The appWidgetId that you supplied in the original intent.

のように extras として EXTRA_APPWIDGET_ID というキーで AppWidget の Id が返ってくる、とあるのですが、なぜか試したみたところ onActivityResult() に返ってくる Intent が null なのです(上記のコードだと data という変数が null になるので data.getIntExtra() のところで落ちる)。 よって startActivity() するときに現状の AppWidgetId を持っておいてそれを使うしかない感じです。

うーん。。。


1 件のコメント: