Android PendingIntentのフラグ

Android

Android PendingIntentのフラグ

PendingIntentは今すぐスタートするIntentではないけど、タイミングが来たら使うよー というIntentです。

 PendingIntent replyPendingIntent =
        PendingIntent.getActivity(context, 0, replyIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT);

などとして作ります。

今回私がハマってしまったのは、Android Wearの開発にて出てきた次のような問題です。

  1. メッセージ受信
  2. そのメッセージを音声で返事する

というものですが、2回目以降の音声の返事が、1回目のしか取れない…。

なーんでかっ。

というと、下記のようにやっていたのですが

 Intent intent = getIntent();
 CharSequence voice_massage = getMessageText(intent);
 
 private CharSequence getMessageText(Intent intent) {
    Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
    if (remoteInput != null) {
        return remoteInput.getCharSequence(EXTRA_VOICE_REPLY);
    }
    return null;
 }

そもそも、このgetIntent()で取得するIntentの中身が2回目以降更新されていない。
最初は、Intentに設定するフラグの方がおかしいのかな?
と思ってました。

 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 

というアレです。

しかし、これではなく、PendingIntentにつけるフラグが問題だったんです。
3番目の引数のフラグは、FLAG_CANCEL_CURRENTでないといけなかったんですね。
一番最初に書いた、PendingIntentのコードは下記のようになります。

 PendingIntent replyPendingIntent =
        PendingIntent.getActivity(context, 0, replyIntent,
                        PendingIntent.FLAG_CANCEL_CURRENT);

下記の公式ドキュメントを見ますと、
http://developer.android.com/reference/android/app/PendingIntent.html

FLAG_UPDATE_CURRENT…既存のPendingIntentがあれば、それはそのままでextraのデータだけ置き換える

FLAG_CANCEL_CURRENT…既存のPendingIntentがあれば、それをキャンセルして、新しいPendingIntentを実行する

ということです。
今回はIntent自体新しくしないといけなかったので、FLAG_CANCEL_CURRENTを選択しないといけなかったわけですね!

メッセージは新しくなっていたので、フラグのことを気にしていなかったのですが、なんか意外な落とし穴でした!

 

Android Parcelの中身がおかしくなる

Android

例えば、IntentからIntentになんらかのオブジェクトを受け渡したいときに、Parcelableを利用します。
ですが、このParcelableを受け取ったときに、思いがけない中身になっていたりします。
Googleさんの公式ドキュメントでは、
https://developer.android.com/reference/android/os/Parcelable.html

 //書き込み
      public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mData);
     }
 //読み出し
     private MyParcelable(Parcel in) {
         mData = in.readInt();
     }

となっていて、一つのデータしかありませんが、実際のところは複数のデータとかが存在していると思います。

その時、書き込んだ順番と、読みこむ順番を合わせておかないといけません。

 //書き込み
 @Override
 public void writeToParcel(Parcel dest, int flags) {
 
        dest.writeString(hogehoge);
        dest.writeString(mokemoke);
  
    }
 //読み込み
 public User(Parcel in) {
 
        hoghoge = in.readString();
        mokemoke = in.readString();
 
    }

Android Notificationからレシーバーを呼びだす

Android

Android Notificationからレシーバーを呼びだす

