2012年9月19日水曜日

Android 特定のパーミションがあるか調べる

PackageManagercheckPermission() を使うと、特定のパーミションがあるかどうかチェックすることができます。

int p = getPackageManager().checkPermission(Manifest.permission.READ_PHONE_STATE, getPackageName()); if(p == PackageManager.PERMISSION_GRANTED) { // パーミッションあり } if(p == PackageManager.PERMISSION_DENIED) { // パーミッションなし }

パッケージ名を指定すればいいので、他のアプリもチェックできます。

例えば、インストールされているアプリの中で READ_PHONE_STATE のパーミッションがあるアプリの一覧を出すには次のようにします。

public class MainActivity extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); List<String> list = new ArrayList<String>(); PackageManager manager = getPackageManager(); List<PackageInfo> packages = manager.getInstalledPackages(0); for (PackageInfo pkg : packages) { int p = getPackageManager().checkPermission(Manifest.permission.READ_PHONE_STATE, pkg.packageName); if(p == PackageManager.PERMISSION_GRANTED) { list.add(pkg.packageName); } } ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list); setListAdapter(adapter); } }

Android Movie で File をデコードする

Movie を使うとアニメーション Gif を表示できるのですが、まぁバグがあるとかなんとかは自分でぐぐってください。 

よくあるサンプルは assets や raw にある Gif を読むやつばっかりです。 InputStream is = context.getResources().openRawResource(R.drawable.piggy); Movie movie = Movie.decodeStream(is); http://androidosbeginning.blogspot.jp/2010/09/gif-animation-in-android.html

とか InputStream is = context.getAssets().open("piggy.gif"); Movie movie = Movie.decodeStream(is); とか



SD にある Gif ファイルから読もうとして try { InputStream is = new FileInputStream(filePath); Movie movie = Movie.decodeStream(is); } catch (FileNotFoundException e) { e.printStackTrace(); } とすると Native 側で IOException が起こって表示されません。

LogCat で

09-19 15:38:37.828: W/System.err(7523): java.io.IOException 09-19 15:38:37.828: W/System.err(7523): at java.io.InputStream.reset(InputStream.java:218) 09-19 15:38:37.828: W/System.err(7523): at android.graphics.Movie.decodeStream(Native Method)

と出ていて、どうも InputStream の reset() で起こっていると。

InputStream のコードをみると、サブクラスが Override しないかぎり IOException が投げられるようになっています。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/libcore/luni/src/main/java/java/io/InputStream.java#217 217 public synchronized void reset() throws IOException { 218 throw new IOException(); 219 }

FileInputStream は Override してませんでした。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/libcore/luni/src/main/java/java/io/FileInputStream.java

AssetManager の open() で返ってくる InputStream は AssetInputStream です。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/content/res/AssetManager.java#288 288 public final InputStream open(String fileName) throws IOException { 289 return open(fileName, ACCESS_STREAMING); 290 } ... 309 public final InputStream open(String fileName, int accessMode) 310 throws IOException { 311 synchronized (this) { 312 if (!mOpen) { 313 throw new RuntimeException("Assetmanager has been closed"); 314 } 315 int asset = openAsset(fileName, accessMode); 316 if (asset != 0) { 317 AssetInputStream res = new AssetInputStream(asset); 318 incRefsLocked(res.hashCode()); 319 return res; 320 } 321 } 322 throw new FileNotFoundException("Asset file: " + fileName); 323 } この AssetInputStream は reset() を Override しています。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/content/res/AssetManager.java#538 538 public final class AssetInputStream extends InputStream { ... 569 public final void reset() throws IOException { 570 seekAsset(mAsset, mMarkPos, -1); 571 } ... 597 }

InputStream のサブクラスで reset() を継承しているクラスとして BufferedInputStream があります。

