2020年8月30日日曜日

GDPR

古いアプリをAndroid10に対応する必要があるのですが、ついでにAdmobも最新のルールにそって実装しておこうかなと思ったのですが、ちょっと躓いてしまいました。

 GDPR(一般データ保護規則)です。

EU圏向けに対応する必要があるらしく、結構面倒そう。

ひとまず勉強中です。

2020年8月29日土曜日

Androidの古いアプリのターゲットを「Android 10(API レベル 29)」にしたので、その記録

やりたいこと
だいぶ前に作ったAndroidのアプリがあります。
最終更新が2015年のアプリと2016年のアプリです。
この古いアプリのターゲットをAndoroid10(APIレベル29)に変更することが目的です。

Googleから2020年11月2日までにターゲットにAndroid10を含めてねというメールが来たためとなります。

Googleからのメールには移行ガイドのリンクが張ってあったのですが、そのページをみても具体的にどうすればよいのかよくわかりませんでした。

そこで、ひとまず古いアプリのプロジェクトをAndroid Studio 4.0.1で開いてみたのですが、よくわからないエラーがいろいろでてきました。

ググりながらなんとか立ち上がるところまでできたので、手順を記録しておきます。
環境
OS : Windows10 1909
IDE : Android Studio 4.0.1
古いAndroidアプリの情報
 compileSdkVersion 21
 buildToolsVersion "21.1.2"
 minSdkVersion 9
 targetSdkVersion 21

やったこと① バックアップ
失敗すると悪いので、いったん昔のプロジェクトのフォルダをコピーしてから作業をしました。
やったこと② 開く
Android Studioでプロジェクトを開く。
開くとConvertProjectのメッセージが表示されたので、Convertボタンを押す。

そのあと、Android SDKのパスも前と違うよというメッセージが表示されました。
これはSDKのパスを以前と変えたのででたようです。通常は表示されないです。
やったこと③ versionを修正
build.gradleのバージョンを最近作ったアプリのバージョンに合わせました。
 compileSdkVersion 29
 buildToolsVersion "30.0.0"
 minSdkVersion 16
 targetSdkVersion 29

build.gradleのclasspathのバージョンも修正
classpath 'com.android.tools.build:gradle:4.0.0'

gradle-wrapper.propertiesのdistributionUrlを修正
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

このあたりはあまり深く考えずにやってみました。
やったこと④ Migrate to AndroidX
appcompat-v7, support-v4が解決できないみたいなエラーがでているので、これを解消するために下記の手順を実施しました。
Refactorメニュー - Migrate to AndroidX... を選択する。

次のメッセージが表示されるので、Migrateボタンを押す。
※backup project as Zip fileをONにしておくと、バックアップしてくれます。不要ならOFFにします。

