外部ストレージへアクセスするパーミッションと権限の範囲

投稿日:  更新日:

外部ストレージへアクセスするアプリは、パーミッション(許可)をユーザーから取得する必要があります。

パーミッションを得たアプリは、許可された権限の範囲内で、ストレージへアクセスが可能です。

これらは、ユーザーデータのプライバシー保護と密接に関係しています。

プライバシー保護をより強固にするために、パーミッションと権限の範囲の仕様は改変されてきました。

今回は、改変の歴史を辿りつつ、「パーミッションと権限の範囲」について、まとめます。

※環境:Android Studio Narwhal | 2025.1.1 Patch 1

スポンサーリンク

パーミッションと権限の範囲

パーミッション

外部ストレージはアプリ間で共有するデータを保存する領域です。

データを「他のアプリと共有する」は「他のアプリに公開する」と同意です。悪意のあるアプリがインストールされた場合に、このアプリを経由してデータ(個人情報)が漏洩します。

ですので、秘匿性の高いデータの保存に適しません。

内部・外部ストレージ

このセキュリティ上の問題点を承知の上で、外部ストレージへデータを保存するためには、アプリがユーザーからパーミッション(許可)を取得し、外部ストレージへアクセスする権限を持つ必要があります。

スポンサーリンク

権限の範囲

パーミッションを取得したアプリは、パーミッションが示す権限の範囲内で、ストレージのアクセスが可能です。

以下の表はパーミッションと権限の範囲を表しています。

API≦28API=29API=30~32API≧33
外部ストレージ プライマリ
/storage/emulated/ユーザーID
Java /
Android
API
Media Store
API
SAF
メディアデータPictures
自:△wrp
他:△wrp
 
自:◎wrp
他:◎wrp
 
自:△wr 
他:△wr 
 
DCIM
Movies
Music
Alarms
Notifications
Podcasts
Ringtones
Audiobooks(API≧30)
Recordings(API≧31)
ドキュメント
他のファイル
Downloads
自:○wrp
他:○wrp
自:◎wrp
他:◎wrp
自:◎wr 
他:◎wr 
Documants
Android
(Android/dataを除く)
ユーザーフォルダ
ユーザーファイル
アプリ別
 Android/data/パッケージ名/files
自:○wr 
他:△wrp
自:○wr 
他:○wr 
※自:自アプリのファイル
 他:他アプリのファイル
 ◎:アクセスできる(最適)
 ○:アクセスできる
 △:アクセスできる(不適または制限有)
 ×:アクセスできない
 空:アクセスできない(サポート外)
 w:書き込みできる
 r:読み出しできる
 p:パーミッションが必要
外部ストレージ プライマリ
/storage/emulated/ユーザーID
Java /
Android
API
Media Store
API
SAF
メディアデータPictures
自:×   
他:×   
 
自:◎wr 
他:◎ rp
 
自:△wr 
他:△wr 
 
DCIM
Movies
Music
Alarms
Notifications
Podcasts
Ringtones
Audiobooks(API≧30)
Recordings(API≧31)
ドキュメント
他のファイル
Downloads
自:×   
他:×   
自:◎wr 
他:×   
自:◎wr 
他:◎wr 
Documants
Android
(Android/dataを除く)
ユーザーフォルダ
ユーザーファイル
アプリ別
 Android/data/パッケージ名/files
自:○wr 
他:×   
自:○wr 
他:○wr 
※自:自アプリのファイル
 他:他アプリのファイル
 ◎:アクセスできる(最適)
 ○:アクセスできる
 △:アクセスできる(不適または制限有)
 ×:アクセスできない
 空:アクセスできない(サポート外)
 w:書き込みできる
 r:読み出しできる
 p:パーミッションが必要
外部ストレージ プライマリ
/storage/emulated/ユーザーID
Java /
Android
API
Media Store
API
SAF
メディアデータPictures
自:×   
他:×   
 
自:◎wr 
他:◎ rp
※1
自:△wr 
他:△wr 
 
DCIM
Movies
Music
Alarms
Notifications
Podcasts
Ringtones
Audiobooks(API≧30)
Recordings(API≧31)
ドキュメント
他のファイル
Downloads
自:○wr 
他:×   
自:◎wr 
他:×   
自:◎wr 
他:◎wr 
Documants
Android
(Android/dataを除く)
自:×   
他:×   
ユーザーフォルダ
ユーザーファイル
アプリ別
 Android/data/パッケージ名/files
