[Android] Android Studioでpermissionの指定が効かない

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こむろです。札幌暑いです。

Permissionの指定が機能しない

自分の現在の環境はこんなかんじです。

  • OS: Mac OSX Yosemite 10.10.3
  • IDE: Android Studio 1.2 Beta

Bluetooth LEの機能を利用したアプリケーションを作成するため、以下のようなAndroidManifest.xmlを作成しました。

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

    <uses-permission android:name="ANDROID.PERMISSION.INTERNET" />
    <uses-permission android:name="ANDROID.PERMISSION.BLUETOOTH" />
    <uses-permission android:name="ANDROID.PERMISSION.BLUETOOTH_ADMIN" />

    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Permissionの部分は、Android Studioの補完機能を使って出てきてくる値をそのまま利用しています。

スクリーンショット 2015-04-30 18.20.52

表示ではandroid.permission.となっているにも関わらず、AndroidManifestに実際記入されるのは全て大文字です。しかし特に気にせずそのまま実装を続けていました。

実装したFragmentクラスはこんな感じです。

/**
 * A placeholder fragment containing a simple view.
 */
public class MainActivityFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater!!.inflate(R.layout.fragment_main, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        val startScanButton:Button = getActivity().findViewById(R.id.button) as Button

        startScanButton.setOnClickListener { v ->
            val adapter: BluetoothAdapter? = if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
                Toast.makeText(v.getContext(), "Bluetooth LEに対応していません.", Toast.LENGTH_SHORT).show()
                null
            } else {
                getBluetoothAdapter(getActivity())
            }

            adapter?.startLeScan(leScanCallback)
        }

        val stopScanButton:Button = getActivity().findViewById(R.id.button2) as Button

        stopScanButton.setOnClickListener { v ->
            val adapter: BluetoothAdapter? = if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
                null
            } else {
                getBluetoothAdapter(getActivity())
            }
            adapter?.stopLeScan(leScanCallback)
        }
    }
    
    val leScanCallback = object:BluetoothAdapter.LeScanCallback {
        override fun onLeScan(device: BluetoothDevice?, rssi: Int, scanRecord: ByteArray?) {
            Log.d("MainActivityFragment", "BluetoothDevice: " + device?.getAddress() + ", RSSI: " + rssi)
        }
    }
}

まあ、特別難しいことをしていません。Start Scan Buttonが押されたら、LeScanを実行しているだけです。

実行するとエラーで落ちる

このコードですが、実行すると落ちました。Stacktraceを見ると以下の通り。

java.lang.SecurityException: Need BLUETOOTH permission: Neither user 10003 nor current process has android.permission.BLUETOOTH.
            at android.os.Parcel.readException(Parcel.java:1547)
            at android.os.Parcel.readException(Parcel.java:1499)
            at android.bluetooth.IBluetooth$Stub$Proxy.getState(IBluetooth.java:878)
            at android.bluetooth.BluetoothAdapter.getState(BluetoothAdapter.java:583)
            at android.bluetooth.BluetoothAdapter.startLeScan(BluetoothAdapter.java:1824)

Logcat「やれやれ。Bluetoothを使いたいならPermissionを指定してくれなきゃ困るよ。キミィ」という言葉が聞こえてきそうです。指定してるよ!

小文字の箇所は小文字で指定せよ

一番怪しいのはAndroidManifest.xmlファイルです。以下のように修正します。

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

これで起動し直します。

04-30 18:34:36.526    9835-9852/sample.android.classmethod.jp.sampleble D/BluetoothLeScanner﹕ onClientRegistered() - status=0 clientIf=5
04-30 18:34:36.580    9835-9852/sample.android.classmethod.jp.sampleble D/BluetoothLeScanner﹕ onScanResult() - ScanResult{mDevice=49:E2:98:EE:7E:6D, mScanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=null, mManufacturerSpecificData={76=[12, 14, 0, -118, -42, 46, -115, 93, 111, -18, -31, -111, 81, 37, -117, 4]}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=null], mRssi=-57, mTimestampNanos=40659127925103}
04-30 18:34:36.582    9835-9835/sample.android.classmethod.jp.sampleble D/MainActivityFragment﹕ BluetoothDevice: 49:E2:98:EE:7E:6D, RSSI: -57
04-30 18:34:36.592    9835-9852/sample.android.classmethod.jp.sampleble D/BluetoothLeScanner﹕ onScanResult() - ScanResult{mDevice=73:33:9D:47:E7:A9, mScanRecord=ScanRecord [mAdvertiseFlags=26, mServiceUuids=null, mManufacturerSpecificData={76=[12, 14, 0, -92, 0, 80, -112, 17, -41, 113, 125, 122, 38, 120, 39, 5]}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=null], mRssi=-82, mTimestampNanos=40659139928957}
04-30 18:34:36.592    9835-9835/sample.android.classmethod.jp.sampleble D/MainActivityFragment﹕ BluetoothDevice: 73:33:9D:47:E7:A9, RSSI: -82

無事BLEのスキャンが実行されたようです。

結論

Android Studioの補完を信じすぎずに自分で書きましょう!
Android Studioを利用してるとサクサク補完してくれるので、諸々入力をサボっていたのですがこんな罠が待っていたとは。