この段階だとまだMigrateは完了してないです。Migrateするために、Do Refactorボタンを押します。
ステータスバーがくるくるしてちょっと待つと終わります。
やったこと⑤ compileを修正
compileを直した方がよいようなので修正しました。
マウスカーソルを合わせると電球のマークが表示されるので、そこからcompileをimplementationに置き換えるメニューを選択すると直してくれます。
やったこと⑥ ActionBarActivityをおきかえ
ActionBarActivityを使っていたのですが、そこでエラーがでてたので、AppCompatActivityに置換しました。
//import android.support.v7.app.ActionBarActivity;
import androidx.appcompat.app.AppCompatActivity;
~途中省略~
//public class MainActivity extends ActionBarActivity implements SensorEventListener {
public class MainActivity extends AppCompatActivity implements SensorEventListener {

おわり
ここまでやったところ、エラーの表示がなくなり、エミュレーターで起動できるようになりました。
自分のアプリはこれで解決しましたが、使ってるクラスや機能によって⑥以降は違ってくるはずです。

2020年7月30日木曜日

Android10 充電ケーブル 抜き差しのイベント来ない?

充電ケーブルの抜き差しのイベントを検知するアプリを作っているのですが、
Android10でテストしたところ、うまく動作しないことがわかりました。
(イベントを検知するやり方を記載した記事はこちらです。)

プログラムは、公式の情報を参考にしているので、間違えはなさそう。
他のOSのバージョンもテストしたところ、Android8, Android9は問題なし。

ちなみにテストはemulatorで実施しており、emulatorの電源の状態を変更するコントロールでAC charger / noneを切り替えて実施してます。

環境

Android Studio : 
スマホ : emulator Pixel 3a API29 (android10)

これが原因か?①

emulatorを起動すると、常にUSB充電中となっていることがわかりました。

これについては、開発モードにはいって(ビルド番号を何回か連打)、USBデバックをOFFにすると解除されることが分かりました。

→ USB充電中は解除されたが、電源のイベントを拾えないのは変わらず。

※Android9もUSBデバックはONだったので、emulatorはONになっているのかもしれません。


これが原因か?②

emulator (もしくはOS)が悪そう。
emulatorのバッテリーのコントロールパネルで電源の状態を変更しても、
emulator側に反映されていないことが分かりました。

左がemulator, 右がコントロールパネルです。
levelやconnectionがあっていないことが分かります。



これが原因か?③

②の原因ですが、Android10で「電池使用料のトラッキング」について変更が入ってます。
これが原因かどうかはもうちょっと情報を集めてみようと思います。

以下引用です。
------------------------
Android 10 以降では、主要な充電イベントの後にデバイスのプラグを抜くと、SystemHealthManager によって電池使用量の統計情報がリセットされます。主要な充電イベントとはつまり、デバイスが完全に充電されること、またはほとんど電池残量がない状態からほぼ完全に充電された状態になることです。

Android 10 より前は、電池残量がほとんど変化していなくても、デバイスのプラグを抜くと電池使用量の統計情報がリセットされていました。
------------------------


結論

すみません。まだよくわかってません。





2020年7月23日木曜日

Android Foreground Serviceのメモ

やりたいこと
ボタンを押すと、フォアグラウンドサービスを起動するやり方を記録しておきます。

環境
OS : Windwos10
Android Studio 4.0
言語 : java

参考にしたサイト
■Foreground Serviceの基本
https://qiita.com/naoi/items/03e76d10948fe0d45597
おおむねこちらのページを参考にしてます。

■公式情報
https://developer.android.com/guide/components/services?hl=ja
公式情報も参考にしました。

背景
※ここは読み飛ばしていただいて結構です。※
充電ケーブルが接続されたかどうかを検知して、何か処理するというアプリを検討中なのですが、通常のアプリだと裏側にいると勝手にOSによって終了させられてしまいます。

Androidの公式ページでこのように記載されてます。
「システムは RAM を解放する必要がある場合にプロセスを強制終了します。システムが特定のプロセスを強制終了する可能性は、...」
https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ja

また、Android7まではマニフェストに「イベントを検知するよ。」という一文を書いておくと、アプリのプロセスがいなくても、イベントが発生すると再起動してくれたのですが、Android8以降はアプリのプロセスがいないと、イベントがひろえなくなってしまいました。
(イベントを検知する仕組みの詳細はこちらを参考にしてください。)

じゃあどうするかというと、アプリをServiceとして裏側で生かしておいて、常にイベントを検知できるようすればよいということになります。

それで終わりかというと、Android8以降ではまたルール改正が発生しており、Serviceをバックグラウンド(通知とかなにもださずに)で動作させていると、強制終了させられるというルールが追加されました。
詳しくはこちらを参考にしてください。


ルール改正がはげしい...


じゃあどうするかというと、その答えがForegroundServiceとなります。

ということで、前置きが長くなりましたがForegroundServiceの勉強をしたので、メモしておきます。
ざっくり言うと
ざっくりいうと以下の手順となります。
・serviceクラス作成
   この中で自分がForegroundであることを知らせるための処理をごちゃごちゃいれる。
・通知用のアイコン準備 ・Manifestにserviceクラスを登録
・MainActivityからserviceを起動

それでは、細かく書いていきます。

Step1.準備
1.Android StudioでEmpty Activityでプロジェクトを作成する。
Name : MyApp
Package name : test.test ← 適当につけました。
Minimum SDK : Api 16 (android 4.1) ←デフォルトのまま

2.ボタンを1個配置して、OnClickListenerを作る。
後でこのボタンを押すとServiceが起動するようにします。
大事なところはまだですが、ここまではこんな感じのコードになります。
package test.test;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // ボタンのイベント
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // サービスを呼ぶ
            }
        });
    }
}
Step2.通知用のアイコン作成
serviceクラスから通知(notification)を行う必要があるのですが、通知用の小さいアイコンが必須になるので、事前に作っておきます。

(1)app - res - drawable - 右クリック - New - Image Assetを選択

(2)下図の①のところをNotification Iconsに設定、
    ②のところに任意の名称を設定(ここではic_service_notificationに設定しました)、
    ③でiconの形状を設定(ここではデフォルトのままにしました。)します。

  (3)設定したらNext - Finish
Step3.serviceクラス作成
package test.test;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;

import androidx.core.app.NotificationCompat;


//(1) Serviceの派生クラスを作成
public class MyService  extends Service {

    //(2) 必須のメソッド。 バインドしない場合はnullを返す。
    @Override
    public IBinder onBind(Intent intent){
        return null;
    }

    //(3.3)の一部  startforegroudnに渡すIDを定義する。 0以外の数字 。
    private static final int ONGOING_NOTIFICATION_ID = 1;

    //(3) 呼び出し側でstartService()かstartForegroundService() をすると呼び出されます。
    @Override
    public int onStartCommand(Intent intent,
                              int flags,
                              int startId) {

        //(3.1)  チャンネルIDの登録
        String channelID = "MY_CHANNEL_ID"; // 通知チャンネル用のID。適当な名前を定義 
        createNotificationChannel(channelID);

        // 通知をタップしたときにアプリを呼び出す準備
        Intent notificationIntent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent =  PendingIntent.getActivity(this, 0, notificationIntent, 0);

        // 通知の準備
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelID)
                .setContentTitle("通知のタイトル")
                .setContentText("通知の内容")
                .setSmallIcon(R.drawable.ic_service_notification)
                .setContentIntent(pendingIntent)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT);

        // (3.3) このAPIを呼ぶと、フォアグラウンドサービスとなる。
        startForeground(ONGOING_NOTIFICATION_ID, builder.build());

        // (3.4) 戻り値を指定。
        return START_STICKY;
    }


    // 通知のチャンネルを作成する。この関数はほぼもってきたそのまま。
    // https://developer.android.com/training/notify-user/build-notification?hl=ja
    private void createNotificationChannel(String channelID) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            //アプリ - 通知 に表示される情報
            CharSequence name = "サービス起動中の通知"; // なんでもOK。
            String description = "サービス起動中の通知は、XXXのための通知です。"; // なんでもOK。

            // 通知のレベルとか名称とか設定
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(channelID, name, importance);
            channel.setDescription(description);

            // 通知の登録
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }
}