自:○wr 
他:×   
自:×   
他:×   
※自:自アプリのファイル
 他:他アプリのファイル
 ◎:アクセスできる(最適)
 ○:アクセスできる
 △:アクセスできる(不適または制限有)
 ×:アクセスできない
 空:アクセスできない(サポート外)
 w:書き込みできる
 r:読み出しできる
 p:パーミッションが必要
※1:書き込みはファイル毎に権限の取得が必要、通常のパーミッションとは別
外部ストレージ プライマリ
/storage/emulated/ユーザーID
Java /
Android
API
Media Store
API
SAF
メディアデータPictures
自:×   
他:×   
 
自:◎wr 
他:◎ rp
※1
自:△wr 
他:△wr 
 
DCIM
Movies
Music
Alarms
Notifications
Podcasts
Ringtones
Audiobooks(API≧30)
Recordings(API≧31)
ドキュメント
他のファイル
Downloads
自:○wr 
他:×   
自:◎wr 
他:×   
自:◎wr 
他:◎wr 
Documants
Android
(Android/dataを除く)
自:×   
他:×   
ユーザーフォルダ
ユーザーファイル
アプリ別
 Android/data/パッケージ名/files
自:○wr 
他:×   
自:×   
他:×   
※自:自アプリのファイル
 他:他アプリのファイル
 ◎:アクセスできる(最適)
 ○:アクセスできる
 △:アクセスできる(不適または制限有)
 ×:アクセスできない
 空:アクセスできない(サポート外)
 w:書き込みできる
 r:読み出しできる
 p:パーミッションが必要(コレクション毎)
※1:書き込みはファイル毎に権限の取得が必要、通常のパーミッションとは別

大きな変更

パーミッションと権限の範囲は、APIのバージョンが上がる(API 23以降を想定)中で、大きな変更が2度ありました。

  • 対象範囲別ストレージ(API≧29)
  • きめ細かいメディア権限(API≧33)

ちなみに、「対象範囲別ストレージ」は「ストレージ名」ではなく「アクセスモデル名」です。

スポンサーリンク

大きな変更前(API≦28)

アプリ自身が所有するファイルへアクセスする時も、他アプリが所有するファイルへアクセスする時も、パーミッションの取得が必要です。

パーミッションの設定&取得

書き込み(WRITE_EXTERNAL_STORAGE)と読み出し(READ_EXTERNAL_STORAGE)パーミッションがあり、権限の範囲は外部ストレージ全体です。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

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

    <application ... > ... </application>

</manifest>
    reqPerm(arrayOf(
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    ))
    ...

    fun reqPerm(perms: Array<String>) {
        var _isGranted = true
        perms.forEach {
            val _result = PermissionChecker.checkSelfPermission(this, it)
            _isGranted = _isGranted && (_result == PermissionChecker.PERMISSION_GRANTED)
        }

        if (! _isGranted) {
            requestPermissions(perms, 0)
        }
    }

パーミッションの取得を行うと、次のようなダイアログが表示されて、ユーザの許可を求めます。
※詳細は「Runtime Permissionの取得(システムがリクエストを管理)」を参照

【英語】

外部ストレージアクセス許可ダイアログ

※エミュレータ―(API28)の表示

【日本語】

外部ストレージアクセス許可ダイアログ
スポンサーリンク

対象範囲別ストレージ(API≧29)

対象範囲別ストレージとは、ドキュメントに「アプリとユーザーデータのプライバシー保護を目的に、アプリの外部ストレージに対するアクセスを制限します。」と説明されています。

変更点

具体的には、次の点が変更になります。

★Java/Android APIを使ったアクセスを禁止
★Media Store APIを使ったアクセスを強制
  ・アプリ自身が所有するファイルへアクセス  ... ※1
    -パーミッションは不要
  ・他アプリが所有するファイルへアクセス
    -Readアクセスにパーミッションが必要
    -Writeアクセスは個別にURIへ許可を出す ... ※2
     (通常のパーミッションとは別)
★サンドボックスと同等な領域(アプリ別)を確保

※サンドボックス:内部ストレージにあるアプリ固有の領域

