技術はメシのタネ

底辺プログラマで技術の向上を目指しているけれどやりたい事が無くて困っている

play video from android internal storage

Androidの話。
自アプリ内のコンテンツの再生を、暗黙的インテントで他アプリに依頼するとします。

本当は、getFilesDir()を使って/data/data/パッケージ名/files下に保存して、ファイルプロバイダーで依頼するのがいいと思います。

本当は。

しかしですね、諸事情で/data/data/パッケージ名/app_a/b/c/d/e/にコンテンツを保存せざるを得ないとします。
主に仕様とか仕様とか仕様とか。

しかも、コンテンツがクソ重くてコピーもままならず、このままインテントで再生を依頼するしかないと仮定します。


この場合、コードとしてはこんな感じになります。

play video from android internal storage

(以下引用)

Uri fileUri = Uri.fromFile(new File(String file_path));
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(fileUri, URLConnection.guessContentTypeFromName(fileUri.toString()));
startActivity(intent);

このURLConnection#guessContentTypeFromName、優秀です。
この呪文1つ唱えるだけでテキストでも動画でも画像でも再生できます。
拡張子で場合分けする必要はありません。
(再生に対応するアプリが存在すればね)

さて……。
これで再生できれば、もうここから先を読む必要はありません。

どうです?
再生できました?


ここからは、再生できなかった人向けです。
しかも、こういうケースです。

  • 暗黙的インテントで対応するアプリを選ぶダイアログが起動する
  • 再生するアプリで再生を行うと「この動画は再生できません」的メッセージ
  • Logcatでログを確認するとAwesomePlayerとかからpermission deniedって言われてる
  • 他にも(1, -2147483648)とか


こういう場合、ログに素直に従って再生コンテンツのパーミッションを確認しましょう。

adbコマンドで/data/下にアクセスできるかどうかがポイントですね。
lspermission deniedと言われても、run-as パッケージ名でアクセスできる可能性があります。

コンテンツの保存されているディレクトリへ向けて、1つずつパーミッションを確認していきましょう。
run-asができるかどうかは機種依存らしいので、ダメなら別の方法で確認します。

コンテンツを一旦/data/data/パッケージ名/app_aに移動させて、暗黙的インテントで再生できるか確認してみてください。
app_agetDir()で作成するのが一般的と思いますが、この時にファイルアクセスを設定できます。
Context.MODE_WORLD_READABLEとか付ければいいんじゃないですかね、たぶん。

さて、どうでした?
途中のディレクトリも含めて他アプリから読み取り可能なパーミッションでしたか?
/data/data/パッケージ名/app_a直下であれば再生可能でしたか?

自分の場合はrun-asで確認できまして、途中のディレクトリのパーミッションの設定が他アプリから読み取り可能ではありませんでした。

原因は、/data/data/パッケージ名/app_aディレクトリ以下をmkdirs()で作ったことだったっぽいです。
mkdir()だと親ディレクトリのパーミッションを引き継ぐとどこかで読んだ気がするのですが、mkdirs()は違うんですかね?

対処法ですが、File#setReadable(boolean executable, boolean ownerOnly)とかでmkdir()したディレクトリを読み取り可能にしてあげれば良いです。

int mode = Context.MODE_WORLD_WRITABLE | CONTEXT.MODE_WORLD_READABLE;
// /data/data/パッケージ名/直下にフォルダを作るとapp_がくっつきます
File root = getDir("a", mode);
File second = new File(root, "b");
if (!second.exists()) {
    second.mkdir();
    second.setExecutable(true, false);
    second.setWritable(true, false);
    second.setReadable(true, false);
}
...

過剰にパーミッションを盛りましたが、適度に調節してください。
あ、ファイル自体が再生不可能な場合はここの説明の対象外です。
悪しからず。

以上。

iOSでiBeaconを探す、AndroidでiBeaconを探す

ちょっとメモなので親切で無いのはご勘弁を。
AndroidiOSの両方で、iBeaconのアドバタイズ・パケットを受信して領域検知を行うアプリを作るとする。

肝心要の領域検知を開始する開始の仕方。

iOSの場合。

var myLocationManager:CLLocationManager = CLLocationManager()
var myBeaconRegion:CLBeaconRegion!

myBeaconRegion = CLBeaconRegion(proximityUUID: uuid)
myLocationManager.startMonitoringForRegion(myBeaconRegion)