上記コードの説明
(1)serviceクラスを継承したクラスを作成します。
(2)onBind()を実装。必須のメソッドとなります。
  bindしない場合はreturn nullでOKです。
(3)onStartCommand()を実装。
  親アプリがstartService()をするとこちらが呼ばれます。
(3.1) createNotificationChannelをして、通知チャンネルを作成。Android8以降で必要。
    設定 - 通知で使用される機能になります。
(3.2) 通知の準備
フォアグラウンドサービスにするには、通知を表示して、生きていることをユーザーに知らせる必要があるため、必須となります。 その準備として、通知のタイトルや通知のアイコンなどを準備してます。

 (3.3) startForeground()を実行
このAPIを呼び出すことでフォアグラウンドサービスとなります。第一引数のidには0以外 の値を設定する必要があります。ここでは1を指定しました。

(3.4) 戻り値を設定( 3種類あるので用途に合わせて設定 )

このコードでは文字列は直値を記載してますが、実際はstrings.xmlに定義して、使用したほうが良いと思います。

Step4.manifest
(1) applicationの中に、<service android:name=".MyService">を追加します。
(2) application>の外に、<uses-permission android:name="android.permission.FOREGROUND_SERVICE">を追加します。

(2)のほうは、Android9以降では必須となります。

以下xml全体となります。うまくコードをはれなかったので、図を張ってます。

Step5.MainActivityからサービスを呼ぶ
package test.test;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // ボタンのイベント
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // サービスを呼ぶ
                Intent serviceIntent = new Intent(MainActivity.this, MyService.class);
                startService(serviceIntent);
            }
        });
    }
}
Step5.テスト
emulatorの5.1と10で動くことだけ確認しました。

2020年7月10日金曜日

最近勉強したことのメモ ( Android )

SQLite
こちらを参考に実装しました。
大変参考になりました。
https://qiita.com/kengo_kuwahara/items/a8ef858a9810cad42ca6
Fragment
Fragmentがいくつかあって、それぞれから共通の処理を呼ぶときどうするのかな?
というのがよくわかりませんでした。

DBのアクセスを複数のFragmentからアクセスしたかったためです。

あってるのかどうかは不明ですが、以下のように実装しました。

1.MainActivityに共通の処理を記載する(SQLのHelperクラスを持つ)
2.MainActivityにDBを読むメソッドを追加する。
3.FragmentでgetActivity()をしてMainactivityを取得してMainActivityのメソッドを使う。
ConstraintLayout
なんか思ったように動かないんですが、
効率もよいとの情報もあり、BasicActivityを選択するとこれでUIが作られるので、これを使ってます。

・コネクタでUIの要素どうしをつなぐ。
・余白と、layout_widh, layout_heightで微調整する。
これをAndroid StudioのAtrributeで調整したほうがよい。
(指定できないものとかもあるので)
・たとえばmach_parentは使えないようだが、xmlとしてはかけてしまう。

2020年6月23日火曜日

Android 充電ケーブル挿す、抜くのイベントを拾う

やりたいこと
Android で充電ケーブルを挿したときと、抜いた時のイベントを拾う方法を勉強したので、手順をまとめておきます。

環境
Windows 10
Android Studio : 4.0

テスト環境
Nexus S API 22(emulator) : Android5.1
Pixel 3 API 26(emulator) : Android8.0

参考にしたサイト

[(1)ブロードキャストレシーバーについて]
https://developer.android.com/guide/components/broadcasts
[(2)電源管理について]
https://developer.android.com/training/monitoring-device-state/battery-monitoring?hl=ja

ざっくりと
ざっくり説明すると以下の手順となります。
(1)manifestにひろいたいイベントと関数をかく。(android7まで用)
(2)イベントを拾う用のクラスを作成する。そのクラスはBroadcastReceiverを継承させる。
(3)イベントを拾う用のクラスをnewして、レシーバーとして登録する。

Android7までなら(1)と(2)だけでイベントを拾うことができます。 アプリがいなくてもイベントが拾えます。

Android8以降は(1)と(2)だけではイベントを拾えなくなってるので、(3)の手順が必要になります。 しかもAndroid8以降はアプリが起きていないと拾えなくなってます。 この辺のことは参考にしたサイト(1)に詳しく記載されております。

以降は手順を細かく記載します。

Step1.Manifestの登録
AndroidManifest.xmlに下記の6行を追加します。
        
            
                <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
            
        
「PowerConnectionReceiver」はイベントをキャッチするクラスの名前です。
 任意の名前でOKです。
