20210428のAndroidに関する記事は9件です。

[Android]Retrofitって、なんか名前かっこいいから、なんか分からないけど使ってみたい(ゆるく解説)

Retrofitって何? どうもReoです。 Retrofitとは、API通信ができるライブラリです。 API通信とは、自分のスマホからサーバと通信して適当な情報を取ってくることです。 図式化するとこんな感じ。。 ※実際にはお金は貰えないので注意 ライブラリは、既に先人によって作られた便利な部品みたいなもの。 つまり、Retrofitという部品をお借りしてAPI通信をします。 そして、例としてこちらのNewsAPIを使い、Top business headlines in the US right now(サイト内参照)を取得する処理を書きたいと思います。 サイト https://newsapi.org/ 取得する情報 https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey="取得したApiKey" 使い方 導入 まずは、こちらのリンク(https://newsapi.org/) に入り、Get Startというボタンを押して、メールアドレス等入力するとAPIキーを取得できると思います。そちらが、今回のAPI通信で必要になるものです。 お次は、AndroidStudioでRetrofitを使えるようにします。 まず、build.gradle(:app)を開きます。 そして、dependencies{}内に以下を追加。 def retrofit2_version = "2.5.0" implementation "com.squareup.retrofit2:retrofit:$retrofit2_version" implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2_version" implementation "com.squareup.retrofit2:converter-gson:2.9.0" // ついでにこいつも def moshi_version = "1.5.0" implementation "com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" implementation "com.squareup.retrofit2:converter-moshi:2.9.0" // 非同期処理をするために必要(使うときに解説) implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' ついでに追加したものは、Moshiといって、JSONをJavaオブジェクトに変換してくれる便利な奴です。 今回API通信で受け取る情報はJSONファイルという形式で受け取るのですが、それをこう、、、使えるようにするには変換しないといけないのです。 (筆者も良く分かってないw) その為に、Moshiを使います!!! 追加した後は、Sync nowしてくださいね! ①ちょーだい(する情報) サーバに情報をちょーだいするための指示を作る必要があります。 まず、その前に今回ちょーだいする情報はこちらです。 { "status": "ok", "totalResults": 70, -"articles": [ -{ -"source": { "id": null, "name": "CNBC" }, "author": "Elliot Smith", "title": "Deutsche Bank reports its best quarterly profit since the first quarter of 2014 - CNBC", "description": "Deutsche Bank on Wednesday reported a 908 million euro ($1.1 billion) profit for the first quarter, buoyed by continued strong performance in its investment banking division.", "url": "https://www.cnbc.com/2021/04/28/deutsche-bank-earnings-q1-2021.html", "urlToImage": "https://image.cnbcfm.com/api/v1/image/105343415-1532101496379gettyimages-975924044.jpeg?v=1553790495", "publishedAt": "2021-04-28T05:16:43Z", "content": "LONDON Deutsche Bank on Wednesday reported a 908 million euro ($1.1 billion) profit for the first quarter, buoyed by continued strong performance in its investment banking division.\r\nThe bank vastly … [+2839 chars]" }, 以下省略… 先程も触れました、JSON形式と呼ばれる情報の書き方で書かれているので、???とされたかもしれませんが、怖がらなくていいですよ! 書き方としては、変数名 : "値",となっています。 []は、リストのことです。 articleというリスト型の変数名の中に、idは、nullという値が。 また、nameという変数には、"CNBC" という文字列が。 飛ばして、urlToImage(ネット上にある画像リンク)という変数には、"http://..(省略)" という文字列が。 publishedAtは、そのニュース記事が投稿された日。 contentは、ニュース記事内容。 このように、考えると何となく情報がニュース情報であり、形式も読み解けるはずです。 はずです… ①ちょーだい(する指示) interface ApiService { @GET("/v2/top-headlines") suspend fun getNews( @Query("apiKey") apiKey: String, @Query("country") country: String ): Response<ResponseData> } 新しいファイルを作成し、interfaceにて上記のように書きます。 @GETは、エンドポイントを指定します。 エンドポイントというのは、取得する情報の所在位置です。 最初の図式化に付け加えるとこんな感じ。 先程のイラストにてお金が、どこにあるのか?を示すのがエンドポイント、という風に捉えてください。 ※飽くまでも比喩なので悪しからずw、サーバーの中に金庫的な概念はあるかどうかは分かりませんw 今回は、/v2/top-headlinesというパス(ファイルの場所)に目当てのお金…、いや情報があるということです。 そして、お次、suspend fun getNewsについて fun getNewsはいいですね。メソッドを宣言しています。 そして、このメソッドなのですが非同期処理で呼び出さないといけません。 非同期について世界一分かりやすい記事 https://wa3.i-3-i.info/word1623.html そして、導入の最後で追加したCoroutines(こちらが非同期処理を行ってくれる便利な奴です。)に対応させるためにsuspendを接頭語に加えているわけです。 そして@Queryについて、こちらはクエリパラメータと言います。 クエリパラメータについての分かりやすい記事 https://webtan.impress.co.jp/e/2012/04/26/12663 最初に取得する情報として、下記のURLを掲載しました。これを分析してみましょう。 https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey="取得したApiKey" https:// こちらは、今回のAPI通信における、通信手段のことです。 つまり、https(httpとsslが融合した暗号化通信の事、分からなくても大丈夫)という通信手段を使うという意味です。 newsapi こちらは、ホスト名です。 この名前のコンピュータから情報を取得しようとしています。 .org こちらは、ドメイン名です。 newsapiというコンピュータが、ネット上のどこにあるのかを人に分かりやすいようにしたものです。 /v2/top-headlines こちらは、パスです。 v2というフォルダの中に、top-headlinesというファイルが入っていることを示しています。 ?country=us&category=business&apiKey="取得したApiKey" それ以降がお待たせ登場、クエリパラメータ! 前述したJSONと同じように、?変数名=値という形式です。 &は、複数のクエリパラメータを続けて書く時に用います。 つまり、country、category、apiKeyの三つあります。 こちらは、検索条件の役割を果たしています。 呼び出す時には、@apiKeyには、自分で取得したapiKeyをセットし、@countryには、jpをセットします。(これで、日本のニュースが取得する操作が出来るはずです。) そして、Response(型はResponseData)というリストで返します。 ※ResponseDataは、後述。 ①ちょーだい(を保持する場所) data class ResponseData( val url: String, val urlToImage: String, val publishedAt: String, val content: String ) こちらは、サーバから頂いた情報を保持するデータクラスと呼ばれるものです。 図式化するとこんな感じ。 ①ちょーだい(してみる) private var response: Response<ResponseData>? = null (省略) val newsService = Retrofit.Builder() .baseUrl("https://newsapi.org/") .addConverterFactory(MoshiConverterFactory.create()) .build() .create(ApiService::class.java) GlobalScope.launch(Dispatchers.IO) { response = newsService.getNews("475a6716e0b1401ea0191041db0e6fff", "jp") if (response!!.isSuccessful) { val article = response!!.body() Log.d("debug", article.toString()) } 本当は、ViewでAPI叩くの良くないのです。 MVVM化した記事を書きたい反面、まだ理解が浅いので今回だけ許して~! とはいえ、今回の趣旨はRetrofitを使うことなので! Retrofitを用いて、API叩く処理は次のように書きます。 baseUrlには、スキームからドメイン名をセット。 addConverterFactoryには、最初に説明したMoshiをセット。 crateには、①ちょーだい(する指示)にて作成したApiServiceをセット。 また、GloalScope.launch(Dispathers.IO) {}ですが、この中で非同期処理を書くことが出来ます。 getNewsのパラメータには、取得したApiKeyとjpをセット。 そして、本来はエラーハンドリングすべきなのですが省略。。。。 取得した情報を受けている、response。 if文にて、response.isSuccessfulで、データ取得に成功したら、response.!!body() = articleで中身を確認できます。 Logcatにて、 こんな感じのデータが、表示していれば無事終了です! 終わりに 取り敢えず、1週間前は自力でAPI叩くの無謀な取り組みだと思っていましたが、何とか実装し記事を書けるまで完了できました。ただ、ViewでAPI叩いたり、!!を用いているたりとコード自体は決してきれいなものではありません。それが、これから学びたいことだなと思いました。 プリンシパルプログラミングでも読むか~!(終)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでスマホゲーム自動化~FGO自動化編~