スマホの上のバーに出るNotificationから、レシーバーを起動させるサンプルスクリプトです。

 //呼び出し元のActivity
    @Override
    public void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);               
        setContentView(R.layout.test);
        
        Button btn1 = (Button) findViewById(R.id.btn1);
        btn1.setOnClickListener(this);
        
        Intent intent = new Intent("STOP_ASYNC_TASK_VIDEO_UPLOAD");
        PendingIntent contentIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);        
 
        NotificationCompat.Builder b = new NotificationCompat.Builder(this);
 
        b.setAutoCancel(true)
         .setDefaults(Notification.DEFAULT_ALL)
         .setWhen(System.currentTimeMillis())         
         .setSmallIcon(R.drawable.ic_launcher)
         .setTicker("テスト")            
         .setContentTitle("テストタイトル")
         .setContentText("テストだってばよ!!")
         .setDefaults(Notification.DEFAULT_LIGHTS| Notification.DEFAULT_SOUND)
         .setContentIntent(contentIntent)
         .setContentInfo("Info");
 
        NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(1, b.build());
    
    }
 //AndroidManifest.xml
     <receiver
            android:name=".receivers.StopVideoUploadReceiver"
            android:exported="false" >
            <intent-filter>
                <action android:name="STOP_ASYNC_TASK_VIDEO_UPLOAD" >
                </action>
            </intent-filter>
        </receiver>
 //レシーバー本体
 public class StopVideoUploadReceiver extends BroadcastReceiver {
 
    private Handler handler;
    private final static String TAG = "StopVideoUploadReceiver";
 
    @Override
    public void onReceive(Context context, Intent intent) {
        
        Log.d(TAG, "レシーバー起動");
        
        if(handler !=null){
            
            Message msg = new Message();
            
            handler.sendMessage(msg);
        
        }
    }
 }

ちょっとつまらないことで実ははまりまして、呼び出し元のPendingIntentを

 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);        

とやっていて、どうしてレシーバーが呼び出されないの??ってずっとなてtました。
getActivityじゃなくって、getBroadcastしないと、Broadcastされないんですね!!

ボタンを押されたらレシーバー発動するのは、次のように作りますが、この時ももちろんsendBroadcastなわけです。

 public void onClick(View v) {
 
        switch (v.getId()) {
            case R.id.btn1:
                Intent intent = new Intent("STOP_ASYNC_TASK_VIDEO_UPLOAD");
                sendBroadcast(intent);
                break;
        }
    }

Android NotificationCompat.BuilderのaddActionで指定したボタンが消える

Android

Android NotificationCompat.BuilderのaddActionで指定したボタンが消える

AndroidのSDKのバージョンが16以上から、Notificationにボタンなどを表示して、Notificationエリアからアプリのいろんなことができるようになりました。

試してみていると、時々出るときと出ないときが・・・?

調べてみると、なんと他の通知が出ているとき、そして他の通知が上位に表示されているときは、ボタンが出ないらしい・・・

下記のサイトさんで紹介されてました、ありがとうございます!!
http://stackoverflow.com/questions/18249871/android-notification-buttons-not-showing-up

ってか、ホント、それ早く言ってよ…って感じですね。

 setPriority(Notification.PRIORITY_MAX)

とやれば、一番上に通知を持ってこれるので、とりあえずの解決になります!!

 NotificationCompat.Builder b = new NotificationCompat.Builder(this);
  
 b.setAutoCancel(true)
         .setDefaults(Notification.DEFAULT_ALL)
         .setWhen(System.currentTimeMillis())         
         .setSmallIcon(R.drawable.ic_launcher)
         .setTicker("テスト")            
         .setContentTitle("テストタイトル")
         .setContentText("テストだってばよ!!")
         .setDefaults(Notification.DEFAULT_LIGHTS| Notification.DEFAULT_SOUND)
         .setContentIntent(contentIntent)
         .addAction(R.drawable.close_cross, "キャンセル",
                            contentIntent)
         .setPriority(Notification.PRIORITY_MAX)
         .setContentInfo("Info");
  
 NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
 notificationManager.notify(1, b.build());

Android Mock Locationsを利用して、位置情報のテストをする

Android

Android Mock Locationsを利用して、位置情報のテストをする

結論から言うと、失敗した話なんですが、記録のために書いておきます。

AndroidのGoogle Play Serviceを利用する位置情報APIについて最近書いておりますが、中でも下記のモックの位置情報で色々と位置情報アプリのテストができる、というのは魅力的ですね。