「ACTION_POWER_CONNECTED」は接続したときのイベントを拾うときのIntent、
「ACTION_POWER_DISCONNECTED」は抜いた時のイベントを拾うときのIntentとなります。

manifest全体ではこうなります。
 



    
        
            
                <action android:name="android.intent.action.MAIN" /&gt

                <category android:name="android.intent.category.LAUNCHER" /&gt
            
        
        
    
        
            
                <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
            
        
       
       
    


Step2.イベントをキャッチするクラスを追加
2-1.下図のようにクラスを追加します。クラス名はStep1で定義したものと同じ名前としてください。


2-2.BroadcastReceiverを継承します。
 
import android.content.BroadcastReceiver;

public class PowerConnectionReceiver extends BroadcastReceiver {
}
2-3.onReceiveメソッドを追加します。
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class PowerConnectionReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        
    }
}
2-4.イベントを拾ったときに文字列を画面に表示するようにしておきました。
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class PowerConnectionReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {

        if (intent.getAction() == Intent.ACTION_POWER_CONNECTED)  {
            Toast.makeText(context, "ケーブルを接続した", Toast.LENGTH_LONG ).show();
        }
        else if (intent.getAction() == Intent.ACTION_POWER_DISCONNECTED) {
            Toast.makeText(context, "ケーブルを抜いた", Toast.LENGTH_LONG ).show();
        }
    }
}

Android 7以前が対象の場合は、Step1, Step2までで完了となります。Android5.1で動作することが確認できました。
さらにAndroid 7以前の場合はアプリが起動中でなくても、このイベントが発生するとアプリが起動します。
Step3.レシーバーを明示的に登録する
Android8以降はStep1のmanifestの方法でイベントがひろえなくなってます。
そこで、明示的にPowerConnectionReceiverをnewしてレシーバーとして登録しておきます。
以下のようなコードとなります。
 
        BroadcastReceiver br = new PowerConnectionReceiver();
        IntentFilter filter = new IntentFilter(Intent.ACTION_POWER_CONNECTED);
        filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        this.registerReceiver(br, filter);

これをMainActivityのonCreateメソッドに追加しました。
 
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        // ここから追加
        BroadcastReceiver br = new PowerConnectionReceiver();
        IntentFilter filter = new IntentFilter(Intent.ACTION_POWER_CONNECTED);
        filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        this.registerReceiver(br, filter);

    }
※Googleのサイトの用語でいうと「コンテキスト登録されたレシーバー」というものになります。

Step4.テスト
emulatorで電源挿抜のテストをします。
emulatorの右の操作パネルの一番したのボタンをクリックします。

「Extended controls」ダイアログが表示されるので、Batteryを選択し、Charger connectionを操作します。

ここを操作すると電源ケーブルの挿抜の操作が仮想的にできます。
emulatorの下側に文字列が表示されたら成功です。

Android8でも確認。できました。

 
Step5.今後の課題
Android8以降は、アプリが生存していないとイベントを拾えないので、そこを解決したいです。 ヒントとなるのが、JobScheduler とserviceかなと考えてるので、この辺を勉強していきたいと思ってます。

2020年6月22日月曜日

Appium 入門6 ( 電卓のボタン押す)

はじめに
Appiumを使って、電卓のボタンを押すところまでやってみました。
今までjavascriptでやってきたのですが、うまくできなかったために、javaに乗り換えました。

環境
OS : Windows 10
スマホ : emulator (Pixel3 API 26)
Android Studio : 3.6.3
Appium : 1.17.1
言語 : java

参考にしたサイト

https://www.swtestacademy.com/appium-tutorial/
↑ 細かく書かれており、とても参考になりました。

1.準備
appium等のインストールは完了した状態で実施しております。
まだの場合はAppium入門1 を参考に、 javascriptでサンプルコードが動作するところまで実施できるようにしてみてください。
2.intellij IDEA をインストール
下記のサイトから無料版(コミュニティ)の方をダウンロードします。  https://www.jetbrains.com/ja-jp/idea/download/#section=windows
3.dependencies.の設定
3-1. IntelliJ IDEAを起動する。
3-2.+ Create New Projectを押す。
3-3.Mavenを選択して、Nextボタンを押す

3-4.Nameに適当な名前を付けてFinishボタンを押す。
3-5.https://mvnrepository.com/ にアクセスする。
3-6.「io.appium」 で検索する。
java-client - 最新のバージョンを選択し、mavenというタブに表示されたxmlをコピーする。
 3-7.コピーしたものをintellj IDEAのpom.xmlにペーストする。
dependenciesというタグで囲む必要があります。
同じようにselenium-javaと, testngもコピペします。
最終的にはこんな感じになります。
 
	
    4.0.0

    org.example
    untitled
    1.0-SNAPSHOT

  
    
        
        
            io.appium
            java-client
            7.3.0
        

        
        
            org.seleniumhq.selenium
            selenium-java
            3.141.59
        

        
        
            org.testng
            testng
            7.1.0
            test
        

    
  
  
	
3-8. versionの数字が赤い場合は、下図の更新ボタンを押すか、intelljを再起動すると     読み込まれるようです。(この辺はちょっと怪しいです。) 
 