概要 pythonを用いた画像処理を用いてスマホゲームを自動化していきます. PCを初期化したので,再度設定してきます. 前回の記事 https://qiita.com/m_tani_july/items/6691bc590693c3cf65cb 解説動画 後日,動作確認の動画をアップロードしたいと思います. 動画をアップしました. コードの紹介 スマホから画像を取得してから,テンプレートマッチングしてその結果を可視化したプログラムです. def _click_image3_vis(temp_path): ############################################################### # 画像取得 # # テンプレート画像の読み込み temp = cv2.imread(temp_path) # # スマホのキャプチャ画像 img = capture_screen_2(device_id) # # デバッグ用に一応保存 cv2.imwrite('_screen.png', img) ############################################################### # デバッグ用 # # img = cv2.imread('_screen.png') # temp_path = r'img\fgo\arts.png' # temp = cv2.imread(temp_path) # cv2.imwrite('_screen.png', img) result = cv2.matchTemplate(img, temp, cv2.TM_CCOEFF_NORMED) # 最も類似度が高い位置を取得する。 minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result) # print(f"max value: {maxVal}, position: {maxLoc}, temp_path: {temp_path}") # print("max value:{:6.3f}, position:{:<10}, temp_path:{}".format(maxVal, maxLoc, temp_path)) print("max value:{:6.3f}, position:{:>16}, temp_path:{}".format(maxVal, "{}".format(maxLoc), temp_path)) ############################################################### # 確認用描画セクション # # 読み込んだ画像の高さと幅を取得 kenel_size = 201 height = img.shape[0] width = img.shape[1] # # ============================================================= # show resized base image resized_img = cv2.resize(img,(int(width/2), int(height/2))) cv2.imshow("image", resized_img) cv2.moveWindow("image", 0, 0) # # ============================================================= # show heat map & roi # # create heat map # # 0.5 以下は無視 result[result<0.5] = 0 # 3 channel heat_map heat_map = np.zeros((img.shape[0], img.shape[1], 3)) # result to heatmap heat_map[0:-temp.shape[0]+1, 0:-temp.shape[1]+1, 2] = result # heatmap GaussianBlur heat_map_blur = cv2.GaussianBlur(heat_map,(kenel_size, kenel_size),0) result2 = heat_map[:, :, 2] # # create gray image resized_img_gray = resized_img.copy() img_gray = cv2.cvtColor(resized_img, cv2.COLOR_BGR2GRAY) # RGB2〜 でなく BGR2〜 を指定 for i in range(3): resized_img_gray[:, :, i] = img_gray # temp image show cv2.imshow("temp", temp) cv2.moveWindow("temp", int(resized_img.shape[1]), 0) # resized heat_map resized_heat_map = cv2.resize(heat_map_blur,(int(width/2), int(height/2))) * 255 # resized heat_map xheat = (resized_heat_map *255 * 0.6 + resized_img_gray * 0.4)/255 # detect peak coordinates = peak_local_max(resized_heat_map[:, :, 2], min_distance=2) # draw rectangle color_ = np.array([102, 51, 255])/255 # RGB -> GBR for p in coordinates: cv2.rectangle(xheat, (p[1]+int(temp.shape[1]/2), p[0]+int(temp.shape[0]/2)), (p[1], p[0]), color_, thickness=1) # show heat map cv2.imshow("heat", xheat) cv2.moveWindow("heat", 0, int(resized_img.shape[0] + 50)) k = cv2.waitKey(1) おわりに 今回はadbコマンドと画像処理を組み合わせたコードを紹介しました. これでオート周回するプログラムができます. 今後は別ゲームへの対応や戦略アルゴリズムの開発をやっていく予定です. 次回 「pythonでスマホゲーム自動化~ファイナルファンタジー ブレイブエクスヴィアス(FFBE)自動化編~」 https://qiita.com/m_tani_july/items/df81ea3a10a70581bb77
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ViewからActivityを取得するメモ

