Android6.0 互換モード(4) 端末情報取得はヌルポ系

A+ a-

互換モード:「Android6.0以上の端末で、インストールタイムパーミッションモデルで実行しているアプリケーションが、端末の設定画面から、特定のパーミッションをはく奪された時」の動作確認記事4本目です。

前回までの記事です。互換モードの詳しい説明については(1)を参照してください

互換モードの動作は、仕様把握するよりも、さっさとランタイムパミッション対応に移行したほうがいいと思うので、あまりブログ記事にしても仕方ないと思っているのですが、ガッツリ色々と調べたので備忘も含めて記事にしております。

今回は、READ_PHONE_STATEパミッションが必要なメソッドの互換モードの動作です。READ_PHONE_STATEは、電話番号を取得したりするのに必要なパミッションですが、結論としては互換モードの時にNULLが返ります。戻り値がStringなのであまりないとは思いますが、ヌルポで落ちる可能性がありますので注意が必要です。

その他、READ_PHONE_STATEが所属するPHONEグループのその他パミッションに関しても調査しました。



以下調査結果です。


互換モードでどのような動作をするかを調査するために、targetSdkVersion=21で作成したアプリをAndroid OS 6.0の端末にインストールし、設定画面より電話へのアクセス(READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG及びWRITE_CALL_LOG)を不許可にした時の動作を確認しました。
また、比較のため、同じコードをtargetSdkVersion=23に変更しAndroid 6.0の端末にインストールした時の動作も確認しました(requestPermissionはしていないので必ずSecurityException等なんらかのExceptionを吐く)

パーミッショングループパーミッション説明
PHONEREAD_PHONE_STATE 端末のステータスとIDの読み取り
端末の電話機能へのアクセスをアプリに許可します。これにより、電話番号、端末ID、通話中かどうか、通話相手の電話番号をアプリから特定できるようになります。。
CALL_PHONE 電話番号発信
電話番号への自動発信をアプリに許可します。これにより、予期せぬ発信や料金が発生する可能性があります。なお、緊急通報番号への発信は許可されません。悪意のあるアプリが確認なしで発信し、料金が発生する恐れがあります。
READ_CALL_LOG 通話履歴の読み取り
携帯端末の通話履歴(着信や発信のデータなど)の読み取りをアプリに許可します。これにより、アプリに通話履歴データの保存を許可することになり、悪意のあるアプリによって知らないうちに通話履歴データが共有される恐れがあります。
WRITE_CALL_LOG 通話履歴の書き込み
携帯端末の通話履歴(着信や発信のデータなど)の変更をアプリに許可します。この許可を悪意のあるアプリに利用されると、通話履歴が消去または変更される恐れがあります。


テストコード

端末のステータスとIDの読み取りのテストコード

AndroidManifest.xml

targetSdkVersion=xxx
<uses-permission android:name="android.permission. READ_PHONE_STATE"/>

ソース
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);

// 端末IDを取得
String deviceId = tm.getDeviceId();

// 端末のソフトウェアバージョンを取得
String deviceSoftwareVersion = tm.getDeviceSoftwareVersion();

// 端末のレベル1グループIDを取得
String g roupIdLevel1 = tm.getGroupIdLevel1();

// 端末の電話番号を取得
String line1Number = tm.getLine1Number();

// SIMのシリアル番号を取得
String simSerialNumber = tm.getSimSerialNumber();

// 固体識別番号(サブスクライバID)を取得
String subscriberId = tm.getSubscriberId();

// ボイスメール番号を取得
String voiceMailNumber = tm.getVoiceMailNumber();

// ボイスメールアルファタグを取得
String voiceMailAlphaTag = tm.getVoiceMailAlphaTag();
….

電話番号発信のテストコード

AndroidManifest.xml
targetSdkVersion=xxx
<uses-permission android:name="android.permission.CALL_PHONE"/>

ソース
startActivity(new Intent(Intent.ACTION_CALL).setData(Uri.parse("tel:09012345678")));
….

通話履歴の読み取りのテストコード

AndroidManifest.xml
targetSdkVersion=xxx
<uses-permission android:name="android.permission.READ_CALL_LOG"/>

ソース
Cursor cursor = getContentResolver().query(
CallLog.Calls.CONTENT_URI, null, null, null, CallLog.Calls.DEFAULT_SORT_ORDER);
….


通話履歴の書き込みのテストコード

AndroidManifest.xml
targetSdkVersion=xxx
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>

ソース
// 通話履歴データの追加
ContentValues values = new ContentValues();
values.put(CallLog.Calls.NUMBER, System.currentTimeMillis());
values.put(CallLog.Calls.DATE, System.currentTimeMillis());
values.put(CallLog.Calls.DURATION, 0);
values.put(CallLog.Calls.TYPE, CallLog.Calls.OUTGOING_TYPE);
values.put(CallLog.Calls.NEW, 1);
values.put(CallLog.Calls.CACHED_NAME, "TEST");
values.put(CallLog.Calls.CACHED_NUMBER_TYPE, 0);
values.put(CallLog.Calls.CACHED_NUMBER_LABEL, "");
getContentResolver().insert(CallLog.Calls.CONTENT_URI, values);