4.inspectorで情報取得
4-1. emulator を起動
4-2. 電卓アプリを起動
4-3. appiumを起動
4-4. uiautomatorviewerを起動
4-5. 電卓の1のボタンのresource-idを後で使います。
※uiautomatorviewerの使い方はこちらを参考にしてください。

5.emulatorの情報取得
5-1.emulatorを起動しておく
5-2.PCでコマンドプロンプトを起動する。
5-3.adb devices とコマンドプロンプトに打ち込んで実行する。
※adbコマンドが実行できない場合は、環境変数のpathに%ANDROID_HOME%\platform-toolsを追加するか、platform-toolsにcdで移動して、実行してください
5-4.表示された文字列を後で使います。

6.javaのコードを作成
6-1. intellJ IDEAで src - test - javaを右クリック
New - Java Class を追加 - 任意の名前でクラスを追加する。

6-2. ここまでで集めた情報を使って次のコードを書きます。
①~④のところに今まで集めた情報を入れます。 後は特に変更しませんでした。
参考にしたサイトのコードとは少し異なります。不要と思うところを削除したためです。

      import io.appium.java_client.MobileElement;
      import io.appium.java_client.android.AndroidDriver;
      import org.openqa.selenium.By;
      import org.openqa.selenium.remote.DesiredCapabilities;
      import org.openqa.selenium.support.ui.ExpectedConditions;
      import org.openqa.selenium.support.ui.WebDriverWait;
      import org.testng.annotations.AfterMethod;
      import org.testng.annotations.BeforeMethod;
      import org.testng.annotations.Test;

      import java.net.MalformedURLException;
      import java.net.URL;

      public class calctest {
          public AndroidDriver driver;
          public WebDriverWait wait;

          @BeforeMethod
          public void setup () throws MalformedURLException {
              DesiredCapabilities caps = new DesiredCapabilities();
              caps.setCapability("deviceName", "Pixel 3 API 26"); // ①emulatorの名前
              caps.setCapability("udid", "emulator-5554");        // ②adb devicesコマンドの結果
              caps.setCapability("platformName", "Android");      // ③androidなのでandroid

              driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"),caps);
              wait = new WebDriverWait(driver, 10);
          }

          @Test
          public void basicTest () throws InterruptedException {
              //Click and pass Splash
              wait.until(ExpectedConditions.visibilityOfElementLocated
                      (By.id("com.android.calculator2:id/digit_1"))).click();    // ④uiautomatorviewer.exeで取得した情報を
          }

          @AfterMethod
          public void teardown(){
              driver.quit();
          }
      }

      
	  

7.実行
7-1. emulatorを起動(「サーバーVXXXXXを起動する」ボタンも押しておく)
7-2. emulator上で電卓アプリを起動
7-3. intellj IDEAで、Runメニュー Runを押す。
7-4. 電卓の1が押せたら成功です。

8.できた
こんな感じで押せました。

2020年6月17日水曜日

Appium 入門5 ( UIの情報を取得する方法を整理)

はじめに
AppiumでUIの情報を取得する方法を整理します。
いろいろあるようなのですが、試してみてできたものを記載します。
使用した環境はこちらです。

環境

OS : Windows 10
スマホ : emulator (Pixel3 API 26)
Android Studio : 3.6.3
Appium : 1.17.1

準備
emulatorを起動させておきます。
1.android studio 起動
2.Toolsメニュー - AVD Manager
3.再生ボタンを押して起動する
4.情報を取得したいアプリを起動しておく。(電卓で試しました。)

(方法1)Appium Inspector を使う方法
1.コマンドプロンプトで、「adb devices」と打ち込みます。表示される情報を覚えておきます。


※adbコマンドが実行できない場合は、環境変数のpathに%ANDROID_HOME%\platform-toolsを追加するか、platform-toolsにcdで移動して、実行してください

2.Appiumを起動する。
3.サーバvXXXを起動するボタンを押す。
4.右上の虫眼鏡のボタンを押す。

5.しばらく待つと下の画面が表示されます。

6.左下のテキストボックスに次の値を入れます。
udid, テキスト, emulator-5554 ← 手順1で表示された文字列
platformName, テキスト, Android

7.セッションを開始するボタンを押す。
そうすると、emulatorの画面が表示され、マウスで要素をクリックすると右のペインに情報が表示されます。

できた。
ネットの情報や、「実践 Appium」ではWindowsではInspectorは使えないという記載があるため、 だめなパターンがあるのかもしれないです。電卓ではできました。


(方法2)uiautomatorviewer を使う方法

1 uiautomatorviewerを起動

%ANDROID_HOME%\tools\binにある「uiautomatorviewer.bat」をダブルクリックします。
※uiautomatorviewer.batが起動しない場合は、こちらが参考になるかもしれません。

2 uiautomatorviewerで情報を取得

起動したら、左上のボタンを押します。
ちょっと待つと画像が表示されるので、押したいボタンをマウスでクリックします。
右のペインにクリックしたボタンの情報が表示されます。

できた。
まとめ
AppiumでUIの情報を取得する方法を2通り、やってみました。
①Appium Inspector
②uiautomatorviewer
②は設定も特に不要で、起動するだけでよいので、こっちのほうが良いのかもしれません。
「実践 Appium」という書籍にはほかの方法も記載されていたので、試してたら追記します。