Androidの場合。
APIだけで申し訳ないが、ちょっと時間が無いのでご勘弁を。

public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback)

ここで注目したいのが、UUID。

iOSの場合、CLBeaconRegionの引数のUUIDは、proximity UUID
実際のアドバタイズ・パケットに含まれているもの。

一方、Androidの場合、startLeScanの引数のUUIDはService UUID
GATTサーバーに登録済みのプロファイルを引っ掛けるもの……だと思う、たぶん。

もし特定のperipheralのみを探す場合, 代わりにstartLeScan(UUID[], BluetoothAdapter.LeScanCallback)を使うことができる. アプリケーションがサポートしているGATT serviceのUUIDのarrayを提供すればよい.
Yukiの枝折


同じ128bitのUUIDであっても、2つのUUIDのもたらす効果は全く別ってわけ。
まあ本家本元だけあってiOSの方が断然使いやすくできているのは仕方ないとして、Androidの紛らわしさったら無い。

private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        ...
        }
};
.
.
.
mBluetoothAdapter.startLeScan(mLeScanCallback);


この様に、Androidでは、引数1つのstartLeScan()を用いてBLEの検出をコールバックで待ち受けて、scanRecordを解析するまでそれがiBeacon形式のアドバタイズ・パケットかどうかはわからない。

……っていうの、本当かなあって疑ってる。
今ひとつBLEとかGATTのことがよく理解できていないのであれなんだけども。

サービスがこうで、
Bluetooth Developer Portal サービス

proximityのプロファイルがこうなわけでしょ。
Home > GATT Specifications > Profiles > Profile Viewer

<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2011 Bluetooth SIG, Inc. All rights reserved. -->
<Profile xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/profile.xsd" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    type="org.bluetooth.profile.proximity"
    name="Proximity">
    <InformativeText>
        <Abstract>
            The Proximity profile enables proximity monitoring between two devices.
        </Abstract>
        <Summary>
            The Proximity profile defines the behavior when a device moves away from a peer device so that the connection is dropped or the path loss increases above a preset level, causing an immediate alert. This alert can be used to notify the user that the devices have become separated. As a consequence of this alert, a device may take further action, for example to lock one of the devices so that it is no longer usable.
        <p>
            The Proximity profile can also be used to define the behavior when the two devices come closer together such that a connection is made or the path loss decreases below a preset level.
        </p>
        </Summary>
    </InformativeText>
    <Role name="Proximity Reporter">
        <Service type="org.bluetooth.service.link_loss">
            <Declaration>Primary</Declaration>
            <Requirement>Mandatory</Requirement>
        </Service>
        <Service type="org.bluetooth.service.immediate_alert">
            <Declaration>Primary</Declaration>
            <Requirement>Optional</Requirement>
        </Service>
        <Service type="org.bluetooth.service.tx_power">
            <Declaration>Primary</Declaration>
            <Requirement>Optional</Requirement>
        </Service>
        <Conditional condition="if_any_supported_all_must_be_supported">
            <Type>org.bluetooth.service.immediate_alert</Type>
            <Type>org.bluetooth.service.tx_power</Type>
        </Conditional>
    </Role>
    <Role name="Proximity Monitor">
        <Client type="org.bluetooth.service.link_loss">
            <Requirement>Mandatory</Requirement>
        </Client>
        <Client type="org.bluetooth.service.immediate_alert">
            <Requirement>Mandatory</Requirement>
        </Client>
        <Client type="org.bluetooth.service.tx_power">
            <Requirement>Mandatory</Requirement>
        </Client>
    </Role>
</Profile>

ビーコン側がたぶんProximity Reporterだと思うんだけど、0000XXXX-0000-1000-8000-00805f9b34fbのXXXXに、

  • org.bluetooth.service.link_loss……0x1803

入れてやってUUID.fromString(String)したのをstartLeScan()に渡してやれば、GATTサーバに登録済みのサービス(BLE)を無制限にscanするんじゃなくて、iBeacon(proximity)だけ拾うんじゃ無いかなあ……と思う次第。

追記

ダメでした。
そっかー、proximityじゃないのか……。
噂のAndroid用iBeaconライブラリAltBeaconのソースを確認してみたけど、やっぱり引数1つのstartLeScan()を使っている。
ソース

こういう質問も出てるけど、今のところ回答なし。
Service UUIDはわかりませーん。