package com.objectfanatics.commons.android.view import android.app.Activity import android.content.ContextWrapper import android.view.View val View.activity: Activity get() = activityOrNull!! val View.activityOrNull: Activity? get() = when (val context = context) { is Activity -> context is ContextWrapper -> context.baseContext as Activity else -> null }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

URL除外文字によるAndroidアプリへの悪性リンク挿入の話

はじめに 本記事ではAndroidアプリについて知識がない状態で、脆弱性を探してやろうと挑んだ末に見つけた危険な挙動をまとめる。 脆弱性と言えないこともなく、おそらくBugBountyProgramなどを行っているアプリにも多く混入している実装であるため、報告は各自に任せる(が私は焼き肉が食べたい)。 各開発元への確認を行った後に公開しています。記事の内容を験する場合、法律により処罰される可能性があります。 不思議な挙動の発見 国際化ドメイン名によるスパムチェック回避の論文を読み、URL中の特殊文字の挙動について調査しようとDiscordアプリ(Androidクライアント)の自分専用チャンネルに特殊文字を含むリンクを投げていた。 すると自動リンク部分に以下のような挙動が見られた。 ポップアップ中の|でリンクが切れているようだ。 前半をタップするとGoogleへ、後半をタップするとEvilへ飛ばされる。 URLに使用できる文字はRFC2396とRFC3986に定義されているらしく、|や`はその中で除外されており使用不可能でエスケープされなければいけないとある。 これだけを見ると正常な挙動に思える。 しかし、ポップアップ外では一本のリンクになっている。 念のためPCクライアントでも確認しようと開くと、以下のようであった。 こちらもエンコードされて一本のリンクになっている。 こうして同じ投稿でも場所や環境によってリンクの解釈が異なるといった不思議な挙動を引き当てた。 以下のようなリンクで同様の現象が再現した。 https://www.google.com/search?q=satoki00|evil.com https://www.google.com/search?q=satoki00`evil.com https://www.google.com/search?q=satoki000evil.com https://www.google.com/search?q=satoki00愛evil.com URL自動リンク処理 アプリのURL自動リンク処理がどのように行われているか調査すると、Linkifyが利用されるようだ。 Linkify https://developer.android.com/reference/android/text/util/Linkify Linkifyを用いることでtext中の指定した部分をリンク化することができる。 指定方法によってはURLすべてをリンクにすることや、パターンマッチすることもできる。 試しにアプリを作成してみたところ、URLすべてをリンクする設定にすると|で分割が発生した。 このことからもわかる通り、他の環境では発生しない挙動だといえる。 危険性 環境によってリンクが分割されることで、意図せずフィッシングサイトなどに誘導される危険性がある。 以下のようにURLクエリパラメータを長くした場合、その部分をタップする確率が上がる。 https://www.google.com/search?q=satoki00%7Cevil.com?a=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Discordの場合はリンクの色が変化するため、注意深く確認すればリンクが分割されていることがわかるが、後述するアプリの中にはリンクの色が変化しないものもある。 この場合URLの分割をユーザが見分けることは難しい。 また、ユーザが該当するURLを他の端末で過去に開いたことがある場合、そのアドレスは信用できると認識してしまう。 ユーザから見るとPCなどでは正規リンクに、Androidでは悪性リンクとなる。 実際のアプリたち Discord特有の問題であるか確認したところ、以下に挙げるアプリで同様の現象を確認できた。 Instagram リンクの色が変化しないため、ユーザが見分けることが出来なくなっている。 アプリ版 PC版 LINE 文字色で分割が判断できるが、多数のリンクとテキストが混在する場合は注意が必要である。 Note機能中にも同様の現象が確認できた。 アプリ版 PC版 Messenger 分割後のURLにクエリパラメータを含めるとテキストと認識された。 悪性サイトにリンクされる部分が少ないため、危険性は低い。 アプリ版 PC版 おわりに アプリの仕様に依存するが、ユーザの入力を信用しそのまま自動リンク処理に渡すことは避けたほうがよさそうだ。 環境によって異なるリンクとなるのは望ましくなく、ユーザが悪性リンクをAndroid以外の環境で開き、信用してしまう原因となる。 また、パターンマッチ、パーセントエンコーディング、置換でのある程度の対策が可能となる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidアプリへのAdMob導入方法(Kotlin)