2020年6月14日日曜日

Appium 入門4 (javascript から javaに乗り換える)

前回までで、サンプルアプリのボタンが押せるところまでできたので、
別のアプリの操作をやってみようと思いました。

そこで、電卓のアプリのボタン操作にトライしてみたのですが、
javascrpt (webdriverio)だとやりかたがよくわからず、断念しました。

電卓のボタンの押し方がわからない...

ということで、javaに乗り換えることにしました。

こちらのwebページを参考に進めていったら、電卓のボタンが押せるところまでできました。


手順はこんど整理しようと思います。





2020年6月9日火曜日

Appium 入門 3 ( サンプルアプリのボタンを押す)

前回の記事で、サンプルアプリでHello World!を表示されるまでをやってみました。
やったというか、ただ公式サイトのサンプルコードをコピペしただけですが...

今回はこのサンプルアプリについているボタンを押すところをAppiumでやってみます。
サンプルアプリ(API Demos)を起動して、Views → Buttons と遷移させると表示されるボタンです。

初心者の自分にとってはとても大変でした。
環境
Windows 10
Android 5.1 (実機)
appium 1.15.1

1.概要
大まかには、この画面やボタンの情報を取得して、それを元に前回のjavascriptのコードに反映させるという流れとなります。

2.画面の情報取得( appActivity )
const opts = {
  path: '/wd/hub',
  port: 4723,
  capabilities: {
    platformName: "Android",
    platformVersion: "5.1",
    deviceName: "002628105537",
    app: "C:/Users/XXXXX/Desktop/appium_test/ApiDemos-debug.apk", 
    appPackage: "io.appium.android.apis",
    appActivity: ".view.TextFields",   // ← ここに入れる情報
    automationName: "UiAutomator2"
  }
};
javascriptの 「appActivity」に入れる情報を取得します。
実機の方でサンプルアプリを起動して、押下したいボタンの画面を表示しておき、
コマンドプロンプトで「adb shell」と打ち込みます。

※adbコマンドが実行できない場合は、環境変数のpathに%ANDROID_HOME%\platform-toolsを追加するか、platform-toolsにcdで移動して、実行してください。

そのままコマンドプロンプトで下記のコマンドを打ち込みます。
dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp'
表示された文字列の赤線の部分がappActivityに入れるものとなります。


参考にしたサイトはこちらです。

3.画面の情報取得( ボタンの情報)

3.1 uiautomatorviewerを起動

%ANDROID_HOME%\tools\binにある「uiautomatorviewer.bat」をダブルクリックします。
※uiautomatorviewer.batが起動しない場合は、こちらが参考になるかもしれません。

3.2 uiautomatorviewerで情報を取得

起動したら、左上のボタンを押します。
ちょっと待つと画像が表示されるので、押したいボタンをマウスでクリックします。
右のペインにクリックしたボタンの情報が表示されるので、class名をコピーしておきます。


4.クリックの表現方法を調べる
Appiumのサイトでコマンドのページがあるので、そちらを参考にしました。
「$('#SomeId').click();」


5.javascriptに反映
手順の2~4で調べた情報を元にjavascriptに反映します。
// javascript

const wdio = require("webdriverio");
const assert = require("assert");

const opts = {
  path: '/wd/hub',
  port: 4723,
  capabilities: {
    platformName: "Android",
    platformVersion: "5.1",
    deviceName: "002628105537",
    app: "C:/Users/XXXXX/Desktop/appium_test/ApiDemos-debug.apk",  // ここは環境に合わせてください。
    appPackage: "io.appium.android.apis",
    appActivity: ".view.Buttons1",     					// 手順2で調べたものに書き換える
    automationName: "UiAutomator2"
  }
};

async function main () {
  const client = await wdio.remote(opts);

  const field = await client.$("android.widget.Button");  // 手順3で調べたものに書き換える
  await field.click();									  // 手順4で調べたものに書き換える


  await client.deleteSession();
}

main();
5.結果
これで実行すると、ボタンが押されました。
できた。

実行方法がわからない場合は、前回前々回の記事を参考にしてください。

2020年6月5日金曜日

uiautomatorviewer.bat は jdk 8でないと動かない。

uiautomatorviewer.bat を起動したら、エラーがでて起動しませんでした。
-Djava.ext.dirs=..\lib\x86_64;.......  

ぐぐってみたところ、jdk8でないとだめということでした。
jdk14を入れていたので、こちらをアンインストールして、jdk8に入れなおしました。

直後だとまた別のエラーがでたので、再起動したら、uiautomatorviewerが起動しました。

よかった。

2020年6月3日水曜日

Appium 入門2 ( 実機で Hello World! )


emulator上でHelloworldを表示することに前回やってみました。
今回は実機でHello worldを表示する方法を試してみました。

環境
Windows 10
Android 5.1 (実機)
appium 1.15.1

インストール
インストールの手順は省略します。前回の記事を参考にしてください。

手順

1.Android端末側の準備

開発向けオプション : ON
USBデバッグ : ON
にしました。

2.ADBコマンドで確認

コマンドプロンプトで「adb devices」を実行して、実機がリストされることを確認しました。
adbコマンドが使えない場合は、android sdkのフォルダのplatform-toolsを環境変数のパスに登録するか、そこに移動して、adbコマンドを実行する必要があります。
 