このクラスを使って、次のようにすると IOException が発生せずに読むことができます。 try { InputStream stream = new BufferedInputStream( new FileInputStream(filePath), 16 * 1024); stream.mark(16 * 1024); mMovie = Movie.decodeStream(stream); } catch (FileNotFoundException e) { e.printStackTrace(); } ポイントは mark() メソッドを呼ぶ必要がある点です。



ちなみに Movie には deocdeFile() というメソッドもあるのですが、なかみは FileInputStream 使ってるだけなので、使えません。。。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/graphics/java/android/graphics/Movie.java#51 51 public static Movie decodeFile(String pathName) { 52 InputStream is; 53 try { 54 is = new FileInputStream(pathName); 55 } 56 catch (java.io.FileNotFoundException e) { 57 return null; 58 } 59 return decodeTempStream(is); 60 }



おまけ

Movie.cpp
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/jni/android/graphics/Movie.cpp#82 82 static jobject movie_decodeStream(JNIEnv* env, jobject clazz, jobject istream) { 83 84 NPE_CHECK_RETURN_ZERO(env, istream); 85 86 // what is the lifetime of the array? Can the skstream hold onto it? 87 jbyteArray byteArray = env->NewByteArray(16*1024); 88 SkStream* strm = CreateJavaInputStreamAdaptor(env, istream, byteArray); 89 if (NULL == strm) { 90 return 0; 91 } 92 93 SkMovie* moov = SkMovie::DecodeStream(strm); 94 strm->unref(); 95 return create_jmovie(env, moov); 96 } http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp#CreateJavaInputStreamAdaptor 12 class JavaInputStreamAdaptor : public SkStream { 13 public: 14 JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar) 15 : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) { 16 SkASSERT(ar); 17 fCapacity = env->GetArrayLength(ar); 18 SkASSERT(fCapacity > 0); 19 fBytesRead = 0; 20 } 21 22 virtual bool rewind() { 23 JNIEnv* env = fEnv; 24 25 fBytesRead = 0; 26 27 env->CallVoidMethod(fJavaInputStream, gInputStream_resetMethodID); 28 if (env->ExceptionCheck()) { 29 env->ExceptionDescribe(); 30 env->ExceptionClear(); 31 SkDebugf("------- reset threw an exception\n"); 32 return false; 33 } 34 return true; 35 } ... 145 SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, 146 jbyteArray storage, int markSize) { 147 static bool gInited; 148 149 if (!gInited) { 150 jclass inputStream_Clazz = env->FindClass("java/io/InputStream"); 151 RETURN_NULL_IF_NULL(inputStream_Clazz); 152 153 gInputStream_resetMethodID = env->GetMethodID(inputStream_Clazz, 154 "reset", "()V"); 155 gInputStream_markMethodID = env->GetMethodID(inputStream_Clazz, 156 "mark", "(I)V"); 157 gInputStream_availableMethodID = env->GetMethodID(inputStream_Clazz, 158 "available", "()I"); 159 gInputStream_readMethodID = env->GetMethodID(inputStream_Clazz, 160 "read", "([BII)I"); 161 gInputStream_skipMethodID = env->GetMethodID(inputStream_Clazz, 162 "skip", "(J)J"); 163 164 RETURN_NULL_IF_NULL(gInputStream_resetMethodID); 165 RETURN_NULL_IF_NULL(gInputStream_markMethodID); 166 RETURN_NULL_IF_NULL(gInputStream_availableMethodID); 167 RETURN_NULL_IF_NULL(gInputStream_availableMethodID); 168 RETURN_NULL_IF_NULL(gInputStream_skipMethodID); 169 170 gInited = true; 171 } 172 173 if (markSize) { 174 env->CallVoidMethod(stream, gInputStream_markMethodID, markSize); 175 } 176 177 return new JavaInputStreamAdaptor(env, stream, storage); 178 }

2012年9月14日金曜日

67WS でマルチデバイス対応の講座やります!

