2013 年 9 月 3 日

アンドロイドアプリのinstallLocationを取得する

3010-logo

前回はキャッシュ消去するためにハイドメソッドを利用した方法を紹介したが、ハイドメソッドだけでなくハイドアトリビュートが存在する。
たとえば、アンドロイドアプリのManifestには、インストールロケーションを指定できる(Android2.2以上)。この指定はこのアプリがSDに移動可能かどうかを設定する。
この値をわかれば、事前にSDカードへ移動可能かどうか知ることが可能になる。しかし、この値を取得するAPIは存在しない。
AndroidManifest.xmlを読もうとしても難読化されているので、簡単には読めないだろう。

アプリが内部にあるのか、SDにあるのかは、ApplicationInfoで判断できるし、これぐらいAPIで取得できてもよさそうだが、セキュリティ上の観点からか、非公開となっている。

今回もJavaのリフレクション機能を利用して、見えない属性から値を取得してみる。

    List<ApplicationInfo> application = new ArrayList<ApplicationInfo>();
    application = mPackageManager.getInstalledApplications(0);
    for (int i = 0; i < application.size(); i++) {
      ApplicationInfo app = application.get(i);
      int installLocation = 0;
      try {
        PackageInfo pi = mPackageManager.getPackageInfo(app.packageName, PackageManager.GET_META_DATA);
        try {
          installLocation = PackageInfo.class.getField("installLocation").getInt(pi);
        } catch (IllegalArgumentException e) {
          Log.d(TAG, e.toString());
        } catch (IllegalAccessException e) {
          Log.d(TAG, e.toString());
        } catch (NoSuchFieldException e) {
          Log.d(TAG, e.toString());
        }
      } catch (NameNotFoundException e) {
        Log.d(TAG, e.toString());
      }
      if ((app.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == ApplicationInfo.FLAG_EXTERNAL_STORAGE) {
        Log.d(TAG, "[external]" + app.packageName + "  " + " loc: " + installLocation);        
      } else {
        Log.d(TAG, "[internal]" + app.packageName + "  "  + " loc: " + installLocation);        
      }
    }

installLocationの値は以下のように定義されている。もちろん、これらの定数も参照することはできない。

public static final int INSTALL_LOCATION_AUTO = 0;
public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1;
public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;

実際に取得してみると、-1が返ってくるときがあるが、もしかすると2.2以前のアプリなのかもしれないが、そこまで調査はしていない。

ここまでできれば、SDへの移動APIも使ってみたいと思うだろう。実際にそのようなAPIも存在する。またパッケージを削除するAPIも存在する。

public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags);
public void movePackage(String packageName, IPackageMoveObserver observer, int flags);

しかし、残念ながら、これらは通常のアプリからは使えない。以下のパーミッションが必要だが、system appにしか許されないとされている。
実際に呼び出すと、SecurtiyExceptionが発生する。

<uses-permission android:name="android.permission.MOVE_PACKAGE"/> <!-- only system app. -->
<uses-permission android:name="android.permission.DELETE_PACKAGES"/> <!-- only system app. -->

Manifestの構造や内部での利用がよくわからないが、ここをうまく設定できれば、動かすことができるのかもしれないが、難しいだろう。
これらもちょっとしたことで可能となれば、乱用されてしまう可能性があるので、出来ない方がよいだろう。