Android app の Owner 変更と Google Play Developer account の削除
をしたので手順のメモ
Owner 変更はだいたいここに書いてあるとおり。
チェックすべきことも書いてあるので一度目を通しておくと良さそうです。
Transfer apps to a different developer account - Play Console Help
1. 移行先の Google Play Developer account を登録する
これはまぁ普通に。
Registration fee を支払う必要がありますが、先の Help に書いてあるとおり、元の Account を削除する際に返金してもらうことができるようです。
何も言わなければ返金されないので、あとの手順で Support にメールする時ちゃんと「返金して」って書いておくと良いと思います。
2. 移行先の Transaction ID を取得する
これは console にあるものではなく、支払ったときに付与される ID です。
なので、Registration fee を支払ったときに来る Google Payments からのメールに記載されています。
日本語だと「Google の注文番号」ってやつです。
https://pay.google.com からも参照できます。この場合は「取引 ID」です。
3. App の移行申請
ここから申請します。全部移行する場合は全部選択します。
https://play.google.com/console/developers/app-transfer
App が多い場合など、最初から Support にメールして進めたほうが早いかもしれません。
2-3 営業日でメールが来ます。
4. Developer account の削除依頼
以下に書いてあるとおり、3 で来たメールに返信する形で削除を依頼します。
Manage your developer account information - Play Console Help
まぁいい感じに私の Account 削除してください的なこと書いておけばいいと思います。 (返金を求める場合はそれも)
その際、削除対象の Account の Transaction ID や Developer name を記載しておくとスムーズです。
Developer name は Google Play Console の設定 -> デベロッパーアカウント -> デベロッパーページ で参照できます。
その後、移行先の Account の情報とか聞かれたりして、答えていくと削除してくれます。
なんだかんだ Owner 変更と削除で 2 週間くらいかかりました。
Reports API で G Suite 監査ログの更新通知を受け取る
G Suite の Admin console には監査ログというものがあり、そこで色々見たり Alert を上げたりすることが出来るのですが、
G Suite Admin SDK の Reports API を使用すると、この監査ログを取得することが出来ます
また、Callback URL を登録することで更新通知を受け取ることも出来ます。
ということでこの記事では Meet の更新通知を受け取ってみます。
Meet は非対応ですが、主要なイベントだと Alert Center でメールに飛ばせるのでメールならそちらのほうが良いでしょう。
注意
監査ログはリアルタイムに通知されるわけではなく、利用できるようになるまでに一定の時間がかかります。また、この時間はサービスによって異なります。
Meet はほぼリアルタイムに更新されるようですが、私が見ていたところでは監査ログに表示されるまでに概ね 3~5 分程度かかっていました。
Data retention and lag times - G Suite Admin Help
また、更新通知の受け取りに関してですが、最初は届いていたのですが何度も試していたせいか途中から届かなくなってしまいました。(同期用の Message は届くが監査ログの更新は通知されない)
Stackoverflow でもいくつか同様の投稿が見受けられましたので、使用する際には注意したほうが良いかもしれません。
個人で契約している G Suite Basic なので、Business とか Enterprise だと違ったりするのかもしれません。
実装
実装したやつ置いときます
通知を受け取る Web API の用意
とりあえず通知が POST されるので、その先を予め用意しておく必要があります。
Flask だと以下のようになります。
@app.route('/events', methods=['POST']) def events(): headers = request.headers if __name__ == '__main__': app.run(debug=True)
まず、ここにはいくつかの Message が POST されますが、そのうちの一つに同期用の Sync Message があります。
これは登録後に POST される確認用の Message です。
この Message には登録解除用の ID などが含まれていますが、監査ログとしての情報はないため、ここでは無視します。
@app.route('/events', methods=['POST']) def events(): headers = request.headers if headers['X-Goog-Resource-State'] == 'sync': return {}
Header に含まれる情報はここに書いてあります。
あとは通知の Parse です。
POST される json には種別や ID などが含まれますが、おそらく大体の人が必要としている情報は events にあります。
配下は配列となっており、その中身には Event type, name と Event の parameter が含まれています。
Event の parameter についてはそれぞれの Reference に記載があります。
Meet の場合は以下
Hangouts Meet Audit Activity Events | Reports API | Google Developers
あとは欲しい情報を取得してなんかいい感じにするだけです。
@app.route('/events', methods=['POST']) def events(): headers = request.headers if headers['X-Goog-Resource-State'] == 'sync': return {} for event in request.json['events']: print(event['name'])
Deploy する
さきほど用意した Web API をどこか見えるところに Deploy します。
Callback URL に登録するためにはいくつか条件があります。
- https であること
- 自己署名などは不可です
- Search console で domain の所有権が確認できること
私の場合は Google App Engine に deploy しました。
Repository に app.yaml を commit しておいたので同じように試すときは gcloud app deploy で試すことが出来ます。
Domain を登録する
Google API Console にアクセスし、必要なら任意の Project を選択します (無いなら作る)。
Domain verification から Add domain を選択し
Callback URL の domain を入力します。
この時、所有権が確認できていない場合は Seach Console での domain 登録を求められますので、その通りにします。
私は meta tag で行ったので Repository に index.html.example として commit してあります。
Callback URL を登録する
Activities: watch | Reports API | Google Developers
あとは Callback URL を登録するだけです。
Python だったらこのへんを参考にします。
Python Quickstart | Reports API | Google Developers
def __get_google_service(): creds = None if os.path.exists(TOKEN_PICKLE): with open(TOKEN_PICKLE, 'rb') as token: creds = pickle.load(token) if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES ) creds = flow.run_local_server(port=0) with open(TOKEN_PICKLE, 'wb') as token: pickle.dump(creds, token) return build('admin', 'reports_v1', credentials=creds) def __register(): response = __get_google_service().activities().watch( userKey='all', applicationName='meet', body={ 'address': settings.CALLBACK_URL, 'type': 'web_hook', 'id': ID } ).execute()
applicationName は今回は Meet の Event を取得するため 'meet' にします。Calendar の場合は 'calendar' です。
userKey は特定の User に絞りたい場合は使用します。今回は全て見たいので 'all' とします。
Response で得られる Resource ID と ID は保持するなり覚えておくなりすると登録解除が出来ます。
登録状態を見る API は無いため、Resource ID と ID がどこかへ行ってしまうと期限が切れる (たぶん 6 時間) のを待つ以外にありません。
登録するとしばらくして Sync Message が届くはずです。
登録を解除する
Channels: stop | Reports API | Google Developers
登録の解除は以下のように行います。
__get_google_service().channels().stop( body={ 'id': id, 'resourceId': resource_id } ).execute()
が、どうやら Python の Client library だとこの stop API の URL が間違っているようなので Issue を参考に書き換えます。
channels._baseUrl = channels._baseUrl.replace('/admin/reports/v1', '')
なお Issue では AuthorizedSession を使う方法も提案されていて、そちらのほうが良さげです。
おわり
EMV Contactless 対応のクレジットカードから情報を読み取る
クレジットカードやデビットカードには EMV Contactless という非接触決済に対応しているものがあります。
- https://www.visa.co.jp/pay-with-visa/featured-technologies/contactless.html
- https://www.mastercard.co.jp/ja-jp/consumers/features-benefits/contactless.html
- https://www.jcb.jp/products/contactless/index.html
- https://www.americanexpress.com/jp/benefits/service-security/contactless-payments.html
EMV Contactless は NFC を使用しているため、手持ちの Android 端末でカードの情報を読み取る事ができます。
ということでここではカードからカード番号及び有効期限を読み取ってみます。
なお、触れると長くなるのであまり具体的な実装には触れません。Android での実装は下部に Repository の URL を貼っておくので興味があればそちらを見てください。
はじめに
情報を読み取るために、ISO/IEC 14443-4 に定義されている通信プロトコルを介して通信を行います。データへのアクセスには ISO/IEC 7816-4 で定義されている Application Protocol Data Unit (APDU) という通信フォーマットを使用します。
雑に Android での実装の話をすると、IsoDep を使って connect で操作を開始し、transceive でやり取りし、close で終了します。
APDU Commands
Command は以下のように構成されます。
| CLA | INS | P1 | P2 | Lc | Data | Le |
Lc には Data length、Le には Response length を入れます。Response length は Response をすべて取りたい場合は 0 にします。
例えばこのあと出てくる SELECT FILE command の場合
| 00 | A4 | 04 | 00 | Lc | Data | Le |
となります。
P1/P2 は Parameter です。P1 の 04 (00000100) はこの場合 Dedicated File (DF) をその名前によって選択するということになります。詳しくは以下の Table 58 を見てください。
また、Response は以下のように構成されます。
| Data | SW1 | SW2 |
SW1-SW2 で Status を表します。90 00 が成功ステータスです。詳しくは以下の Table 17,18 を見てください。
読み取り
本編です
PSE を選択する
SELECT FILE Command を送信し、Payment System Environment (PSE) を選択します。
PSE の DF Name は "1PAY.SYS.DDF01"、PPSE の場合は "2PAY.SYS.DDF01" です。
サポートしていれば PSE の File Control Information (FCI) が返却されます。これに Application Elementary Files (AEF) の Short File Identifier (SFI) が含まれています。
AEF のレコードを読む
PSE をサポートしているカードのみの手順です。
SFI を使用して READ RECORD Command を送信し、Application Identifier (AID) を取得します。
AID でファイルを選択する
PSE をサポートしていた場合は先に取得した AID を、していなかった場合は直接 AID を指定し選択します。
例えば、Visa のクレジットカード・デビットカードは A0 00 00 00 03 10 10 です。
以下のように SELECT FILE Command を送信します。
| 00 | A4 | 04 | 00 | 07 | A0 00 00 00 03 10 10 | 00 |
Response として Processing Options Data Object List (PDOL) が返却されます。
PDOL を Parse する
返却された PDOL を Parse します。
PDOL は概ね以下のようになります。
... 01 01 9F 38 18 9F 66 04 9F 02 06 9F 03 06 9F 1A 02 95 05 5F 2A 02 9A 03 9C 01 9F 37 04 ...
PDOL の開始 Tag は 9F 38 なので、それを探します。
また、開始 Tag の直後は length です。9F 38 18 であれば length は 24 となります。
このことから PDOL は以下のようになります。
9F 66 04 9F 02 06 9F 03 06 9F 1A 02 95 05 5F 2A 02 9A 03 9C 01 9F 37 04
この PDOL は次に送る Command に request として何をどの length で送ればよいかを示しています。
- 9F 66
- Terminal Transaction Qualifiers (TTQ)
- 9F 02
- Amount, Authorised
- 9F 03
- Amount, Other
- 9F 1A
- Terminal Country Code
- 95
- Terminal Verification Results (TVR)
- 5F 2A
- Transaction Currency Code
- 9A
- Transaction Date
- 9C
- Transaction Type
- 9F 37
- Unpredictable Number
以上の Data を指定された length で構築します。
以下はそれぞれの例です。実際はそれぞれの要件等に合わせて構築します。
- TTQ
- bit で Option を切り替えます。詳細は以下の PDF の D.11 を見てください。
- https://www.emvco.com/wp-content/uploads/2017/05/C-6_Kernel_6_v2.6_20160512101849195.pdf
- 00101000 00000000 00000000
- b6 = EMV Mode supported
- b4 = Offline-only
- 28 00 00 00
- bit で Option を切り替えます。詳細は以下の PDF の D.11 を見てください。
- Amount, Authorised
- Amount, Other
- 取引しないため 0 で良いです。
- 00 00 00 00 00 00
- Terminal Country Code
- TVR
- https://en.wikipedia.org/wiki/Terminal_verification_results
- 00 00 00 00 00
- Transaction Currency Code
- Transaction Date
- YYMMDD
- 20 04 29
- Transaction Type
- 00
- Unpredictable Number
- 暗号文生成のためのユニークな数値
- 00 00 00 01
できたらこれらをくっつけます。
GPO Command を送信する
GET PROCESSING OPTIONS (GPO) Command を送信します。
先程の Data の length を先頭に付けます。
21 28 00 00 ...
Tag (83) をつけます。
83 21 28 00 00 ...
以下のように送信します。
| 80 | A8 | 00 | 00 | 23 | 83 21 28 00 ... | 00 |
Track2 Equivalent Data を Parse する
カード情報が含まれている Data を Parse します。Response として Track2 Equivalent Data が返却されている場合、開始 Tag は 57 です。
返却されていない場合はこちらではなく次を見てください。
57 の直後は length で、delimiter は 'd' です。
以下のようにデータが含まれています。
xx xx ... xx dy yy ...
- x = カード番号
- y = 有効期限
AFL を Parse する
ここから最後までは Track2 Equivalent Data ではなく Application File Locator (AFL) が返却された場合の手順です。
AFL の開始 Tag は 94 です。
Data から SFI と start, end の 3 bytes を取得します。
レコードを読む
さきほどの SFI をもとにすべてのレコードを読みます。
P1 として index (start から end まで), P2 として下位 3 bits を 1 0 0 とした SFI をセットします。これは P1 のレコードを読むという意味です。以下の Table 36 を見てください。
Track2 Data を Parse する
返却された Track2 Data を Parse します。開始 Tag は 9F 6B です。
あとは Track2 Equivalent Data と同じです。
終わりに
以上で読み取りまでの流れは終わりです。
結構端折った部分や私自身の知識が甘い部分もあり、調べながらでないとこの記事を読んで実装することは難しいと思いますが、私が実装するときに情報が散らばっていたりしてかなり苦労したので書いておきました。
以下に参考にしたリンクを貼っておきます。すべて非常に参考になるので読んでみてください。
あと実装した Repository も置いておきます。EMV Contactless 対応の Visa と Mastercard 3 枚で動作を確認していますので興味があれば。
参考
- https://www.emvco.com/wp-content/uploads/2017/05/C-6_Kernel_6_v2.6_20160512101849195.pdf
- https://www.emvco.com/wp-content/uploads/2017/05/C-7_Kernel_7_v2.6_20160512101943350.pdf
- https://cardwerk.com/smart-card-standard-iso7816-4-section-6-basic-interindustry-commands
- https://www.eftlab.com/knowledge-base/145-emv-nfc-tags/
- https://emvlab.org/emvtags
- https://www.openscdp.org/scripts/tutorial/emv/index.html
- https://www.microcosm.com/blog/smart-card-standards-explained
Android で Liquid-like な animation を Path#cubicTo で実現する
Dribbble にありがちなスライムのような何かぽよぽよした Animation をどう実現するか
ちなみに特に何かイベントに反応する必要がなければ Lottie で良いと思います
Path#cubicTo
実現する方法として、結論としてはタイトルにある通り Path#cubicTo を使います
これで Bezier curve を描くことができるので、柔らかい曲線や遷移を実現できます。
以下のような感じで引いていきます。
path.moveTo(0f, 0f) path.cubicTo(0f, 0f, 0f, 0f, 0f, 0f) path.cubicTo(0f, 0f, 0f, 0f, 0f, 0f) path.cubicTo(0f, 0f, 0f, 0f, 0f, 0f) path.close()
一つ注意点として、cubicTo の x1, y1 は point の左側の control point ではないので、
以下のような曲線を引く時
x1, y1 は前の point の右側の control point を指します。
作ってみる
これだけだと cubicTo の説明にしかなっていないので、作るときに楽だった方法だけ書いておきます。
まず Sketch や Figma などまぁそのへんの色々を使用して Animation のあたりをつけておきます。
先にあった gif の一番目だとこのような感じで作っていました。
こうするとだいたい progress 0.0 ~ 1.0 までどのように control point を動かしていけば想定どおりの動きとなるかが見えてきます。
まず progress 0.0 と 1.0 の状態 (もっと状態が複雑ならそれら) を作って、大体出来たらそれを補完するように point を動かします。
やることはこれだけです。
なお、作るときには control point も同じく描画しておくと良いでしょう。今は Android Studio 上で View の preview が見られるので調整作業がかなり楽になります。
終わりに
完全な円は書けないので、円から何かするというのは難しいかもしれません。
あと、Animation の見え方が device によってかなり変わると思うので、お仕事でやるなら相当苦しむことになると思います。パフォーマンス的にもとてもおすすめできない。
作るだけならまぁまぁ楽しいので、一回やってみると良いかもしれません。
上の Animation の実装は以下の Repository にあります。とても参考にできない実装ですが興味があれば。
Jetpack Compose の @Model で生成されるコード
メモ
こういうの書いたときに生成されるコードが気になっていた
@Model class AnimalState( var isAnimal: Boolean, var cats: List<Cat>, var dog: Dog ) @Model class Cat( var isRunning: Boolean, var isWalking: Boolean ) @Model class Dog( var count: Int, var type: Type ) { enum class Type { BULLDOG, DACHSHUND, SHIBA } }
build 配下を見て Kotlin で書き直すと概ねこんな感じになりそう
class AnimalState( isAnimal: Boolean, cats: List<Cat>, dog: Dog ) : Framed { private var record: Record = Record() private val readable get() = _readable( record, this ) as Record private val writable get() = _writable( record, this ) as Record var isAnimal: Boolean get() = readable.isAnimal set(value) { writable.isAnimal = value } var cats: List<Cat> get() = readable.cats set(value) { writable.cats = value } var dog: Dog get() = readable.dog!! set(value) { writable.dog = value } init { record.isAnimal = isAnimal record.cats = cats record.dog = dog _created(this) } override val firstFrameRecord: Record get() = record override fun prependFrameRecord(value: androidx.compose.frames.Record) { value.next = record record = value as Record } class Record : AbstractRecord() { var isAnimal: Boolean = false var cats: List<Cat> = emptyList() var dog: Dog? = null override fun assign(value: androidx.compose.frames.Record) { (value as Record).let { isAnimal = it.isAnimal cats = it.cats dog = it.dog } } override fun create(): androidx.compose.frames.Record = Record() } } class Cat( isRunning: Boolean, isWalking: Boolean ) : Framed { private var record = Record() private val readable get() = _readable( record, this ) as Record private val writable get() = _writable( record, this ) as Record var isRunning: Boolean get() = readable.isRunning set(value) { writable.isRunning = value } var isWalking: Boolean get() = readable.isWalking set(value) { writable.isWalking = value } init { record.isRunning = isRunning record.isWalking = isWalking _created(this) } override val firstFrameRecord: Record get() = record override fun prependFrameRecord(value: androidx.compose.frames.Record) { value.next = record record = value as Record } class Record : AbstractRecord() { var isRunning: Boolean = false var isWalking: Boolean = false override fun assign(value: androidx.compose.frames.Record) { (value as Record).let { isRunning = it.isRunning isWalking = it.isWalking } } override fun create(): androidx.compose.frames.Record = Record() } } class Dog( count: Int, type: Type ) : Framed { private var record = Record() private val readable get() = _readable( record, this ) as Record private val writable get() = _writable( record, this ) as Record var count: Int get() = readable.count set(value) { writable.count = value } var type: Type get() = readable.type set(value) { writable.type = value } init { record.count = count record.type = type _created(this) } override val firstFrameRecord: Record get() = record override fun prependFrameRecord(value: androidx.compose.frames.Record) { value.next = record record = value as Record } enum class Type { BULLDOG, DACHSHUND, SHIBA } class Record : AbstractRecord() { var count: Int = 0 var type: Type = Type.BULLDOG override fun assign(value: androidx.compose.frames.Record) { (value as Record).let { count = it.count type = it.type } } override fun create(): androidx.compose.frames.Record = Record() } }
なるほどー
第 1 水準以外の漢字を検出する
JIS 漢字コードの第 1 水準漢字以外を検出します。
Perl が一番楽そうだったので Perl 使いました。
何がしたいか
$ echo "perl" | hoge false $ echo "あいうえお。" | hoge false $ echo "漢字 | hoge false $ echo "弌腕。" | hoge # 第 2 水準漢字が入っている true
第 1 水準漢字
第 1 水準漢字は亜から腕までの範囲を指します。
ただ、亜-腕のような正規表現でチェックするだけでは不十分です。
この記事に詳細は書いてあり、非常に参考になりました。
ということで範囲を調べる必要があります。
コード表を参照して、Shift JIS における文字コードの範囲を調べます。
亜だったら 889F, 腕は 9872 です。
一通り調べると、
- 889F - 88FC
- 8940 - 97FC
- 9840 - 9872
の範囲であることが分かります。
これを正規表現に起こすとこんな感じ
\x88[\x9F-\xFC]|[\x89-\x97][\x40-\xFC]|\x98[\x40-\x72]
第 1 水準漢字以外の漢字
第 1 水準漢字の検出はできましたが、実際に欲しいものは違います。
漢字の検出は \p{Han} で可能です。
なので、\p{Han} で取得した漢字を先程の正規表現に当てて、引っかからないものが第 1 水準漢字以外としました。
ただ、\p{Han} だと句読点なども含まれます。\p{Script=Han} にすると句読点は除くことができますが、々などは入ってきます。
なので、先程の正規表現で記号も見ることにします。コード表によると記号は
- 8140-81AC
あたりです (実際はもうちょっとあります、サボりました)
これを付け加えて
\x81[\x40-\xAC]|\x88[\x9F-\xFC]|[\x89-\x97][\x40-\xFC]|\x98[\x40-\x72]
これでふんわり記号 + 第 1 水準漢字です。ちゃんと見るなら 1 区から 8 区までの文字コードを追加すれば良いと思います。
この正規表現で引っかからない場合は第 2 水準漢字とか、そのへんです。
あと、第 1 水準漢字のところでは Shift JIS になっている必要があるので
encode("sjis", $hans);
こういう処理が必要です。
Repository はここ
おわり。
動的解析ツール Frida を Android に使う
Frida を使ってみたメモ
Frida
自分の Script を Inject したり、値を取得したり、色々なことができる Toolkit です。
リバースエンジニアリングとかする時に使うみたいですね。
iOS / Android にも対応しており、今回は Android の話です。
ちなみに日本語の記事もいくつかあります。
もし試す場合は自分のアプリや許可されているものを使用して試してください。
ここからの手順やコマンド、コードによって何が起きても私は責任を取りません。
入れてみる
Rooted device のほうが簡単らしいのですが、持ってないので Without root で試します。
肝心の手順はここに全部書いており、写すことはしないのでこちらを参照してください。
簡単に手順を書くと
1. apk を device から抜く (pm path とかしてから pull する)
2. apktool を用いて apk を decode する
3. 諸々いじった後、libfrida-gadget.so を lib/ に入れる
4. apktool で apk を build
5. jarsigner で署名 / 検証
6. zipalign で最適化
という感じです。
3 での諸々いじる工程もすべて上記の記事に書いてあります。
使ってみる
試したコードとアプリはここにあります。以降はこのデモ app を使用しています。
準備
Frida の install が終わっていない場合は
$ pip install frida-tools
さて、Frida は Python で書くのですが、Inject する Sctipt は JavaScript です。
ということでまずはベースとなる Python の Script が必要になります。
こちらをベースにして、
import sys import frida def on_message(message, data): print(message) js = """ Java.perform(function() { }); """ process = frida.get_usb_device().attach('Gadget') script = process.create_script(js) script.on('message', on_message) script.load() sys.stdin.read()
こんな感じです。今回は Gadget を使っているので attach('Gadget') となります。
アプリが待ち受けている状態等で、
$ python example.py
を実行すると何も起きないはずです。
エラーが出る場合はおそらくどこか間違えています。
Detector の返す値を変更する
Repository を見てもらうと分かりますが jp.cordea.fridademo.Detector という Class があり、Detector#detect が false を返しています。
Detector#detect が true だと Button を click した際に toast が表示されます。ということで toast が表示されるようにします。
var detector = Java.use('jp.cordea.fridademo.Detector'); detector.detect.overload().implementation = function() { return true; }
Button を click すると toast が表示されるはずです。
fab の click を上書きする
続いて fab の click を上書きします。まず fab を取得する必要があります。
var fabId = activity.findViewById(0x7f080069); var fab = Java.cast( fabId.$handle, Java.use('com.google.android.material.floatingactionbutton.FloatingActionButton') );
findViewById で fab を取得し、FloatingActionButton に cast しています。
なお、この ID はデモ app の場合は smali/jp/cordea/fridademo/R\$id.smali を検索することで取得できます。
Listener も必要です。
var listener = Java.use('android.view.View$OnClickListener');
そして Listener をセットします。
fab.setOnClickListener(Java.registerClass({ name: 'jp.cordea.fridademo.OnClickListener', implements: [listener], methods: { onClick: function(v) { } } }).$new());
TextView の count を上書きする
さて、先程 fab の click を上書きしたので今まで click 毎に +1 されていた TextView の count が動かなくなりました。
これを *2 するようにしてみましょう。
var textViewId = activity.findViewById(0x7f0800de); var textView = Java.cast( textViewId.$handle, Java.use('android.widget.TextView') );
そしてさきほどの onClick の中に *2 する実装を入れます
var count = 1; fab.setOnClickListener(Java.registerClass({ name: 'jp.cordea.fridademo.OnClickListener', implements: [listener], methods: { onClick: function(v) { count *= 2; } } }).$new());
そして上書きしますが、java.lang.String としてセットする必要があります。
なので、以下のようにセットします。
var count = 1; fab.setOnClickListener(Java.registerClass({ name: 'jp.cordea.fridademo.OnClickListener', implements: [listener], methods: { onClick: function(v) { count *= 2; var string = Java.use('java.lang.String'); textView.setText(string.$new(count.toString())); } } }).$new());
これで fab を click すると +1 ではなく *2 で値が増えていく様子が見られると思います。
詳しくは Repository の example.py を見てください。