67ワークショップさんで講座を受け持つことになりました。
有料ですが、少人数(なんと定員6人)でがっつり教えます!
初回は10月16日です。
よろしくお願いします。


告知文↓

■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
あんざいゆきの Android マルチデバイス対応講座 がはじまります
■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Android端末の魅力はその数と多様性です。しかし、多様性があるがゆえに直面するのが、端末間での表示の差異や動作の互換性のなさです。

この講座では、Android関連の著書も多数出版されている あんざいゆき さんを講師に、マルチデバイス対応しやすい画面設計のコツから、リソースや Fragment の使い方、9 patch の効果的な利用方法まで、実践的な手法を解説していきます。

実際にAndroidアプリ開発を行われている皆様、ご参加お待ちしています。


●カリキュラム概要
1. 様々な画面サイズ、解像度対応
 リソースの使い方
 対応しやすいレイアウトのコツ
 9 patch の使い方
 タブレットとハンドセット両対応

2. マルチバージョン対応
 Support pakcage の活かし方
 バージョン判別とクラスの分け方
 Action Bar Sherlock を使って 2.x でも Action Bar を使う方法


初回開講
2012年10月16日(火)

http://67.org/ws/workshop/detail/095android.html


2012年9月5日水曜日

Android Bluetooth でシリアル通信(SPP)する Fragment 書いた

そのうち GitHub におくかもしれない。

・Bluetooth が利用可能かチェック
・Bluetooth が有効になっていない場合は有効にするダイアログを表示
・有効になったらペアリング済みの一覧ダイアログを表示
・BLUETOOTH_ADMIN permission がある場合は、一覧ダイアログにデバイス検索開始のボタンを表示
・バックグラウンドでの通信(読み書き)

が入ってます。

・画面回転考慮してません
・デバイス検索中のクルクルをつけてません
(そのうちやる、と、思う、たぶん)

