Androidのアプリ(apkファイル)を抽出して、証明書を確認する

Androidのアプリ(apkファイル)を抽出して、証明書を確認するメモ。

端末にインストールされているアプリ(apkファイル)を抽出する

Google Playでそのアプリのページを開き、URLからパッケージ名を確認します。
URLの「id=」に続く部分がパッケージ名になります。

https://play.google.com/store/apps/details?id=パッケージ名

パッケージ名を一覧表示

adb shell pm list packages -f

パッケージ名を絞り込んで表示

adb shell pm list packages -f | grep パッケージ名

「package:アプリの場所=パッケージ名」の形式で表示される

package:/data/app/~/base.apk=~

apkファイルを取り出す

C:\~\adb.exe pull /data/app/~/base.apk

証明書のfingerprintを表示する

keystoreから取り出す場合

keytool -list -keystore my-signing-key.keystore

apkから取り出す場合(Java7以上)

keytool -list -printcert -jarfile app.apk

Android Studio 2.1でJNIを使って開発するには

Android Studio 2.1でJNIを使って開発する手順です。

環境

  • Windows 10
  • Android Studio 2.1.1

プロジェクトの作成

Android Studioで新しいプロジェクトを作成します。

011

「Application name」と「Company Domain」を入力します。

ここでは、「Application name」を「JniTest」としました。

012

「Phone and Tablet」をチェックし、「Minimum SDK」を選択します。

013

「Empty Activity」を選択します。

014

「Activity Name」と「Layout Name」は初期値のままにします。

015

プロジェクトが作成されます。

016

メニューの「Run」→「Run ‘app’」を選択して、アプリケーションを実行できることを確認します。

017

018

Gradleの設定

メニューの「File」→「Settings」を選択し、「Build, Execution, Deployment」→「Build Tools」→「Gradle」を選択します。

「Use Default Gradle wrapper (recommended)」にチェックを入れ、「OK」ボタンを押します。

019

Android NDKのインストール

メニューの「Tools」→「Android」→「SDK Manager」を選択します。

「SDK Tools」タブを選択し、「Android NDK」をチェックします。。

001

「Apply」ボタンを押します。

002

License Agreement画面の「Accept」をチェックして、「Next」ボタンを押します

003

インストールが始まります。

004

メニューの「Run」→「Run ‘app’」を選択して、アプリケーションを実行できることを確認します。

build.gradle(Project)ファイルの編集

「build.gradle(Project: jnitest)」を開きます。

classpath 'com.android.tools.build:gradle:2.1.0'

の行を次のように変更します。

classpath 'com.android.tools.build:gradle-experimental:0.7.0'

020

gradle-wrapper.propertiesファイルの編集

「gradle-wrapper.properties (Gradle Version)」を開きます。

distributionUrl=…

の行を次のように変更します。

distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

021

build.gradle(Module)ファイルの編集

「build.gradle(Module: app)」を開きます。

次のように変更します。

apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.3"

        defaultConfig {
            applicationId = "jp.gesource.jnitest"
            minSdkVersion.apiLevel = 19
            targetSdkVersion.apiLevel = 23
            versionCode = 1
            versionName = "1.0"
        }
        buildTypes {
            release {
                minifyEnabled = false
                proguardFiles.add(file('proguard-android.txt'))
            }
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
}

メニューの「Run」→「Run ‘app’」を選択して、アプリケーションを実行できることを確認します。

Android NDK locaationの設定

「File」→「Project Structure」→「SDK Location」を選択します。

「Android NDK locaation」が設定されていなければ、設定して「OK」ボタンを押します。

022

build.gradleの設定

build.gradle(Module: app)を開き、「buildTypes」の設定の下に「ndk」の設定を追加します。

buildTypes {
    …
}
ndk {
    moduleName = "jnitest"
}

023

MainActivityの編集

MainActivity.javaを開き、次の行を追加します。

static {
    System.loadLibrary("jnitest");
}
public native String getMsgFromJni();

build.gradleのmoduleNameで設定した名前をSystem.loadLibraryの引数に設定します。

024

getMsgFromJni関数が存在しないため、getMsgFromJniの部分でエラーになっています。
カーソルをgetMsgFromJniの上に移動し、ALT+Enterキーを押します。

025

Enterキーを押すと、jniフォルダーにjnitest.cファイルが作成され、関数が作成されます。

#include <jni.h>

JNIEXPORT jstring JNICALL
Java_gesource_jp_jnitest_MainActivity_getMsgFromJni(JNIEnv *env, jobject instance) {
    // TODO
    return (*env)->NewStringUTF(env, returnValue);
}