3.スクリプトの準備

前回のスクリプトのplatformversionを実機のAndroidのバージョンに変更
deviceNameを手順2のadb devicesの結果に変更しました。
※devicenameは変更しなくても動いたので、不要なのかもしれません。
// javascript

const wdio = require("webdriverio");
const assert = require("assert");

const opts = {
  path: '/wd/hub',
  port: 4723,
  capabilities: {
    platformName: "Android",
    platformVersion: "5.1",     // ここ
    deviceName: "002628105537", // ここ
    app: "C:/Users/xxxxxxx/Desktop/appium_test/ApiDemos-debug.apk",  
    appPackage: "io.appium.android.apis",
    appActivity: ".view.TextFields",
    automationName: "UiAutomator2"
  }
};

async function main () {
  const client = await wdio.remote(opts);

  const field = await client.$("android.widget.EditText");
  await field.setValue("Hello World!");
  const value = await field.getText();
  assert.equal(value,"Hello World!");

  await client.deleteSession();
}

main();

4.Appiumを起動

スタートメニュー → Appiumをクリック
特に設定等変更せずに、「サーバーVXXXXXを起動する」 ボタンを押す。

5.実行

コマンドプロンプトを開く
index.jsがおいてあるパスにcdコマンドで移動する。
「node index.js」コマンドを実行

6.結果

実機側でHello World!が表示されたら、成功。 できた。

2020年5月26日火曜日

Appium 入門1 ( emulatorで Hello World! ) 

はじめに
Appiumをwindows環境で構築してみようと思いました。androidの自動化が目的です。
参考にした情報はこちらです。

公式サイト

http://appium.io/docs/en/about-appium/getting-started/?lang=en

書籍

※こちらはたまに眺める程度で使用。
環境
環境はこちらです。
Windows 10
Android 8 (emulator)
appium 1.15.1

インストール

1.appiumのインストール

インストール方法が2通りあって、npmを使う方法と、デスクトップアプリで入れる方法。
npmがわからないので、デスクトップアプリを入れることにしました。
上記サイトのrelease pageからダウンロードをしました。



いろいろなバージョンがあるのですがlatest release を書かれたものをダウンロードしました。

2.ドライバーのインストール

自動化したい端末に合わせてドライバというものをインストールする必要がるようです。
手元にあるAndroidでためしたかったので、Androidのドライバをインストールすることにしました。Androidのドライバには2種類あって、EspressoとUIAutomatetor2があります。
flagshipはUIAutomator2の方ということなので、こっちを入れることにしました。

http://appium.io/docs/en/about-appium/getting-started/?lang=en


ドライバを入れるために下記のモジュールをインストールする必要があるため、入れました。こちらのページの「Basic Setup」の記事に従って入れていきました。

(準備1)Node.js (npm)

こちらからダウンロードしてインストールしました。
https://nodejs.org/en/download/
※npmはNode.jsをインストールすると入ります。
※npmがよくわからないからappiumのデスクトップ版をいれたのですが、ドライバを入れるために結局インストールすることになりました。

(準備2)JDK

JDKはこちらのサイトからダウンロードしました。
https://www.oracle.com/java/technologies/javase-downloads.html
※最新ものを入れました。 最新でよいのかはちょっと不安です。
※この記事でやっていることだけであれば、最新版でもよいのですが、動かないtoolとかもあるのでJDK8のほうが良いかもしれません。
JDK8はoracleのサイトでアカウントを登録しないとダウンロードできないので、ちょっと手間でした。


(準備3)環境変数JAVA_HOMEの設定

システム環境変数にJAVA_HOMEを設定し、JDKのパスを設定する。
「C:\Program Files\Java\jdk1.8.0_251」を設定しました。 

※システムのプロパティ - 詳細設定 - 環境変数
環境変数のダイアログのシステム環境変数(下の方)の新規ボタンを押す。
変数名 : JAVA_HOME
変数値 : C:\Program Files\Java\jdk1.8.0_251 
を設定して、OKで閉じる。 変数値のほうは環境に応じて変更してください。

(準備4)環境変数PATHの設定

システム環境変数のPathに、JDKのbinパスを追加する。
%JAVA_HOME%\bin 

※システムのプロパティ - 詳細設定 - 環境変数
環境変数のダイアログのシステム環境変数で、Pathを選択して、編集ボタンを押し、
環境変数名の編集ダイアログで新規ボタンを押す。
%JAVA_HOME%\bin を追加する。

(準備5)Android Studio のインストール

もともといれてあったので、新しくインストールはしませんでした。
入っていたバージョンは3.6.3です。

(準備6)Android SDK toolsのインストール

Android Studio - Tools - SDK Manager - SDK toolsシート
Hide Obsolete Packagesのチェックを外す
Android SDK Tools が表示されるので、チェックしてapplyボタンを押下

(準備7)環境変数ANDROID_HOMEの設定

システム環境変数にANDROID_HOMEを設定し、android SDKのパスを設定する。
sdkのパスはAndroid Studioを起動して、Toolsメニュー - SDKマネージャー を開いて、 Android SDK Locationから確認しました。