プライバシー保護で問題になる「他のアプリが所有するファイルへアクセスする方法」は残ります。無くしてしまったら、「アプリ間で共有するデータを保存する」という外部ストレージ本来の目的を失うからです。

スポンサーリンク

パーミッションの設定&取得

読み出し(READ_EXTERNAL_STORAGE)パーミッションがあり、権限の範囲はメディアデータです。

※1,2の変更により、書き込み(WRITE_EXTERNAL_STORAGE)パーミッションは不要になりました。

ただし、API≦28では必要なので、API≦32(=23~32)の全てに対応したければ、maxSdkVersionを付けて指定します。

API=29~32のみに対応API≦32に対応
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

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

    <application ... > ... </application>

</manifest>
    reqPerm(arrayOf(
        Manifest.permission.READ_EXTERNAL_STORAGE,
    ))
    ...

    fun reqPerm(perms: Array<String>) {
        var _isGranted = true
        perms.forEach {
            val _result = PermissionChecker.checkSelfPermission(this, it)
            _isGranted = _isGranted && (_result == PermissionChecker.PERMISSION_GRANTED)
        }

        if (! _isGranted) {
            requestPermissions(perms, 0)
        }
    }
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application ... > ... </application>

</manifest>
    reqPerm(arrayOf(
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    ))
    ...

    fun reqPerm(perms: Array<String>) {
        var _isGranted = true
        perms.forEach {
            val _result = PermissionChecker.checkSelfPermission(this, it)
            _isGranted = _isGranted && (_result == PermissionChecker.PERMISSION_GRANTED)
        }

        if (! _isGranted) {
            requestPermissions(perms, 0)
        }
    }

パーミッションの取得を行うと、次のようなダイアログが表示されて、ユーザの許可を求めます。

【英語】

外部ストレージアクセス許可ダイアログ

※エミュレータ―(API30)の表示

【日本語】

外部ストレージアクセス許可ダイアログ
スポンサーリンク

注意点:API29における無効化

API29における対象範囲別ストレージは、デフォルトで有効ですが、設定で無効に出来ます。

ただし、API≧30では強制的に有効です。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application android:requestLegacyExternalStorage="true" ... > 
	... 
	</application>

</manifest>

API29とAPI≧30でアクセスする権限の範囲に違いがあります。注意が必要です。

ですので、「対象範囲別ストレージの実質的な実装はAPI≧30」と捉えて、API29は無効化が妥当だと思います。

注意点:URIのアクセス許可は別物

対象範囲別ストレージは、URI(ストレージ上のファイルを一意に表す)毎にアクセス許可を出せます。

許可を持つアプリは、そのURIにアクセスが可能です。

この「URI毎に出されるアクセス許可」と「パーミッションシステムの出すアクセス許可」は独立しています。両者の関係性は無く、全くの別物です。

対象範囲別ストレージにおいて、他アプリが所有するファイルへ書き込みたい(更新や編集、削除を含む)場合は、URIのアクセス許可を取得する方法を使います。

スポンサーリンク

きめ細かいメディア権限(API≧33)

きめ細かいメディア権限では、メディアデータに対するパーミッションがコレクション毎に細分化されます。

変更点

具体的には、次の点が変更になります。

★読み出し(READ_EXTERNAL_STORAGE)パーミッションを廃止 ... ※3
★メディアデータに対するパーミッション新設         ... ※4
  ・コレクション毎に細分化
    -READ_MEDIA_IMAGES	⇒ imagesコレクションのアクセス許可
    -READ_MEDIA_VIDEO	⇒ videoコレクションのアクセス許可
    -READ_MEDIA_AUDIO	⇒ audioコレクションのアクセス許可

例えば、READ_MEDIA_IMAGESパーミッションを取得したアプリは、imagesコレクションにアクセスする権限をもちます。videoとaudioコレクションへのアクセスは出来ません。

アクセスしたければ、別途、パーミッションを取得する必要があります。

スポンサーリンク

パーミッションの設定&取得

imeges/video/audio毎の読み出しパーミッションがあり、権限の範囲はコレクションです。

パーミッションは※3から※4へ切り替わり、READ_EXTERNAL_STORAGEパーミッションは不要になりました。

ただし、API≦32では必要なので、全てのAPIに対応したれければ、残す必要があります。残してもAPI≧33では無視されます。

