Android ダイレクトブートを検知する

Android

Android N (7.0)以降から、ダイレクトブートという仕組みが取り入れられました。
普通に電源をつけた起動と、ユーザーが起動後ロックを外す時点が変わります。

Android公式 ダイレクトブートのサポート

開発者視点、何が困るかっていうと、今までインテントフィルタで

 <action android:name="android.intent.action.BOOT_COMPLETED" />

を検知していればよかったのですが、Android N (7.0)以降からはただ単に起動した場合は、BOOT_COMPLETEDを検知できません。(>_<)

 <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />

というのを検知する必要があります。

ユーザーがロックを解除してから動作して十分なアプリならいいんですが、例えば目覚まし時計や、メッセージング的なアプリだとユーザーがロックを外す前に動作する必要がありますよね。

なので、LOCKED_BOOT_COMPLETEDを検知する必要があります。

AndroidManifest.xmlは次のようにします。

 <receiver android:name=".receivers.BootReceiver"
            android:exported="false"
            android:directBootAware="true">
   <intent-filter>
      <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
      <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
 </receiver>

下記もわすれずにつけましょう!

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

そして、この起動検知でわかりにくい部分は下記のストレージのことだと思います。(´・ω・)
それを書きたいがために、これを書いたようなものです。

それは、

Android公式 ダイレクトブートのサポート

のページにありますが、この仕組みのために、「認証情報暗号化ストレージ」と「端末暗号化ストレージ」という仕組みができました。
名称、わかりにくっ!!
今までの、普通に使っていたプリファレンスファイルなどが「認証情報暗号化ストレージ」です。
「端末暗号化ストレージ」が新しくできた仕組みですが、これはダイレクトブートのために、できたんですかね??

「認証情報暗号化ストレージ」はユーザーがロックを外してからアクセスできるストレージです。
「端末暗号化ストレージ」はユーザーがロックを外さないでもアプリがアクセスできるストレージです。

なので、ダイレクトブートで使う情報を格納しておくには、「端末暗号化ストレージ」に保存しないといけません。
「認証情報暗号化ストレージ」のほうが、ユーザーがロックを外してからアクセスできる場所なので、秘匿性の高い情報はこちらに格納しましょう。

情報の保存の仕方や、従来のプリファレンスファイルからの移動は長くなるので、下記のGoogleさんが配布しているサンプルコードを見たほうがいいでしょう。

Android DirectBoot Sample

端末暗号化ストレージに保存したpreferenceファイルを見る

Android スマホやタブレットの枠にはまったような画像をすぐ作る

Android

Android スマホやタブレットの枠にはまったような画像をすぐ作る

提案書とか、Webサイトに載せる時に、結構必要な、スマホの画面をスマホにはまった状態で表示させてるようなアレです。

こういう画像を、一発で作れるツールをGoogleさんが提供しているのを発見しました。
そのうち使いそうなので、載せておきます。

http://developer.android.com/distribute/tools/promote/device-art.html

Android スピナー デザイン性のあるスピナー サンプルコード

Android

Android スピナー デザイン性のあるスピナー サンプルコード

Androidのくるくる回って複数項目から一つの項目が選択できるアレをスピナー(Spinner)と言いますが、ネット上のサンプルには、一行だけのシンプルなスピナーが多かったので、字の大きさや色を変えたり、複数行にするスピナーのサンプルコードを掲載しておきます。

画面はこれです。
spinner.png
車の点検項目を選択する、というやつです。
以前、
Android ListViewの項目をタップして関連する内容を入力してListViewに戻る サンプルコード 
ではリストビューとチェックボックスでやっていましたが、仕様変更により、スピナーでの選択式にかわりました(^_^;

InspectionActivity

 public class InspectionsActivity extends ListActivity implements OnItemSelectedListener {
 
    public static final String TAG = "InspectionsActivity";
    private static final int REQUEST_CODE = 1000; // 適当な数字 ProblemDetailActivityを識別するためだけの数字
    private Spinner inspectionSpinner;
    private String[] inspectionList;
    private ArrayList<HashMap<String, String>> mylist;
    private SimpleAdapter adapter;
    private HashMap<String, String> map_temporary;
    private SimpleAdapter list_adapter;
    private ArrayList<HashMap<String, String>> problemlist;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.inspections);
 
        inspectionSpinner = (Spinner) findViewById(R.id.selection);
 
        mylist = new ArrayList<HashMap<String, String>>();
 
        HashMap<String, String> map1 = new HashMap<String, String>();
        map1.put("item_name", "燃料");
        map1.put("detail", "量・漏れ");
        map1.put("label", "fuel_leaks");
        mylist.add(map1);
 
        HashMap<String, String> map2 = new HashMap<String, String>();
        map2.put("item_name", "エンジン");
        map2.put("detail", "かかり具合・異音");
        map2.put("label", "engine");
        mylist.add(map2);
 
        HashMap<String, String> map3 = new HashMap<String, String>();
        map3.put("item_name", "ブレーキペダル");
        map3.put("detail", "踏みしろ・きき具合・片ぎさ");
        map3.put("label", "brake_pedal");
        mylist.add(map3);
 
        HashMap<String, String> map4 = new HashMap<String, String>();
        map4.put("item_name", "ブレーキレバー");
        map4.put("detail", "引きしろ・きき具合");
        map4.put("label", "brake_lever");
        mylist.add(map4);
 
        adapter = new SimpleAdapter(this, mylist,
                R.layout.inspection_sipinner_item,
                new String[] {
                        "item_name", "detail", "problem_comment"
                },
                new int[] {
                        R.id.item_name, R.id.detail, R.id.problem_comment
                });
 
        adapter.setDropDownViewResource(R.layout.inspection_sipinner_item);
 
        inspectionSpinner.setAdapter(adapter);
        inspectionSpinner.setOnItemSelectedListener(this);
 
        // 初回起動時の対応
        inspectionSpinner.setFocusable(false);
 
        problemlist = new ArrayList<HashMap<String, String>>();
    }
 
    // スピナー内のアイテムが選択された時の動作
    @Override
    public void onItemSelected(
            AdapterView<?> arg0,
            View arg1,
            int arg2,
            long arg3) {
 
        // 初回起動時の動作
        if (inspectionSpinner.isFocusable() == false) {
            inspectionSpinner.setFocusable(true);
            return;
        }
 
        int item_id = arg0.getSelectedItemPosition();
 
        mylist.get(item_id);
        String item_name = (String) mylist.get(item_id).get("item_name");
        String detail = (String) mylist.get(item_id).get("detail");
 
        startIntent(item_id, item_name, detail);
 
    }
 
    protected void startIntent(int item_id, String item_name, String detail) {
 
        Log.d(TAG, item_name);
 
        Intent problem_detail = new Intent(this, ProblemDetailActivity.class);
 
        problem_detail.putExtra("item_id", item_id);
        problem_detail.putExtra("detail", detail);
        problem_detail.putExtra("item_name", item_name);
 
        startActivityForResult(problem_detail, REQUEST_CODE);
 
    }
 