※ついでにシステム環境変数のPathに%ANDROID_HOME%\platform-toolsを追加しておくと、のちのち便利です。

(ドライバインストール)

下記のコマンドをコマンドプロンプトで実行
npm install appium-uiautomator2-driver

(appium-doctorでチェック)

コマンドプロンプトで「npm install -g appium-doctor」を実行し、appium-doctorをインストール
「appium-doctor --android」を実行し、状況をチェック
ここまでで必須のものがOKとなっていることを確認できました。
デモアプリでHello World!

1.デモアプリの準備

公式サイトからダウンロードする。
「・・・this test Apk・・・」というリンクがあるので、そこからダウンロードしました。
これをwindows上にダウンロードします。
※よくわからず、これをemulatorでダウンロードしたりして、四苦八苦しました。

2.emulatorの起動

Android Studio - Tools - AVD Manager から emulatorを作成して、起動しておく。
サンプルに合わせてandroid 8のemulatorを作成しました。

3.サンプルコードの準備

windows上の任意の場所にindex.jsファイルを作成します。
テキストに公式サイトのサンプルをそのまま貼り付けます。
app: のところを、デモアプリをダウンロードしたパスに書き換えます。
// javascript

const wdio = require("webdriverio");
const assert = require("assert");

const opts = {
  path: '/wd/hub',
  port: 4723,
  capabilities: {
    platformName: "Android",
    platformVersion: "8",
    deviceName: "Android Emulator",
    app: "C:/XXXXXXXXXXXX/ApiDemos-debug.apk", // ←ここだけ書き換える。 
    appPackage: "io.appium.android.apis",
    appActivity: ".view.TextFields",
    automationName: "UiAutomator2"
  }
};

async function main () {
  const client = await wdio.remote(opts);

  const field = await client.$("android.widget.EditText");
  await field.setValue("Hello World!");
  const value = await field.getText();
  assert.equal(value,"Hello World!");

  await client.deleteSession();
}

main();
※内容はまだ理解していないです。

4.appium起動

スタートメニュー → Appiumをクリック
特に設定等変更せずに、「サーバーVXXXXXを起動する」 ボタンを押す。

5.実行

コマンドプロンプトを開く
index.jsがおいてあるパスにcdコマンドで移動する。
「npm init -y」コマンドを実行
「npm install webdriverio」コマンドを実行
「node index.js」コマンドを実行

※npm XXXは一回やればいいのかなぁ

6.結果

emulator側でHello World!が表示されたら、成功。
できた。

2020年4月27日月曜日

10周年

このブログをはじめて10年経ちました。

もう10年がんばるぞ。

2020年4月23日木曜日

GetFontStream() 解放する?しない?

こうした
using (System.IO.Stream gfs = m_typeface.GetFontStream())   
{
   XXXX
}
これでよいという確実な根拠はないのですが、使ったものは解放したほうが良いのだろうと思い、usingを使ってます。

気になった点は、自分でnewしていないので、usingで解放されるのか?

逆に勝手に解放してよいのか?という点も気になりました。


(気になった点①)Usingで解放されるか逆コンパイルして確認する
下記のサイトを参考に確認しました。

初期化なしのusing文ってOK?

初めて逆コンパイルしたので、自分へのメモも兼ねてやり方も記載してます。

(1)スタートメニュー → Visual Studio 2019
→ Developer Command Prompt for VS 2019 を起動する。


(2)ildasmと入力してEnter


(3)IL DASMというツールが起動するので、ここに作成したexeをD&Dする。
(4)確認したいメソッドをダブルクリックすると、コードを確認できる。

(5)Disposeが呼ばれているので、おそらく解放してくれるのではないかと思う。


(気になった点②)自分でnewしたオブジェクトではないけど解放して大丈夫か?
解放した後に、getしてみたけど、例外が飛んだりしないので大丈夫なのかな?
            System.IO.Stream gfs = m_typeface.GetFontStream();
            gfs.Close();
            gfs.Dispose();
            gfs = m_typeface.GetFontStream();

2020年4月15日水曜日

GlyphTypeface クラスのGetFontStream()メソッド

これまで、fontファイルにアクセスする際は、fontファイルを直接Openして、アクセスしてました。こんな感じです。
string sfontfile = フォントファイルのパス
FileStream fs = new FileStream(sfontfile, FileMode.Open, FileAccess.Read);


先日、MSDNで、GlyphTypeFaceクラスを見てたら、GetFontStream()というメソッドがあることに気づきました。


引用元 : https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.glyphtypeface?view=netframework-4.8


これ使えそうだなと思い、使ってみました。こんな感じです。
System.IO.Stream gfs = mytypeface.GetFontStream();
MemoryStream ms = new MemoryStream();
gfs.CopyTo(ms);
memorystreamにコピーしているのは、使い方がこれしか分からなかったためとなります。
元々Filestreamを使ってたので、Filestreamにキャストしたりしようとしてたのですが、うまくいかず、 とりあえずこの方法だと例外やエラーが発生しないので、こうしてます。


GlyphTypefaceをnewするときにフォントファイルのパスは必要なので、卵が先かにわとりが先かという感じもしますが、 一度GlyphTypefaceを作ってしまえばそこからフォントファイルにアクセスできるのは、利便性があると感じました。