jnitest.cのreturnValueの部分を編集します。

return (*env)->NewStringUTF(env, "Hello JNI");

res/layout/activity_main.xmlファイルを開き、TextViewのidを「jni_msgView」に設定します。

026

MainActivity.javaを開き、MainActivityクラスのonCreateメソッドの末尾に次の行を追加します。

((TextView) findViewById(R.id.jni_msgView)).setText(getMsgFromJni());

TextViewの部分がエラーになっているので、Alt+Enterキーを押して修正します。

027

メニューの「Run」→「Run ‘app’」を選択して、アプリケーションを実行できることを確認します。

028

C言語で記述した処理が実行されています。

近くにあるスマートフォンと接続・送受信するNearby API

Google Play サービス 7.8からNearby APIが追加されました。
このNearby APIを使うと、物理的に近くのデバイスとやりとりができます。

Nearby APIには2つのAPIがあります。

  • Nearby Messages API
  • Nearby Connections API

Nearby Messages API

ちょっとしたデータを隣の人に送るための簡単な方法が今までありませんでした。
送り先のメールアドレスやLineのアカウントを知る必要がありました。
このAPIがその問題を解決するかもしれません。

Nearby Messages APIは、Bluetooth、Wi-Fi、不可聴のサウンドを使用して、近くにあるデバイスを検出します。

不可聴のサウンドというのは、人には聞こえない周波数のようです。
端末によっては、聞こえる端末もあるようです。
若い人には聞こえやすいかもしれません。

データのやりとりはサーバーを介して行われます。
デバイスは同じネットワークに接続している必要はありませんが、ネットワークに接続している必要があります。

Nearby Messages APIは、AndroidだけでなくiOSにも対応しています。
AndroidとiOSの両方のOSで利用可能です。

Nearby Connections API

Nearby Connections APIは、ローカルネットワーク上の他のデバイスを検出し接続します。

Androidで顔検出APIが使えるようになった

Gppgle Play サービス 7.8から、Face APIを利用できるようになりました。
画像やカメラの映像から人の顔を検出ことができます。

顔検出を行うにはOpenCVのライブラリを使う必要がありました。
Google Play サービスで提供されるようになったので手軽に使用できます。

サンプルアプリケーション

サンプルアプリケーションはGitHubに公開されています。

photo-demoは、顔の画像から特徴点を検出するサンプルです。

face01

ソースコードを見てみます。

顔検出のためのインスタンスを作成します。

PhotoViewerActivityクラスのonCreateメソッドで検出しています。

静止画のため、setTrackingEnabledをfalseにします。
動画や連続した画像の時はtrueにします。

すべてのランドマークを検出するためにsetLandmarkTypeをALL_LANDMARKSに設定します。

FaceDetector detector = new FaceDetector.Builder(getApplicationContext())
        .setTrackingEnabled(false)
        .setLandmarkType(FaceDetector.ALL_LANDMARKS)
        .build();

SafeFaceDetectorを使うのは、非常に小さな画像上の顔検出のバグを回避するための処理とのこと。

Detector<Face> safeDetector = new SafeFaceDetector(detector);

画像からFrameを作成します。

Frame frame = new Frame.Builder().setBitmap(bitmap).build();

作成したFrameから顔検出処理を実行します。

SparseArray<Face> faces = safeDetector.detect(frame);

Faceクラスのインスタンスのコレクションを取得しました。
これをFaceViewクラスのdrawFaceAnnotationsメソッドで描画しています。

個々のFaceインスタンスの座標を取得して円を描画しています。

for (int i = 0; i < mFaces.size(); ++i) {
    Face face = mFaces.valueAt(i);
    for (Landmark landmark : face.getLandmarks()) {
        int cx = (int) (landmark.getPosition().x * scale);
        int cy = (int) (landmark.getPosition().y * scale);
        canvas.drawCircle(cx, cy, 10, paint);
    }
}

サンプルを少し変更して、笑顔の可能性を取得してみます。

FaceDetector detector = new FaceDetector.Builder(getApplicationContext())
        .setTrackingEnabled(false)
        .setLandmarkType(FaceDetector.ALL_LANDMARKS)
        .setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
        .build();

Detector<Face> safeDetector = new SafeFaceDetector(detector);

Frame frame = new Frame.Builder().setBitmap(bitmap).build();
SparseArray<Face> faces = safeDetector.detect(frame);