/** * This fragment process Bluetooth connection. Host Activity have to implements * BluetoothCallback. * * @author yanzm */ public class BluetoothFragment extends Fragment { public interface BluetoothCallback { /** * check whether device support Bluetooth. * * @param isAvailable * true if device support Bluetooth. */ public void onCheckAvailability(boolean isAvailable); /** * called when bluetooth state be changed. * * @param enabled */ public void onChangeBluetoothState(boolean enabled); /** * called when try to connect. * * @param connected */ public void onConnected(boolean connected); /** * called when connection established * * @param in * @param out */ public void onOpenConnection(); public void onDataArrived(int size, byte[] buffer); } public static final String EXTRA_UUID = "uuid"; public static final int SPP_MODE = 0; private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); private static final int REQUEST_ENABLE_BT = 11; private BluetoothCallback mCallback; private BluetoothAdapter mBluetoothAdapter; private boolean mIsAvailable; private boolean mIsEnable; private UUID mUuid; private ConnectThread mConnectThread; private TransmitThread mTransmitThread; @Override public void onAttach(Activity activity) { super.onAttach(activity); if (activity instanceof BluetoothCallback == false) { throw new ClassCastException("Activity have to implement BluetoothCallback"); } mCallback = (BluetoothCallback) activity; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onDestroy() { super.onDestroy(); if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mTransmitThread != null) { mTransmitThread.cancel(); mTransmitThread = null; } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Bundle args = getArguments(); int sppMode = args == null ? 0 : args.getInt(EXTRA_UUID, 0); switch (sppMode) { case 0: mUuid = SPP_UUID; break; default: mUuid = SPP_UUID; break; } // 1. Get the BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mIsAvailable = mBluetoothAdapter != null; mCallback.onCheckAvailability(mIsAvailable); // 2. Enable Bluetooth if (mBluetoothAdapter.isEnabled()) { mIsEnable = true; onEnabled(); } else { mIsEnable = false; Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } mCallback.onChangeBluetoothState(mIsEnable); } BroadcastReceiver mStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); if (state == BluetoothAdapter.STATE_ON) { mIsEnable = true; onEnabled(); } else { mIsEnable = false; } mCallback.onChangeBluetoothState(mIsEnable); } }; @Override public void onResume() { super.onResume(); IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); getActivity().registerReceiver(mStateReceiver, filter); } @Override public void onPause() { super.onPause(); getActivity().unregisterReceiver(mStateReceiver); if (mBluetoothAdapter.isDiscovering()) { stopDiscovery(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_ENABLE_BT) { if (resultCode == Activity.RESULT_OK) { mIsEnable = true; onEnabled(); } else { mIsEnable = false; } mCallback.onChangeBluetoothState(mIsEnable); } } private void onEnabled() { selectPairedDevice(); } public void selectPairedDevice() { // 3. Querying paired devices Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { ArrayList<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); for (BluetoothDevice device : pairedDevices) { deviceList.add(device); } final BluetoothDeviceAdapter adapter = new BluetoothDeviceAdapter(getActivity(), deviceList); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Paired devices"); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int position) { BluetoothDevice selectedDevice = adapter.getItem(position); connectToDevice(selectedDevice); } }); builder.setNegativeButton(android.R.string.cancel, null); Context context = getActivity(); PackageManager manager = context.getPackageManager(); int permission = manager.checkPermission(Manifest.permission.BLUETOOTH_ADMIN, context.getPackageName()); if (permission == PackageManager.PERMISSION_GRANTED) { builder.setPositiveButton("Search for devices", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { discoveringDevices(); } }); } builder.show(); } } BluetoothDeviceAdapter mDiscoveringAdapter; BroadcastReceiver mDiscoveringReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); mDiscoveringAdapter.add(device); } }; private void discoveringDevices() { // 4. discovering devices ArrayList<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); mDiscoveringAdapter = new BluetoothDeviceAdapter(getActivity(), deviceList); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setAdapter(mDiscoveringAdapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int position) { stopDiscovery(); BluetoothDevice selectedDevice = mDiscoveringAdapter.getItem(position); connectToDevice(selectedDevice); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { stopDiscovery(); } }); builder.show(); startDiscovery(); } private void startDiscovery() { IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); getActivity().registerReceiver(mDiscoveringReceiver, filter); mBluetoothAdapter.startDiscovery(); } private void stopDiscovery() { mBluetoothAdapter.cancelDiscovery(); getActivity().unregisterReceiver(mDiscoveringReceiver); } private void connectToDevice(BluetoothDevice device) { mConnectThread = new ConnectThread(device); mConnectThread.start(); } private class ConnectThread extends Thread { private final BluetoothSocket mSocket; public ConnectThread(BluetoothDevice device) { BluetoothSocket tmp = null; try { tmp = device.createRfcommSocketToServiceRecord(mUuid); } catch (IOException e) { } mSocket = tmp; } public void run() { if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); } try { mSocket.connect(); Message msg = mHandler.obtainMessage(MESSAGE_CONNECTED, true); mHandler.sendMessage(msg); } catch (IOException connectException) { try { mSocket.close(); } catch (IOException closeException) { } Message msg = mHandler.obtainMessage(MESSAGE_CONNECTED, false); mHandler.sendMessage(msg); return; } manageConnectedSocket(mSocket); } public void cancel() { try { mSocket.close(); } catch (IOException e) { } } } private void manageConnectedSocket(BluetoothSocket socket) { mTransmitThread = new TransmitThread(socket); mTransmitThread.start(); } private static final int MESSAGE_READ = 1; private static final int MESSAGE_CONNECTED = 2; private static final int MESSAGE_OPEN = 3; final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { int what = msg.what; switch (what) { case MESSAGE_READ: int length = msg.arg1; byte[] buffer = (byte[]) msg.obj; mCallback.onDataArrived(length, buffer); break; case MESSAGE_CONNECTED: boolean connected = (Boolean) msg.obj; mCallback.onConnected(connected); break; case MESSAGE_OPEN: mCallback.onOpenConnection(); break; } } }; private class TransmitThread extends Thread { private final BluetoothSocket mSocket; private final InputStream mInStream; private final OutputStream mOutStream; public TransmitThread(BluetoothSocket socket) { mSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); Message msg = mHandler.obtainMessage(MESSAGE_OPEN); mHandler.sendMessage(msg); } catch (IOException e) { } mInStream = tmpIn; mOutStream = tmpOut; } public void run() { byte[] buffer = new byte[1024]; int length; while (true) { try { length = mInStream.read(buffer); Message msg = mHandler.obtainMessage(MESSAGE_READ, length, -1, buffer); mHandler.sendMessage(msg); } catch (IOException e) { break; } } } public boolean write(byte[] bytes) { if (mOutStream == null) { return false; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { sb.append(bytes[i] + " "); } try { mOutStream.write(bytes); return true; } catch (IOException e) { } return false; } public void cancel() { try { mSocket.close(); } catch (IOException e) { } try { mInStream.close(); } catch (IOException e) { } try { mOutStream.close(); } catch (IOException e) { } } } public boolean write(int i) { byte[] bytes = new byte[4]; bytes[0] = (byte) (i); bytes[1] = (byte) (i >> 8); bytes[2] = (byte) (i >> 16); bytes[3] = (byte) (i >> 24); return write(bytes); } public boolean write(String s) { return write(s.getBytes()); } public boolean write(byte[] bytes) { if (mTransmitThread == null) { return false; } return mTransmitThread.write(bytes); } public static class BluetoothDeviceAdapter extends ArrayAdapter<BluetoothDevice> { public BluetoothDeviceAdapter(Context context, List<BluetoothDevice> objects) { super(context, 0, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater inflater = LayoutInflater.from(getContext()); convertView = inflater.inflate(android.R.layout.simple_list_item_2, parent, false); } BluetoothDevice device = getItem(position); TextView nameView = (TextView) convertView.findViewById(android.R.id.text1); nameView.setText(device.getName()); TextView addressView = (TextView) convertView.findViewById(android.R.id.text2); addressView.setText(device.getAddress()); return convertView; } } }

こんな感じで Activity には BluetoothCallback を implements して利用します。

public class MainActivity extends FragmentActivity implements BluetoothCallback { private static final String TAG = "MainActivity"; BluetoothFragment mBluetoothFragment; View mButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Bundle args = new Bundle(); args.putInt(BluetoothFragment.EXTRA_UUID, BluetoothFragment.SPP_MODE); if (mBluetoothFragment == null) { mBluetoothFragment = new BluetoothFragment(); getSupportFragmentManager().beginTransaction().add(mBluetoothFragment, "BluetoothFragment").commit(); } mButton = findViewById(R.id.button1); mButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { write(new byte[] { 0x50, 0x4d, 0x03 }); } }); mButton.setEnabled(false); } public void onCheckAvailability(boolean isAvailable) { String text = "onCheckAvailability : " + isAvailable; Log.d(TAG, text); showToast(text); } public void onChangeBluetoothState(boolean enabled) { String text = "onChangeBluetoothState : " + enabled; Log.d(TAG, text); showToast(text); } public void onConnected(boolean connected) { String text = "onConnected : " + connected; Log.d(TAG, text); showToast(text); } public void onOpenConnection() { mButton.setEnabled(true); Log.d(TAG, "onOpenConnection"); showToast("onOpenConnection"); } public void onDataArrived(int length, byte[] buffer) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < length; i++) { sb.append(buffer[i] + " "); } String text = "onDataArrived : " + length + ", " + sb.toString(); Log.d(TAG, text); showToast(text); } private void write(byte[] bytes) { if (mBluetoothFragment != null) { mBluetoothFragment.write(bytes); } } private void showToast(String text) { Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); } }