はじめに Android アプリでの AdMob のバナー表示方法に関してです。iOS よりも簡単でした AdMob 導入するために下記アプリをリリースしてみました。 スペイン語の単語をフラッシュカードみたいに表示できるアプリです ソース(GitHub) 導入方法 導入方法はほぼ下記を見れば OK です。 AdMobスタートガイド 導入手順は下記です。 AdMob アカウントを作成する アプリを登録する プロジェクトに SDK を導入する AndroidManifest.xml に AdMob アプリ ID を追加する 広告表示を実装する AdMobアカウントの作成 下記からとりあえずアカウントを作成します。 AdMob に申し込む 「お支払い」からお支払い情報を追加します(2、3日で承認されました)。 アプリを登録 AdMob のサイトで広告を表示するアプリを登録します。 左上のメニュー -> アプリ -> アプリを追加 必要な情報を入力してアプリを登録します。 登録したアプリを選択して左のメニューの「広告ユニット」から広告ユニットを作成します。 これはたぶん表示する広告の数だけ作成します。とりあえず「バナー」を追加しておきます。 SDK導入 build.gradle(Module)に下記を追記して SDK を導入します。 dependencies { : : : implementation 'com.google.android.gms:play-services-ads:20.0.0' } AndroidManifest.xmlにアプリID追加 AndroidManifest.xml に下記のように AdMob アプリ ID を追加します。 <manifest> <application> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/> : : : </application> </manifest> value は AdMob のサイトで登録したアプリ情報から下記のように確認できます。 アプリ -> 左メニューの「アプリ設定」 -> アプリ ID 広告表示には通信も必要なので下記も追加しておきます(とくに記載はありませんでしたが必要なはず)。 <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> 広告表示を実装 アプリ起動時にまず初期化しておく必要があるようで初期表示画面などに下記のように追記します。 package ... import ... import com.google.android.gms.ads.MobileAds; class MainActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) MobileAds.initialize(this) {} } ... } バナー バナー表示の手順は下記です。 layout.xml に AdView を置く <com.google.android.gms.ads.AdView xmlns:ads="http://schemas.android.com/apk/res-auto" android:id="@+id/adView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" ads:adSize="BANNER" ads:adUnitId="ca-app-pub-3940256099942544/6300978111" /> 下記のように実装する val adView = findViewById<AdView>(R.id.adView) val adRequest = AdRequest.Builder().build() adView.loadAd(adRequest) layout.xml で追加する場合 Palette の Google に AdView が用意されています これでバナーが表示されます バナーのサイズと ID は下記のようにコードでも設定できるようですが2つは同じ方法で実装する必要があるようです(片方を XML もう片方をコードで設定はできない)。 val adView = findViewById<AdView>(R.id.adView) adView.adSize = AdSize.BANNER adView.adUnitId = "ca-app-pub-3940256099942544/6300978111" 開発中は iOS 同様テスト用 ID(上記の ID はテスト用のやつです)を使用する必要があるようです。シミュレーターで起動すると勝手にテスト広告が表示されるようでした アプリの開発とテストでは必ずテスト広告を使用し、配信中の実際の広告は使用しないでください。実際の広告でテストすると、アカウントが停止される場合があります。 バナー広告のドキュメント おわりに とりあえず広告表示できるようになりました 実際にアプリで広告が表示されるようになるのは AdMob のサイトから「アプリ情報」の「アプリストア」の情報を追加してはじめて表示されるようになります。私の場合はリリースしてから1日ほどかかりました(さすが Google iOS の方は10日ほどかかりました。。。)。 iOS アプリに導入するよりはお手軽でした
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクリーンリーダーを使って、Android, iOSアプリを作れるだろう環境を作ってみた