if (faces.size() > 0) {
    String text =  "SmilingProbability: " + String.format("%.2f", faces.get(0).getIsSmilingProbability());
    Toast.makeText(this,text, Toast.LENGTH_LONG).show();
}

face02

「SmilingProbability: 0.59」と表示されました。
値は0.0から1.0の間を取るようです。
やや笑顔かな、といったところでしょうか。

サンプルアプリケーションは他に、「FaceTracker」と、「multi-tracker」があります。
カメラの映像から認識するサンプルです。

Androidでバーコード検出APIが使えるようになった。

Gppgle Play サービス 7.8から、バーコード検出機能を利用できるようになりました。
以下の種類のバーコードに対応しています。
1D barcodes: EAN-13, EAN-8, UPC-A, UPC-E, Code-39, Code-93, Code-128, ITF, Codabar
2D barcodes: QR Code, Data Matrix, PDF-417, Aztec
バーコード検出APIを使ったサンプルはGitHubで公開されています。

Gppgle Play サービス 7.8から、バーコード検出機能を利用できるようになりました。

これによって、高機能なバーコード読み取り機能を簡単に使えます。

サポートされているバーコードの種類

以下の種類のバーコードに対応しています。

1D barcodes

  • EAN-13
  • EAN-8
  • UPC-A
  • UPC-E
  • Code-39
  • Code-93
  • Code-128
  • ITF
  • Codabar

2D barcodes

  • QR Code
  • Data Matrix
  • PDF-417
  • Aztec

サンプルアプリケーション

バーコード検出APIを使ったサンプルはGitHubで公開されています。

実行するとカメラの設定と開始ボタンが表示されます。

barcode01

開始すると、カメラの映像からバーコードを読み取ります。
2つのバーコードを両方とも認識しています。

barcode02

取得したいバーコードをタップします。
タップすると、タップした方のバーコードの値が表示されます。

barcode03

関連リンク

Google Play Servicesとは

Google Play Servicesについて調査中。

Google Play Servicesとは

  • 日本語では「Google Play開発者サービス」
  • Googleが提供するサービスを利用するためのクラスライブラリ。
  • Googleの各種サービスを利用するのに必須。認証処理など。
  • Google Play Servicesはシステムに常駐してバックグラウンドで動いている。
    googleplayservices002
  • Google Play Storeで入手できる。
    googleplayservices001
  • Androidスマートフォン・タブレットには、プレインストールされている。
    ※ Kindle(FireOS)にはインストールされていない。
  • Android OSとは別個に更新される。
    • OSのアップグレード用パッケージは端末メーカーから提供されるが、Google Play ServiceはGoogleから提供される。
  • Android 2.3(APIレベル9)以上でGoogle Playストアアプリがインストールされていれば、古い端末でも最新のGoogleのサービスを利用したアプリを利用できる。

APIのリスト

Google Play サービスの主なAPIリスト

  • Google+
  • Google Account Login
  • Google Actions, Base Client Library
  • Google Address API
  • Google App Indexing
  • Google App Invites
  • Google Analytics
  • Google Cast
  • Google Cloud Messaging
  • Google Drive
  • Google Fit
  • Google Location, Activity Recognition, and Places
  • Google Maps
  • Google Mobile Ads
  • Mobile Vision
  • Google Nearby
  • Google Panorama Viewer
  • Google Play Game services
  • SafetyNet
  • Google Wallet
  • Android Wear

Android Studioで利用するには

build.graleを開きます。

次の行を追加します。

apply plugin: 'com.android.application'
...

dependencies {
    compile 'com.google.android.gms:play-services:8.4.0'
}

保存したら、メニューから「Tools」→「Android」→「Sync Project with Gradle Files」を選択して、設定を反映します。

必要なAPIを選択する

Androidアプリが持つことのできるメソッドは最大65536個までの制限があります。
このメソッド数にはフレームワークやライブラリのメソッドも含まれています。

Google Play Services バージョン6.5から、必要なAPIだけを選択してコンパイルできるようになりました。

compile 'com.google.android.gms:play-services-fitness:8.4.0'
compile 'com.google.android.gms:play-services-wearable:8.4.0'

アプリでGoogle Play Servicesが利用できるか確認する

GoogleApiClient.BuilderでGoogleApiClientの設定を行います。
このとき、コールバックを受け取るようにします。

mGoogleApiClient = new GoogleApiClient.Builder(this)
    .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
    })
    .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
    })

使用するAPIを指定します。

.addApi(Drive.API)

必要なOAuthのスコープを指定します。

.addScope(Drive.SCOPE_FILE)