// 通話履歴データの更新
values.clear();
values.put(CallLog.Calls.CACHED_NAME, "UPDATED_NAME");
getContentResolver().update(CallLog.Calls.CONTENT_URI, values,
(CallLog.Calls.CACHED_NAME + " = ?"), new String[]{"TEST"});

// 通話履歴データの削除
getContentResolver().delete(CallLog.Calls.CONTENT_URI,
(CallLog.Calls.CACHED_NAME + " = ?"), new String[]{"UPDATED_NAME"});
….

テスト環境

  • Nexsus 5をAndroid 6.0にバージョンアップした物 
  • イメージバージョン番号:MRA58K 

※アンドキュメントな部分なので確認した環境です

結果

Android6.0上で設定メニューから「電話」パーミッショングループを不許可にした場合

targetSdkVersion=21


  • 端末情報の取得時:
    • 無条件でnullが返却される。
  • 電話番号発信時:
    • Intent.ACTION_CALLが設定されたIntentを使用してActivity#startActivity()を呼び出した際にActivityの遷移が行われず、Exceptionも発生しない。以下のログがWarningレベルで出力される
    • Appop Denial: starting Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxxx cmp=com.android.server.telecom/.components.UserCallActivity } from ProcessRecord{e189813 13538:com.example.taosoftware.readphonestatetest/u0a152} (pid=13538, uid=10152) requires android:call_phone
      
      
  • 通話履歴情報の読み取り時:
    • 無条件で空(Cursor#getCount()が0)のCursorが返却される
  • 通話履歴情報の追加時:
    • ContentProviderへの書き込みが行われず、IDが0となる以下のUriが返却されるCallLog.Calls.CONTENT_URIへの書き込み:content://call_log/calls/0
  • 通話履歴情報の更新時:
    • ContentProvider上のデータの更新が行われず、更新件数として0が返却される
  • 通話履歴情報の削除時:
    • ContentProvider上のデータの削除が行われず、削除件数として0が返却される

targetSdkVersion=23

本来requestPermissionすべきですがしてませんのでエラー系となるのが当然です。

  • 端末情報の取得時:
    • 該当メソッド呼び出し時に以下のSecurityExceptionが発生
    • java.lang.SecurityException: getDeviceId(該当メソッド名): Neither user 10148 nor current process has android.permission.READ_PHONE_STATE
  • 電話番号発信時:
    • Intent.ACTION_CALLが設定されたIntentを使用してActivity#startActivity()を呼び出した際に以下のSecurityExceptionが発生
    • java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxxx cmp=com.android.server.telecom/.components.UserCallActivity } from ProcessRecord{8920e5e 12662:com.example.taosoftware.readphonestatetest/u0a148} (pid=12662, uid=10148) with revoked permission android.permission.CALL_PHONE
      
  • 連絡先情報の読み取り時:
    • ContentResolver#query()呼び出し時に以下のSecurityExceptionが発生 
    • java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.CallLogProvider from ProcessRecord{209eb25 12246:com.example.taosoftware.calllogtest/u0a151} (pid=12246, uid=10151) requires android.permission.READ_CALL_LOG or android.permission.WRITE_CALL_LOG
      
  • 連絡先情報の追加・更新・削除時:
    • ContentResolver#insert(),ContentResolver#update(),ContentResolver#delete()呼び出し時に以下のSecurityExceptionが発生
    • java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.CallLogProvider from ProcessRecord{209eb25 12246:com.example.taosoftware.calllogtest/u0a151} (pid=12246, uid=10151) requires android.permission.READ_CALL_LOG or android.permission.WRITE_CALL_LOG
      

まとめ 

互換モードではTelephonyManagerインスタンスから端末情報を取得するとnullが返却されるため、これらの情報が取得できる前提でアプリを実装している場合はNullPointerExceptionが発生する可能性があるので注意が必要です。
互換モードの電話発信は、何も動作をしないので問題は発生しない。
その他は、ContentProvider系なので、前回のAndroid6 互換モード(3) コンテントプロパイダーと同じ動作となる。

といった感じです。広告モジュールや、ユーザの動向解析系のライブラリでは、端末情報等を取得している物が多いですが、それら自分のコードとは関係ない場所でハングアップする可能性もあるので注意が必要です。



がく

がく

リスクファインダーはAndroidアプリのセキュリティホールを見つけます!

Androidスマートフォンが急速に普及するとともにアプリの脆弱性報告も急増しています。
リスクファインダーは、Androidアプリの脆弱性診断WEBサービスです。
500項目以上のチェックでアプリの脆弱性や問題を検出し、セキュアなアプリ開発をサポートします。

  • ブラウザでファイルをアップロードするだけ
  • アプリの脆弱性を指摘するだけでなく対処方法も提示
  • 頻繁にバージョンアップするAndroidの最新情報に素早く対応

リスクファインダーの詳細はこちら