背景 or 拝啓 この記事をご覧になっていらっしゃる方は「アクセシビリティ」という単語をご存じの方が多いのだろうと思う。しかしながら、まだまだこの単語とその重要性についてご存じないエンジニアの方も少なくないと聞く。私を含めたアクセシビリティ機能を必要としている者にとっては、もう少し認知度が上がってくれればなと切に願っている。せめて「バリアフリー」という単語程度には認知度が上がってくれれば。 さて、質の高いアクセシビリティ機能を実装するためには、その機能を必要としているユーザーからのフィードバックはとても重要である。英語圏で開発されたアプリケーションを日本語化する際、日本語が分かる人がレビューしたり提案したほうが質の高い日本語化ができるのに似ている。翻訳だけでは気が付かない日本語特有の使いやすさに気が付きやすいのは、やはり日本語を母国語としているか日本語がわかる人なのだと思う。 ということは、アクセシビリティ機能を必要としている人(以下、当事者と書く)からのフィードバックの質が上がれば、アクセシビリティ機能も向上していくだろう。 さて、ここからが問題である。フィードバックにはおそらくいくつかの段階がある。 なんとなく使いにくいな、という抽象的フィードバック 具体的に使いにくいところを示したフィードバック 具体的にどうなって欲しいかを示したフィードバック ソースコードがあるならば、それに対するパッチやプルリクエスト 3つ目と4つ目はアプリケーションやOS内部知識やプログラミング技術が必要なので、難易度は高い。が、もし当事者がこのような具体的フィードバックを送ることができるならば、アクセシビリティ機能はかなり向上する。そして、プロプライエタリなソフトウェアでこんな具体的フィードバックを送っている人がいらっしゃれば、その当事者は開発している会社で具体的な貢献をしているということになるだろう。 こんな状況を実現するためには、当事者はプログラミングができる必要がある。大規模なアプリケーションを一人で作成することはそもそも不可能だ。それに加えて、画面デザインなどは完全に視力がないならばなおさら難しいだろう。が、開発できる分野はまだまだある。そうやって、スマートフォンのアプリはどうやって作るのか、アクセシビリティ機能はどうやって実装するのか、どんな実装をするとアクセシビリティ的にまずいものができるのかを、具体的に知っていれば、より質の高いフィードバックを送ることができるだろう。 ということで、実際にAndroid/iPhoneのアプリを作っている方が複数人いらっしゃるので、私もその後追いということで、どこまでできるのかを確認してみようと思う。 これを読んで、自分もプログラミングしてみようかなと思われた当事者の方がいらっしゃれば嬉しい。また、アクセシビリティ機能に興味を持っていただいたエンジニアの方がいらっしゃれば嬉しい。 環境など 試している環境を書いておく。要するに、MacとWindowsのコンピュータがある、そんな環境だ。 コンピュータ: iMac (Retina 5K, 27-inch, 2020) OS: macOS Big Sur 11.3 Screen Reader: VoiceOver XCode: 12.4 Android toolchain - develop for Android devices (Android SDK version 30.0.3) Android Studio (version 4.1) Flutter (Channel stable, 2.0.3, on macOS 11.3 GNU Emacs 27.2 Homebrew 3.1.3 VMWare Fusion 12.1.1 Windows 10 バージョン Dev (OS ビルド 21364.1000) Screen Reader: JAWS 2020 JPN, NVDA 2020.4 Visual Studio Code バージョン: 1.55.2 (system setup) Git for Windowsに付属しているGit Bash Android SDKとFlutterはWindows側にもインストール 基本的に現段階の最新バージョンである。このバージョンでなければ動かないということではないし、動かなければ動くように修正されるべきである。 戦略 先人たちの知恵を最大限お借りしよう。どうやらWindowsでは、Visual Studio Codeを使って開発を行うのが、スクリーンリーダー使用時には良さそうである。また、コマンドライン系のOSの方が使いやすいだろうと私は信じているので、UNIX系のOSを使う、ということでmacOSである。これについてはWSLでも良いかもしれないが、iOS用のアプリも作るんだからmacOSを選択しておいたほうが無難なのではないか。 開発用のプラットフォームもたくさんあるんだけど、Flutterを使ってAndroidとiOSのアプリを両方リリースしている方がいらっしゃるので、これはもう従うのが早道だろう。ただ、私はVisual Studio Codeも興味あるが、長いことEmacsを使ってきた。ということで、Flutterの開発環境をEmacsにも設定しておく。 これで基本的なことはできるはずだ。では勧めてみよう。 macOS側のセットアップ 上記環境をせっせとセットアップすればよい。 XCode とりあえず、App StoreからXcodeをインストールする。特に悩まないと思う。 その他のツール 他はほとんどコマンドラインからインストールできるだろう。ターミナルを開いて、粛々とインストール。 $ xcode-select --install $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" $ brew install cocoapods emacs git android-sdk android-studio flutter たぶん上記な感じ。もう試せないい。 $ flutter doctor してみて問題がないかを確認してほしい。問題があったら修正。 Emacsの設定 ぶっちゃけ長いので他に譲りたい。lsp-modeを使えるようにして、これをベースにflutterとかdart-modeとかcompanyとかflycheckとかをセットアップする。正直好みでどうぞ。 ところで、Flutterで使われる言語はDartという言語である。Dartにもいろいろと親しみたい。Dart単一の勉強もしたい。そうすると、dart-mode単一で使ってもちゃんとlspが動いたほうがうれしい。 やってみると、Language Serverが見つからないとか言われてうまく行かない。探してみたらFflutterのインストールディレクトリ内にあった。 export PATH=/usr/local/Caskroom/flutter/2.0.5/flutter/bin/cache/dart-sdk/bin:$PATH みたいにしておくと、Dart言語で書かれたファイルを単一で開いてもちゃんと動くと思う。Flutterをアップデートしたら書き換えなければならないのが面倒だけど。うまいことシンボリックリンクとか使ってやればいいかな。 ひょっとすると、lsp-modeにしてLanguage Serverが動き出した瞬間にmacOSのセキュリティに引っかかってしまうかもしれない。適当に許可を与えれば動く。 Windows側の設定 スクリーンリーダーはインストールされているだろうか。まだならば、 NVDA日本語版 を確認してインストールする。JAWSは有償のソフトなのでこちらはオプションで。 Git for Windowsのセットアップ WindowsでもGitが使えると便利だからというのもあるが、これに付属しているMSYS2のサブセットがほしいのだ。これでMacがわにリモート接続できる。 Git for Windowsのページ を確認して、最新版をインストール。 Git Bashはインストールできただろうか。開いてみよう。ひょっとしたら、スクリーンリーダーによっては正しく読み上げが行われないかもしれない。 このページ とかを参考に、JAWSは設定する。NVDAはそのままでも動きそうだ。フォントをラスターにすると動くという情報もある。 Bashが起動したら、とりあえずMacに接続してみよう。<ホスト名>.localで接続できるはずだ。私はホスト名がsentaroなので、 $ ssh sentaro.local となる。macOS側でリモート接続を受けられるようにしておく必要がある。 Visual Studio Codeのインストール 参考サイトもたくさんあるので、それらを参考にインストールする。 ここらへん から希望のものをダウンロードしてインストールする。特別難しそうなところはないと思う。必要ならば日本語化をするのが良さそう。 Android開発環境のインストール 公式のページ にアクセスして、パッケージをダウンロードしてインストールする。 Flutterのインストール ここらへん を見て、インストールする。Git Bashを開いて、下記コマンドを実行するのもありなんだって。 ~$ cd /c/ c$ git clone https://github.com/flutter/flutter.git -b stable VSCodeの拡張機能をインストール 拡張機能のインストールに関しては参考サイトがたくさんあるので、そちらも参考にしてほしい。 簡単な手順はこんな感じ。 Ctrl+Shift+Xを押す。 拡張機能の検索ボックスにフォーカスが行くので、"flutte"と入力。 Tabキーを何度か押して検索された拡張機能のリストにフォーカスを置く。 上下矢印キーで、"flutter"を探す。 あったら、Tabキーを押して[インストール]ボタンを探して押す。 他にも入れたいプラグインがあったらここでごそごそ入れる。 最後に 長かった、本当に長い。まだアプリは一つもできてない。これが準備だとはね。 でもね、これで開発ができるのです。ほしいと思っているアプリを開発する準備はできたのです。次はアプリを開発するための学習と、なんとしてもアプリを作ってやろうという根気と執念と怨念(なにを恨むんだ?)です。 そして、ここまで読んでくださったソフトウェア業界の人事の方、ひょっとしたら当事者をエンジニアとして採用すると、十分に開発に貢献してくれるかもしれないですよ。執念は必要ですが、開発チームに加わっていける環境はなんとか整います。まだまだいろいろと改善されるべきですし、社会的環境も整わなければならないでしょう。でも、あなたの会社がその一歩を踏み出すことで、社会的環境は改善されるのです。どうか検討してみてください。 お・ま・け マイコン関連も同じようなものだよお。とりあえず趣味でやるなら、Arduinoも使える。SPresenseはハードウェア持ってないけど、要するにC++コンパイラだから使えるだろう。 やってみたいことができるだけハードル低く試せる時代になるために、もう少し頑張らないとね。 では、この先、開発編に続くのである。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでスマホゲーム自動化~PCからandroidの操作を自動化編~

概要 pythonを用いた画像処理を用いてスマホゲームを自動化していきます. PCを初期化したので,再度設定してきます. 解説動画 下記動画で解説してます. SDK Platform-Toolsのインストール SDK Platform-Tools リリースノート https://developer.android.com/studio/releases/platform-tools?hl=ja PCからandroidの画面をタッチするには,adbコマンドが必要です. 上記のリンクからインストールしたSDK Platform-Toolsの中にadb.exeがあるので,それを用いてandroidの画面をタッチします. スマホの設定 USBデバッグをONにし,adbコマンドを許可する. adbコマンドの確認 主なコマンドとしてtouchscreen, swipe, screencap, pullがある. 上記のコマンド内容と実行方法を下記に記載する. コマンドを実行するために,まず最初,adb.exeがあるフォルダでコマンドプロンプトを開く. >cd C:\Users\XXXXX\Documents\platform-tools_r31.0.2-windows\platform-tools touchscreen touchscreenは画面をタッチするコマンドである. 下記コマンドでタッチコマンドを送信する. 最後に,タッチされたことを確認できたら,touchscreenコマンドの確認は終了. >adb shell input touchscreen tap 330 600 swipe swipeは画面をスワイプするコマンドである. 下記コマンドでスワイプコマンドを送信する. 最後に,スワイプされたことを確認できたら,swipeコマンドの確認は終了. adb shell input swipe 50 50 500 500 screencap screencapは画面をキャプチャするコマンドである. 下記コマンドで画面キャプチャコマンドを送信する. adb shell screencap -p /sdcard/screen.png pull pullは画面をandroidからPCにファイルを転送するコマンドである. 下記コマンドで転送コマンドを送信する. adb.exeと同じフォルダにscreen.pngがあれば完了 adb pull /sdcard/screen.png おわりに 今回はtouchscreen, swipe, screencap, pullの4つのコマンドを紹介しました. 感の良い人はわかると思いますがこれと画像処理を組み合わせればゲームを自動化できます. 今後はこれと画像処理を組み合わせたコードを書いていきます. ↓これです. 次回,「pythonでスマホゲーム自動化~FGO自動化編~」 https://qiita.com/m_tani_july/items/98bddff824cecbf8a395 参考サイト [Android] タッチの座標を確認する https://qiita.com/takeoverjp/items/69c89d300b50b8fe4367 androidの操作を自動化したときの覚書 https://qiita.com/techno-tanoC/items/b93723618a792c7096ee SDK Platform-Tools リリースノート https://developer.android.com/studio/releases/platform-tools?hl=ja adb 経由の screenshot の取り方 https://qiita.com/TNaruto/items/b2407f5668e15e42bedd
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android/Java】権限の取得方法から取得後の処理

今回は、 アプリ起動時にカメラ、ストレージ、位置情報権限を取得したい 途中で位置情報権限を許可しないに変えることを想定 ボタン押下時に位置情報権限がない場合は権限取得OSダイアログ表示 をするための方法をまとめる。 複数の権限を取得する方法 requestPermissionsを扱うが、パッと調べた限りだと3つある。 1.ActivityCompat#requestPermissions public static void requestPermissions (Activity activity, String[] permissions, int requestCode) そもそもActivityCompatクラスって何なの?と気にしたことがなかったのでそのついで公式ドキュメントをみると Helper for accessing features in Activity. とあった。このクラスのメソッドの大半は引数にActivityが必要だったので、必要になったら確認することにする。 2.Activity#requestPermissions public final void requestPermissions (String[] permissions, int requestCode) ActivityクラスはAPI1からなので、Androidの元祖みたいなものだと思うが、このrequestPermissionsは意外にもAPI23から。 3.Fragment#requestPermissions public final void requestPermissions (String[] permissions, int requestCode) ただこれはAPI23で追加されたもののAPI28で非推奨となっているので注意。この代わりに何を使えば良いかは後述。 以上の3つは使える環境に合わせてどれを使っても変わらない気もする(一語一句公式の説明を読んだわけではないが)。今回は実際に使っていた環境がFragmentクラスだったので(今や非推奨だが)、3のFragment#requestPermissionsを扱う 取得したい権限はManifestに記載する必要がある。AndroidManifest.xmlでapplicationタグの上に <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> ACCESS_MEDIA_LOCATIONはAPI29からの権限になるが、OSバージョン分岐せずにしていても特に大丈夫そう。この権限は Allows an application to access any geographic locations persisted in the user's shared collection. とあるが、確か画像から位置情報を取得するときにAPI29以上なら必要だった覚えが。。(残課題) Fragmentクラス内で private static final int REQUEST_PERMISSION = 1000; private static String[] permissions = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION, Manifest.permission.CAMERA }; /* * 中略 */ ActivityCompat.requestPermissions(this.getActivity(), permissions, REQUEST_PERMISSION); これでpermissions配列内の権限全てがチェックされ、不足している権限OSダイアログが表示される。 権限を許可・拒否した次の処理 一度でも実装したことがある人には周知のことだが、以下のように書くと権限取得結果関係なしに次の処理が実行されてしまう ActivityCompat.requestPermissions(this.getActivity(), permissions, REQUEST_PERMISSION); //次の処理 doNextTask(); もし権限を取得できたら次の処理を実行したい場合はonRequestPermissionsResultをオーバーライドする必要がある。 @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { if (requestCode == REQUEST_PERMISSION_LOCATION) { if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //許可された場合 doNextTask(); } } } 第一引数(requestCode)はrequestPermissionsの第三引数になるので、上のサンプルだとREQUEST_PERMISSION_LOCATIONと一致していたらOK。 第三引数(grantResults)の0番目はユーザーがOSダイアログで許可したのかどうかの結果が返ってくる。  許可:PERMISSION_GRANTED  拒否:PERMISSION_GRANTED  要素数0:ユーザーがキャンセルした onRequestPermissionsResultが呼ばれないことがあった 実際に実装していると、なぜかFragment内でrequestPermissionsを実行してもonRequestPermissionsResultが呼ばれずに時間を要したことがあった。ちなみにとある修正をする前までは普通に動いていたのだが、なぜか関係ないと思っている修正をした後だとNGになった。 呼ばれない原因・対策を調べ、こちらの記事を読んでみたものの該当する内容はなし。 結果としてはFragmentクラスでもonRequestPermissionsResultをオーバーライドしていたが、親ActivityクラスでもonRequestPermissionsResultをオーバーライドしていていたことが原因だった。 Fragment#requestPermissionsでも親Activity側のonRequestPermissionsResultが呼ばれる様子。なのでその時は全て親Activity側にまとめるように修正した。 クリックイベント時に権限あるかないかみて処理を分ける ボタン押下時に位置情報権限がある場合は任意の処理を、権限がない場合は権限取得OS表示するように実装 //細かい設定は省略 Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { //位置情報権限が既にある場合 } else { //ない場合は再度リエクエスト requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSION_LOCATION); } } }); これで位置情報権限は取得されるまで要求がループされます。 参考文献(本文中で記載していないもの) 環境 Android Studio4.1.2 Android10
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】権限の取得方法から取得後の処理

今回は、 アプリ起動時にカメラ、ストレージ、位置情報権限を取得したい 途中で位置情報権限を許可しないに変えることを想定 ボタン押下時に位置情報権限がない場合は権限取得OSダイアログ表示 をするための方法をまとめる。 複数の権限を取得する方法 requestPermissionsを扱うが、パッと調べた限りだと3つある。 1.ActivityCompat#requestPermissions public static void requestPermissions (Activity activity, String[] permissions, int requestCode) そもそもActivityCompatクラスって何なの?と気にしたことがなかったのでそのついで公式ドキュメントをみると Helper for accessing features in Activity. とあった。このクラスのメソッドの大半は引数にActivityが必要だったので、必要になったら確認することにする。 2.Activity#requestPermissions public final void requestPermissions (String[] permissions, int requestCode) ActivityクラスはAPI1からなので、Androidの元祖みたいなものだと思うが、このrequestPermissionsは意外にもAPI23から。 3.Fragment#requestPermissions public final void requestPermissions (String[] permissions, int requestCode) ただこれはAPI23で追加されたもののAPI28で非推奨となっているので注意。この代わりに何を使えば良いかは後述。 以上の3つは使える環境に合わせてどれを使っても変わらない気もする(一語一句公式の説明を読んだわけではないが)。今回は実際に使っていた環境がFragmentクラスだったので(今や非推奨だが)、3のFragment#requestPermissionsを扱う 取得したい権限はManifestに記載する必要がある。AndroidManifest.xmlでapplicationタグの上に <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> ACCESS_MEDIA_LOCATIONはAPI29からの権限になるが、OSバージョン分岐せずにしていても特に大丈夫そう。この権限は Allows an application to access any geographic locations persisted in the user's shared collection. とあるが、確か画像から位置情報を取得するときにAPI29以上なら必要だった覚えが。。(残課題) Fragmentクラス内で private static final int REQUEST_PERMISSION = 1000; private static String[] permissions = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION, Manifest.permission.CAMERA }; /* * 中略 */ ActivityCompat.requestPermissions(this.getActivity(), permissions, REQUEST_PERMISSION); これでpermissions配列内の権限全てがチェックされ、不足している権限OSダイアログが表示される。 権限を許可・拒否した次の処理 一度でも実装したことがある人には周知のことだが、以下のように書くと権限取得結果関係なしに次の処理が実行されてしまう ActivityCompat.requestPermissions(this.getActivity(), permissions, REQUEST_PERMISSION); //次の処理 doNextTask(); もし権限を取得できたら次の処理を実行したい場合はonRequestPermissionsResultをオーバーライドする必要がある。 @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { if (requestCode == REQUEST_PERMISSION_LOCATION) { if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //許可された場合 doNextTask(); } } } 第一引数(requestCode)はrequestPermissionsの第三引数になるので、上のサンプルだとREQUEST_PERMISSION_LOCATIONと一致していたらOK。 第三引数(grantResults)の0番目はユーザーがOSダイアログで許可したのかどうかの結果が返ってくる。  許可:PERMISSION_GRANTED  拒否:PERMISSION_GRANTED  要素数0:ユーザーがキャンセルした onRequestPermissionsResultが呼ばれないことがあった 実際に実装していると、なぜかFragment内でrequestPermissionsを実行してもonRequestPermissionsResultが呼ばれずに時間を要したことがあった。ちなみにとある修正をする前までは普通に動いていたのだが、なぜか関係ないと思っている修正をした後だとNGになった。 呼ばれない原因・対策を調べ、こちらの記事を読んでみたものの該当する内容はなし。 結果としてはFragmentクラスでもonRequestPermissionsResultをオーバーライドしていたが、親ActivityクラスでもonRequestPermissionsResultをオーバーライドしていていたことが原因だった。 Fragment#requestPermissionsでも親Activity側のonRequestPermissionsResultが呼ばれる様子。なのでその時は全て親Activity側にまとめるように修正した。 クリックイベント時に権限あるかないかみて処理を分ける ボタン押下時に位置情報権限がある場合は任意の処理を、権限がない場合は権限取得OS表示するように実装 //細かい設定は省略 Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { //位置情報権限が既にある場合 } else { //ない場合は再度リエクエスト requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSION_LOCATION); } } }); これで位置情報権限は取得されるまで要求がループされます。 参考文献(本文中で記載していないもの)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む