build()メソッドでインスタンスを生成します。

.build();

接続に失敗したとき、認証が必要であれば、認証処理を行います。

.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
    /**
     * 接続エラーが発生したとき
     * @param connectionResult
     */
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.d(TAG, "onConnectionFailed");
        Log.d(TAG, connectionResult.toString());
        switch (connectionResult.getErrorCode()) {
            //認証が必要な場合
            case ConnectionResult.SIGN_IN_REQUIRED:
                try {
                    connectionResult.startResolutionForResult(MainActivity.this, REQUEST_OAUTH);
                } catch (IntentSender.SendIntentException e) {
                    e.printStackTrace();
                }
        }
    }
})

認証できたら接続します。

/**
 * 認証が終わった時の処理
 */
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case REQUEST_OAUTH:
            if (resultCode != Activity.RESULT_OK) {
                return;
            }
            mGoogleApiClient.connect();
            return;
    }
}

全体としては次のようなコードになります。

private static final String TAG = "MainActivity";
private static final int REQUEST_OAUTH = 999;
private GoogleApiClient mGoogleApiClient;


@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    checkGooglePlayServices();
}

void checkGooglePlayServices() {
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                /**
                 * 接続に成功したとき
                 * @param bundle
                 */
                @Override
                public void onConnected(Bundle bundle) {
                    Log.d(TAG, "onConnected");
                }

                /**
                 * 接続が中断されたとき
                 * @param i
                 */
                @Override
                public void onConnectionSuspended(int i) {
                    Log.d(TAG, "onConnectionSuspended");
                }
            })
            .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                /**
                 * 接続エラーが発生したとき
                 * @param connectionResult
                 */
                @Override
                public void onConnectionFailed(ConnectionResult connectionResult) {
                    Log.d(TAG, "onConnectionFailed");
                    Log.d(TAG, connectionResult.toString());
                    switch (connectionResult.getErrorCode()) {
                        //認証が必要な場合
                        case ConnectionResult.SIGN_IN_REQUIRED:
                            try {
                                connectionResult.startResolutionForResult(MainActivity.this, REQUEST_OAUTH);
                            } catch (IntentSender.SendIntentException e) {
                                e.printStackTrace();
                            }
                    }
                }
            })
            .addApi(Drive.API)
            .addScope(Drive.SCOPE_FILE)
            .build();
    mGoogleApiClient.connect();
}

/**
 * 認証が終わった時の処理
 */
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case REQUEST_OAUTH:
            if (resultCode != Activity.RESULT_OK) {
                return;
            }
            mGoogleApiClient.connect();
            return;
    }
}

@Override
protected void onStop() {
    mGoogleApiClient.disconnect();
    super.onStop();
}

AndroidのWebViewで位置情報を使用するには

AndroidのWebViewで位置情報を使用するポイントは次の3つです。

(1) パーミッションを有効にする

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

(2) WebViewのJavaScriptと位置情報を有効にする

WebSettings settings = webView.getSettings();
settings.setGeolocationEnabled(true);
settings.setJavaScriptEnabled(true);

(3) WebChromeClientのonGeolocationPermissionsShowPromptメソッドをオーバーライドして位置情報の取得を許可する

    webView.setWebChromeClient(new WebChromeClient() {
        @Override
        public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
            callback.invoke(origin, true, false);
        }
    });

現在の位置をWebViewに表示するサンプルアプリケーションを作成します。

AndroidManifest.xmlに次の行を追加します。

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

画面いっぱいにWebViewを配置します。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="jp.gesource.webviewsample.MainActivity">

    <WebView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/webView"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true" />

</android.support.design.widget.CoordinatorLayout>