Testing Using Mock Locations
http://developer.android.com/training/location/location-testing.html#TestingTips

これからは、位置情報のテストをするために、実際に端末を持って動き回る必要はないのです。
いいのは、たとえば今まで他の偽位置情報送信アプリなどを使っていては、できなかった精度や速度の設定なんかもできるところです。
残念ながら、activity recognitionはテストできないみたいですね。
ちなみにactivity recognitionは、今歩いているのか、止まっているのか、車に乗っているのか、などが判定できる仕組みです。

しかも、移動する間隔なども決められるので、テストにはもってこいです。

自分の作ったアプリに変更を加えてどうこうするわけではなく、まずはGoogleさんの作ったテストアプリを使うと、Fused location providerにモックの位置情報を送れるので、それを利用しましょう。

下記のページの右上にある、青い「Download the Sample」というボタンをクリックして、サンプルアプリをダウンロードしましょう。

http://developer.android.com/training/location/location-testing.html#RunProvider

アプリをダウンロードしたら、普通にEclipseにインポートします。

私の環境では、SendMockLocationServiceがの

 elapsedTimeNanos = 
 SystemClock.elapsedRealtime();//elapsedRealtimeNanos();
 mockLocation.setElapsedRealtimeNanos(elapsedTimeNanos);

がどうしても動作しませんでした。これって、ビルドターゲットがAPIレベル17以降にしないとsetElapsedRealtimeNanosがundefinedでエラーになります。

さて、これをmocklocationアプリと呼びますが、mocklocationアプリを立ち上げると、次のような画面になります。
Pause before test(secs)というボックスには、テストをスタートする前に、何秒待つか、を記入します。テストしたいアプリは後で立ち上げる、等と言う時に便利ですね!
Send intervalというボックスには、それぞれの位置情報を移動する間隔を記入します。

mock_location.png

開始する時は移動しなくてよい場合は、Run onceをタップ、ぐるぐる移動させたい場合はRun continuouslyをタップします。
Run continuouslyをすると、Stop testをタップするまで移動が終了しません。

mocklocationアプリには、最初カリフォルニアの位置情報が書かれています。

LocationUtils.javaの次の部分に、自分がテストしたい緯度、経度、精度を入れましょう。

 public static final double[] WAYPOINTS_LAT = {
    37.377166,
    37.380866,
    37.381224,
    37.382008,
    37.385486,
    37.387021,
    37.384847,
    37.385461};
 
    // An array of longitudes for constructing test data
    public static final double[] WAYPOINTS_LNG = {
    -122.086966,
    -122.086945,
    -122.086344,
    -122.086151,
    -122.083941,
    -122.083104,
    -122.078683,
    -122.078265};
 
    // An array of accuracy values for constructing test data
    public static final float[] WAYPOINTS_ACCURACY = {
        3.0f,
        3.12f,
        3.5f,
        3.7f,
        3.12f,
        3.0f,
        3.12f,
        3.7f
    };

準備ができたら…これで動作するわけではなく!
自分がテストしたい、位置情報アプリの方にもちょっとだけ変更が必要です!!

 @Override
 public void onConnected(Bundle dataBundle) {
 		
 	    // モックモード開始
 	    mLocationClient.setMockMode(true);
 
 	}

と書く必要があります。

もちろん、下記の条件も必要です。

  1. Google play のLocation を利用していること。上記のsetMockModeの以前に、下記が準備されていることです。
  2.     public LocationClient mLocationClient;
        ...
        // Connect to Location Services
        mLocationClient.connect();
    
    1. テスト端末で、開発者向けオプション→疑似ロケーションを許可 にチェックが入っていること
    2. テストするアプリのAndroidManifest.xmlにて、
    3.  <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
      

      が宣言されていること

      さて、それらが終わったら、mocklocationアプリを動かしてみましょう!

      テストしたい位置情報アプリで、位置情報が動作していれば、成功★!です。[smile]