API≧33のみに対応全てのAPIに対応
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

    <application ... > ... </application>

</manifest>
    reqPerm(arrayOf(
        Manifest.permission.READ_MEDIA_IMAGES,
//        Manifest.permission.READ_MEDIA_VIDEO,
//        Manifest.permission.READ_MEDIA_AUDIO
    ))
    ...

    fun reqPerm(perms: Array<String>) {
        var _isGranted = true
        perms.forEach {
            val _result = PermissionChecker.checkSelfPermission(this, it)
            _isGranted = _isGranted && (_result == PermissionChecker.PERMISSION_GRANTED)
        }

        if (! _isGranted) {
            requestPermissions(perms, 0)
        }
    }
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application ... > ... </application>

</manifest>
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
        reqPerm(arrayOf(
            Manifest.permission.READ_MEDIA_IMAGES,
    //        Manifest.permission.READ_MEDIA_VIDEO,
    //        Manifest.permission.READ_MEDIA_AUDIO
        ))
    else
        reqPerm(arrayOf(
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
        ))
    ...

    fun reqPerm(perms: Array<String>) {
        var _isGranted = true
        perms.forEach {
            val _result = PermissionChecker.checkSelfPermission(this, it)
            _isGranted = _isGranted && (_result == PermissionChecker.PERMISSION_GRANTED)
        }

        if (! _isGranted) {
            requestPermissions(perms, 0)
        }
    }

パーミッションの取得を行うと、次のようなダイアログが表示されて、ユーザの許可を求めます。imagesとvideoコレクションは同じダイアログです。

【英語】

外部ストレージアクセス許可ダイアログ
※images/videoコレクション
※エミュレータ―(API33)の表示

【日本語】

外部ストレージアクセス許可ダイアログ
外部ストレージアクセス許可ダイアログ
※audioコレクション
※エミュレータ―(API33)の表示

外部ストレージアクセス許可ダイアログ
スポンサーリンク

関連記事:

ストレージのリソースは、内部メモリー、外部メモリー、クラウドの3つがあります。 外部メモリーは、主にSDカードです。 このSDカードはAndroid端末の世代が進むにつれて、扱いを変えてきました。 リソースとSDカードを主眼に置いたストレージの変遷について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
Adoptable Storage(API≧23、Android 6.0)が導入されて、SDカードの扱い方をユーザ側で指定できるようになりました。 「Adoptable Storage」について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
ストレージはデータの用途別に記憶領域が分けられます。 保存先を守らないと、セキュリティリスクが発生したり、他のアプリと協調した動作が出来なくなったり、します。 ですので、適切な場所へデータを保存しましょう。 今回は「用途別記憶領域とボリューム」について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
ストレージへアクセスする方法は、「扱うデータの種類」「アクセス先」「セキュリティの確保」などの要件により、最適なアクセス方法が存在するので、使い分けが必要です。 今回は「アクセスする方法」について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
「内部ストレージ」にアクセスする方法について、まとめます。 「内部ストレージ」は、セキュリティが高いうえに、端末の内部メモリーに構築されるので、必ず存在します。 使いたい時に安心して使え、もっとも使い勝手のよいストレージと言えます。 アプリのデータを保存したいならば、保存先として真っ先に考えるストレージです。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
「外部ストレージ_プライマリ」にアクセスする方法について、まとめます。 「外部ストレージ_プライマリ」は、アプリ間で共有するデータを保存するストレージです。 主にカメラ、音楽プレーヤー、動画プレーヤーで扱うデータを保存します。 「外部ストレージ_プライマリ」に保存したデータは、他のアプリに公開することになるので、秘匿性の高いデータの保存に適しません。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
「外部ストレージ_セカンダリ」にアクセスする方法について、まとめます。 「外部ストレージ_セカンダリ」は、デバイス間(または、アプリ間)で共有するデータを保存するストレージです。 デバイスとは、他の携帯端末やパソコンなどを指します。 ストレージがSDカードのような取り外し可能なリソース上に構築されているので、データの共有は取り外したストレージを他のデバイスへ取り付けて行います。 「外部ストレージ_セカンダリ」に保存したデータは、他のデバイスに公開することになるので、秘匿性の高いデータの保存に適しません。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
スポンサーリンク