WebViewの設定を行い、位置を表示します。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    WebView webView = (WebView) findViewById(R.id.webView);
    //(2) WebViewのJavaScriptと位置情報を有効にする
    WebSettings settings = webView.getSettings();
    settings.setGeolocationEnabled(true);
    settings.setJavaScriptEnabled(true);

    //(3) WebChromeClientのonGeolocationPermissionsShowPromptメソッドをオーバーライドして
    //    位置情報の取得を許可する
    webView.setWebChromeClient(new WebChromeClient() {
        @Override
        public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
            callback.invoke(origin, true, false);
        }
    });

    // 位置を表示する
    String html =
            "<!DOCTYPE html><html lang=\"ja\"><head><meta charset=utf-8><script>\n" +
            "navigator.geolocation.getCurrentPosition(successCallback, errorCallback);\n" +
            "function successCallback(position) {\n" +
            "  var s = \"緯度:\" + position.coords.latitude + \"<br>\";\n" +
            "  s += \"経度:\" + position.coords.longitude + \"<br>\";\n" +
            "  document.getElementById(\"show_result\").innerHTML = s;\n" +
            "}\n" +
            "function errorCallback(error) {\n" +
            "  var err_msg = \"\";\n" +
            "  switch(error.code) {\n" +
            "  case 1:\n" +
            "    err_msg = \"位置情報の利用が許可されていません\";\n" +
            "    break;\n" +
            "  case 2:\n" +
            "    err_msg = \"デバイスの位置が判定できません\";\n" +
            "    break;\n" +
            "  case 3:\n" +
            "    err_msg = \"タイムアウトしました\";\n" +
            "    break;\n" +
            "  }\n" +
            "  document.getElementById(\"show_result\").innerHTML = err_msg;\n" +
            "}\n" +
            "</script></head><body><p>あなたの現在位置</p><div id=\"show_result\"></div></body></html>";
    webView.loadData(html, "text/html; charset=UTF-8", null);
}

アプリケーションを実行します。
緯度と経度が表示されたら成功です。

Android Studioでベクター画像を使う

Android Studio 1.4からSVGなどのベクター画像を使用できるようになりました。
画質の損失なしに拡大縮小でき、アプリのサイズも小さくすることができます。
様々な画面サイズ・解像度の端末のサポートが簡単になります。

使い方

ファイルを用意する

Android Studioで「app」→「res」フォルダーを右クリックし、ポップアップメニューから「New」→「Vector Asset」を選択します。

001

Vector Asset Studioウィンドウが表示されます。

002

ファイルを用意している場合は、「Local SVG file」を選択して、ファイルの場所を指定します。

022

Android Studioに用意されているアイコンを使用する場合は、「Material Icon」を選択して、「Icon」の「Choose」ボタンを押します。
Select Iconウィンドウが表示されます。

003

いろんなアイコンが用意されています。

004

005

006

007

008

009

010

011

012

013

014

015

016

017

018

アイコンを選択して「OK」ボタンを押すと、Vector Asset Studioウィンドウに戻ります。
選択したアイコンが表示されます。

019

「Next」ボタンを押します。

020

ファイルの保存場所を選んで「Finish」ボタンを押すと、指定した場所にファイルが保存されます。

021

ファイルを表示する

レイアウトファイル(「app」→「resources」→「layout」→「activity_main.xml」)を開きます。

ImageViewを配置し、「src」プロパティにベクター画像のパスを設定します。

023

<ImageView
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/imageView"
  android:layout_alignParentStart="true"
  android:src="@drawable/ic_android_24dp" />

024

実行すると、画像が表示されました。

025

Android StudioでAndroid SDKやJDKのパスを設定するには

新しくJDKをインストールしたとき、Android StudioでJDKのパスを設定する必要があります。

設定は次の手順で行います。

Android Studioを起動します。

「Configure」を選択します。

androidstudio22

「Project Defaults」を選択します。

androidstudio23

「Project Structure」を選択します。

androidstudio24

Android SDKやJDKのパスを設定して、「Apply」ボタンを押します。

androidstudio25

以上です。

Android Studio 1.3でNDKの設定を行う

Android Studioのインストールが完了したら、Android Studioを起動します。

起動すると、Androidアプリケーションの開発に必要なプログラムのダウンロードが始まります。

androidstudio12

Andriod Studioが起動したら、メニューから「Configure」→「Settings」を選択して、設定画面を表示します。

androidstudio19

androidstudio20

「Appearance & Behavior」→「System Settings」→「Android SDK」を選択します。

「SDK Platforms」タブではインストールされているSDKのプラットフォームが表示されています。

androidstudio13

最新版である「Android 5.1 (Lolipop)」がインストールされています。
インストールするSDKをチェックして「Apply」ボタンを押すと、SDKをインストールできます。

「SDK Tools」タブではインストールされているツールが表示されてます。

androidstudio15

NDKを使用する場合は「Android NDK」をチェックして「Apply」ボタンを押します。

Android NDK Componentのインストールが始まります。

androidstudio16

インストールが終わったら、NDKのパスを設定します。

「Configure」→「Settings」→「Project Defaults」→「Project Structure」を選択します。

androidstudio19

androidstudio20

androidstudio22

androidstudio23

androidstudio24

「Android NDK location」欄が空欄になっています。「Select default NDK」の「Select」をクリックします。

androidstudio17

自動的にNDKのパスが設定されます。

androidstudio18