inspection_spinner_item.xnl

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dip"
        android:orientation="horizontal" >
 
        <TextView
            android:id="@+id/item_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:textSize="18sp" />
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dip"
        android:orientation="horizontal" >
 
        <TextView
            android:id="@+id/detail"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="1dp"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textSize="13sp"
            android:textColor="@color/dark_blue" />
    </LinearLayout>
 
 </LinearLayout>

inspections.xml

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" > 
 
    <Spinner
        android:id="@+id/selection"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="5dip"
        android:entries="@array/inspectionList"
        android:prompt="@string/inspection" >
    </Spinner>
 
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal" >
 
        <Button
            android:id="@+id/finish"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginRight="5dp"
            android:background="@drawable/reserved_button"
            android:onClick="onClickButton"
            android:padding="10dip"
            android:text="@string/inspection_finished"
            android:textSize="18sp" />
    </LinearLayout>
 
 </LinearLayout>

Android ジオコーディングに失敗する

Android

Android ジオコーディングに失敗する

Androidの開発で、住所から緯度・経度に変換するジオコーディング、緯度・経度から住所に変換するリバースジオコーディングが

 Geocoder geo = new Geocoder(parent_activity, default_language);

などとやればできますが、エミュレーターや私が持っているSamsung Galaxy Tabでは、これが下記のエラーになって動作しません。

 java.io.IOException: Service not Available

下記のサイトに解決方法があり、WebのGoogle Map APIを呼び出して、帰り値を利用するとすれば解決しました。

http://stackoverflow.com/questions/5205650/geocoder-getfromlocation-throws-ioexception-on-android-emulator

Android ショートカットを作成、既にある場合は作らない方法

Android

Android ショートカットを作成、既にある場合は作らない方法

ホーム画面にショートカットアイコンを作成するのは、本来ならばユーザーがアプリ一覧から長押しとかでユーザーが行うことですが、最近のアプリでは、アプリ自身がやってくれるのが流行のようです。
ホーム画面にあれば、忘れられる可能性も低くなりますもんね!

下記のURLにあったものを参考にしておりますが、一点、既にショートカットアイコンがある場合に何度も作成してしまうのを避ける処理を入れました。

http://monoist.atmarkit.co.jp/mn/articles/1203/16/news006.html

 private void makeAppShortCut(Context context){
   // アプリケーションを起動するためのIntentを作成
    Intent targetIntent = new Intent(this, MenuActivity.class);
    targetIntent.setClassName( context, "takuru.driver.MenuActivity" );
    makeShortCut(context, targetIntent, getString(R.string.app_name), R.drawable.ic_launcher );
    }
 	    
 private void makeShortCut(Context context, Intent targetIntent, String title, int iconResource){
  // ショートカット作成依頼のためのIntent
   Intent intent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
  	 
  // ショートカットのタップ時に起動するIntentを指定
   intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, targetIntent);
 
  //これをつけておかないと、すでにホームアイコンがあっても、また作成してしまう
   intent.putExtra("duplicate", false);
 	 
    Parcelable icon = Intent.ShortcutIconResource.fromContext(context, iconResource);
    intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
  
  // BroadCastを使って、システムにショートカット作成を依頼する
       context.sendBroadcast(intent);
  }   

ダイアログで、「ショートカットを作成しました」「すでにショートカットがあります」というようなものが出ますので、これが嫌な場合は、インストールした直後のような処理にこれを入れたほうがいいでしょう。