20190512のAndroidに関する記事は11件です。

Android 9.0 pie でランチャーアプリによってナビゲーションバーの挙動が変わるっぽいところのソースを読む

Androidでおなじみのナビゲーションバー

image.png

私はあいにくお金がなくてPixel 3を購入できていないんですが、巷の噂によると
これがPixel 3のナビゲーションバーは

image.png

こんなのらしい。

そして、どうやら設定でこのあたりを変えることができるらしい。

あとは、ホームアプリによってもこのナビゲーションの挙動が変わる(?)らしい。

System UIというシステムアプリがホームアプリによって挙動が変わる?!というあたりが気になったので、久々にソース呼んでみました。

あらかじめ書いておきますが、完全に自分用メモです。


設定で変えられるところ

手元には実端末がなくてエミュレータしかないんですが、
https://android.benigumo.com/20180809/pie-navigation-bar/
をみるに
image.png
どうやらこの画面にスワイプアップ時の挙動を変更する設定が存在するらしい。

http://androidxref.com/9.0.0_r3/xref/packages/apps/Settings/res/xml/gestures.xml
設定画面のリソースを見ると

48    <Preference
49        android:key="gesture_swipe_up_input_summary"
50        android:title="@string/swipe_up_to_switch_apps_title"
51        android:fragment="com.android.settings.gestures.SwipeUpGestureSettings"
52        settings:controller="com.android.settings.gestures.SwipeUpPreferenceController" />

たしかにありました。

設定できる端末とできない端末の違いは?

http://androidxref.com/9.0.0_r3/xref/packages/apps/Settings/src/com/android/settings/gestures/SwipeUpPreferenceController.java#44

44    static boolean isGestureAvailable(Context context) {
45        if (!context.getResources().getBoolean(R.bool.config_swipe_up_gesture_setting_available)) {
46            return false;
47        }
48
49        final ComponentName recentsComponentName = ComponentName.unflattenFromString(
50                context.getString(R.string.config_recentsComponentName));
51        final Intent quickStepIntent = new Intent(ACTION_QUICKSTEP)
52                .setPackage(recentsComponentName.getPackageName());
53        if (context.getPackageManager().resolveService(quickStepIntent,
54                PackageManager.MATCH_SYSTEM_ONLY) == null) {
55            return false;
56        }
57        return true;
58    }

2つ条件がある。

1つめは、config_swipe_up_gesture_setting_available っていうのが端末のconfigでtrueになっていないとダメらしい。

http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/values/config.xml#3448 を見るに、おそらくデフォルトはfalse。

3444    <!-- Whether or not swipe up gesture is enabled by default -->
3445    <bool name="config_swipe_up_gesture_default">false</bool>
3446
3447    <!-- Whether or not swipe up gesture's opt-in setting is available on this device -->
3448    <bool name="config_swipe_up_gesture_setting_available">false</bool>

2つ目はちょっと複雑だけど、

config_recentsComponentName っていうのが端末のconfigにあって、そこにRecentAppsを表示するコンポーネントが書いてある。(AOSPだと com.android.launcher3/com.android.quickstep.RecentsActivity )

んで、そいつと同じパッケージに属する子で Intent.ACTION_QUICKSTEP を拾ってくれる子だったらOK。(AOSPだと packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java

frameworks/resのconfig.xmlもLauncher3も端末出荷状態で入ってるんだから、2つ目のif文なくてもいいんじゃね?って思ったけど、端末出荷状態からLauncher3がアップデートしてACTION_QUICKSTEPを拾わなくなってもバグらないようにするための配慮かな?

SwipeUpPreferenceController は何のon/offをしているのか

Settingsアプリは、 SWIPE_UP_TO_SWITCH_APPS_ENABLED のシステム設定値をon/offしているだけ。
http://androidxref.com/9.0.0_r3/xref/packages/apps/Settings/src/com/android/settings/gestures/SwipeUpPreferenceController.java

これの変化を監視してるのは、AOSPだとLauncher3。

http://androidxref.com/9.0.0_r3/xref/packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewInteractionState.java#220

219        @Override
220        public void onChange(boolean selfChange) {
221            super.onChange(selfChange);
222            mHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
223            mHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, getValue() ? 1 : 0, 0).sendToTarget();
224        }

設定値が切り替わった契機で、

http://androidxref.com/9.0.0_r3/xref/packages/apps/Launcher3/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java#61

61    public static TouchController[] createTouchControllers(Launcher launcher) {
62        boolean swipeUpEnabled = OverviewInteractionState.getInstance(launcher)
63                .isSwipeUpGestureEnabled();
64        if (!swipeUpEnabled) {
65            return new TouchController[] {
66                    launcher.getDragController(),
67                    new OverviewToAllAppsTouchController(launcher),
68                    new LauncherTaskViewController(launcher)};
69        }
70        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
71            return new TouchController[] {
72                    launcher.getDragController(),
73                    new OverviewToAllAppsTouchController(launcher),
74                    new LandscapeEdgeSwipeController(launcher),
75                    new LauncherTaskViewController(launcher)};
76        } else {
77            return new TouchController[] {
78                    launcher.getDragController(),
79                    new PortraitStatesTouchController(launcher),
80                    new LauncherTaskViewController(launcher)};
81        }
82    }

このあたりの、タッチイベントハンドラのクラス群のどれを使うべきか?、というのを再評価される。

Quickstep

さっきからちょいちょい出てきてる quickstep っていう単語。あとはOverviewとか。
どうもこいつがキモにみえる。

SystemUIのNavigationBarView付近にこんなソースを見つけた。

http://androidxref.com/9.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java

233                if (mIsVertical) {
234                    exceededScrubTouchSlop =
235                            yDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && yDiff > xDiff;
236                    exceededSwipeUpTouchSlop =
237                            xDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && xDiff > yDiff;
238                    pos = y;
239                    touchDown = mTouchDownY;
240                    offset = pos - mTrackRect.top;
241                    trackSize = mTrackRect.height();
242                } else {
243                    exceededScrubTouchSlop =
244                            xDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && xDiff > yDiff;
245                    exceededSwipeUpTouchSlop =
246                            yDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && yDiff > xDiff;
247                    pos = x;
248                    touchDown = mTouchDownX;
249                    offset = pos - mTrackRect.left;
250                    trackSize = mTrackRect.width();
251                }

このあたりのロジックを流し見るに、

  • 上に24dp以上動かしたら、NavigationBarからLauncher3に「quickstepのハンドリングよろしく」って通知がいく
  • 左右に24dp以上動かしたら、NavigationBarからLauncher3に「quick scrubのハンドリングよろしく」って通知がいく

みたいな感じか。あとは、そもそもMotionEventは基本的にNavigationBarからLauncher3に送られるっぽい?

http://androidxref.com/9.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java#proxyMotionEvents

488    private boolean proxyMotionEvents(MotionEvent event) {
489        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
490        event.transform(mTransformGlobalMatrix);
491        try {
492            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
493                overviewProxy.onPreMotionEvent(mNavigationBarView.getDownHitTarget());
494            }
495            overviewProxy.onMotionEvent(event);
496            if (DEBUG_OVERVIEW_PROXY) {
497                Log.d(TAG_OPS, "Send MotionEvent: " + event.toString());
498            }
499            return true;
500        } catch (RemoteException e) {
501            Log.e(TAG, "Callback failed", e);
502        } finally {
503            event.transform(mTransformLocalMatrix);
504        }
505        return false;
506    }

NavigationBarから送出されるタッチイベントは誰でも受け取れるのか?

「ランチャーアプリによってナビゲーションバーの挙動が変わる」ってところが個人的に気になってたんだけど、サードパーティーのランチャーアプリがPixelLauncherのようにRecentAppsの画面を表示したりQuickstepをハンドリングしたりできるかというと、そういうわけではなさそう。

http://androidxref.com/9.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java

257    public OverviewProxyService(Context context) {
258        mContext = context;
259        mHandler = new Handler();
260        mConnectionBackoffAttempts = 0;
261        mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
262                com.android.internal.R.string.config_recentsComponentName));
263        mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
264                .setPackage(mRecentsComponentName.getPackageName());

このように、端末ベンダーがxmlファイルでconfig_recentsComponentNameを決め打ちしているものしかQuickstepをさばく対象にはなりえないようだ。

WebViewみたいに、いずれサードパーティーアプリに開かれるようになるのかもしれないけど、Android 9.0時点ではそのような口はない。

 

まとめ

  • NavigationBarViewがLauncher3 (端末ベンダーがxmlファイルで変更はできる)にタッチイベントとかquick scrub, quickstep などを送出するようになった
  • サードパーティーのランチャーアプリに導線が開かれているわけではない。端末ベンダーがxmlで指定したコンポーネントだけがNavigationBarViewのタッチイベントを受け取ったりRecentAppsを表示したりできる。

ちなみに、調べてる途中に見つけたのだけど、中国のスーパーハカーがこの記事に書いてないところまで結構事細かに書いている。
https://www.jianshu.com/p/83ce8731fb13

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter始めてみる 〜環境構築からDemoアプリ起動編〜

こんにちはひろにぃです。
タイトル通りやっていきます。

目次

  • 作業環境
  • Flutterのインストール
  • 依存ツールのチェック
  • Android Studioの設定
  • Demoアプリの起動

作業環境

  • macOS Mojave version 10.14.4
  • Flutter v1.5.4-hotfix.2 // Stable版
  • Android Studio 3.4
  • Android SDK version 28.0.3
  • Xcode 10.2.1

Flutterのインストール

ではインストールします。

公式URL https://flutter.dev/docs/get-started/install/macos

$ cd
$ git clone -b stable https://github.com/flutter/flutter.git
$ echo "export PATH=$PATH:$HOME/flutter/bin" >> ~/.zshrc
$ source ~/.zshrc

これでFlutterのインストール完了です。続いて、Flutterに必要なツールがインストールされているかチェックします。

依存ツールのチェック

$ flutter doctor

実行すると以下の画像のように依存ツールがインストールされているかの有無が表示されます。
問題がある場合は、指示に従って修正してください。

スクリーンショット 2019-05-11 21.42.07.png

[!] Android toolchain

ほかのエラーはbrewでインストールしろと書いてありますが、これだけはURL先を見ろと言われます。
Terminalに書いてある通りアップデートしてやりますが、つまずいたところがあるのでここにメモします。

# update
$ ~/Library/Android/sdk/tools/bin/sdkmanager --update

ERROR: JAVA_HOME is set to an invalid directory
上記を実行するとこのようなエラーが出るかもしれません。
その場合は、 $JAVA_HOME を有効なものに変更してください。

# 解決例の一つ
$ brew tap homebrew/cask-versions
$ brew cask install adoptopenjdk8

$ echo $JAVA_HOME
# /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home

アップデート後、ライセンスを有効にしてやります。

$ flutter doctor --android-licenses

Android Studioの設定

FlutterとDartのプラグインをインストールします。
Configure -> Plugins と移動しMarketPlaceでFlutterとDartで検索して当該プラグインをインストールします。
その後、IDEを再起動します。

スクリーンショット 2019-05-12 23.08.22.png

するとAndroid Studioのトップ画面に Start a new Flutter Project が追加されています。

スクリーンショット 2019-05-12 23.09.43.png

それをクリックしプロジェクトを作成していきます。

Demoアプリの起動

まず、Flutter Application を選択します。

スクリーンショット 2019-05-12 23.12.17.png

名前と保存先をテキトーに決めて 次へ

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3135383332342f30643962643939332d313431382d303562652d643131652d3533653862303134613966622e706e67.png

今回はDefaultのDemoアプリを起動するのでなにもいじらずこのままで Finish します。

スクリーンショット 2019-05-12 23.13.15.png

プロジェクトが開いたら、画面右上にある <no devices> と書かれたところをクリックして ios, Android のエミュレーターを起動します。

スクリーンショット 2019-05-12 23.13.58.png

エミュレーターが起動したら下記のようになります。
buildターゲットを選択し、横にある横三角ボタンで実行してください。

スクリーンショット 2019-05-12 23.15.34.png

すると、以下のようになります。

スクリーンショット 2019-05-12 23.05.05.png

以上です。お疲れ様でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android]HashMapからランダムなキーと値を取得する

メモエントリー。

List<String> keysAsArray = new ArrayList<>(map.keySet());
Random random = new Random();
String key = keysAsArray.get(random.nextInt(keysAsArray.size()));
String value = map.get(key);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ログから処理時間を算出する #4(開始日時と終了日時の計算)

はじめに

Androidのlogcatコマンドで出力されたログファイルを解析し、端末の処理時間を算出したい。

開始日時と終了日時の差分を計算する。

実装

from datetime import datetime
from datetime import timedelta

start_time = datetime.strptime('2019-03-26 01:43:37', '%Y-%m-%d %H:%M:%S')
end_time   = datetime.strptime('2019-03-27 02:34:29', '%Y-%m-%d %H:%M:%S')

date_dat = end_time - start_time
print( date_dat )

結果

1 day, 0:50:52

結果は取得できた。
あとは#〜#4を全部足せば、最低レベルの機能は完成する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoogleのKotlin style guideを翻訳してみた

Googleが 公開している Kotlinのスタイルガイドを日本語に翻訳してみました。
訳が間違っているところがあったら教えてください

Kotlin スタイルガイド

この文書はKotlinのソースコード用Androidコーディング標準の完全な定義です。Kotlinのソースファイルは、この規則に準拠している場合に限り、Google Android Styleであるとしてみなされます。

他のプログラミングスタイルガイドと同様に、カバーされている問題はフォーマットの美しさだけでなく、他の種類の規約・コーディング規約にも及んでいます。ただし、この文書では主に私たちが普遍的に従う厳格な規則に焦点を当てていて、人・ツールに関わらず、明確でないアドバイスが含まれないようにしています。

最終更新日: 2018年5月18日

ソースファイル

全てのソースファイルはUTF-8でエンコードされている必要があります。

命名

ソースファイルにトップレベルクラスが1つしか含まれていない場合、ファイル名は大文字と小文字を区別して、クラス名に.kt拡張子を付けたものにします。

複数のトップレベルクラスが含まれている場合は、ファイルの内容が分かる名前をつけて、それをPascalCaseにして.kt拡張子を付けます。

// MyClass.kt
class MyClass { }
// Bar.kt
class Bar { }
fun Runnable.toBar(): Bar = // …
// Map.kt
fun <T, O> Set<T>.map(func: (T) -> O): List<O> = // …
fun <T, O> List<T>.map(func: (T) -> O): List<O> = // …

特殊文字

空白文字

ソースファイルに現れる空白文字は、改行コードを除くと ASCII 水平スペース (0x20) のみを使います。

  • 文字列・文字リテラル内の他の全ての空白文字はエスケープします
  • インデントにはタブ文字を使いません

エスケープシーケンス

特別なエスケープシーケンスがある全ての文字(\b \n \r \t \' \" \\ \$)は、対応するUnicodeエスケープ(例: \u000a) ではなく、そのエスケープシーケンスを使います。

非ASCII文字

非ASCII文字については、実際のUnicode文字(例: ) またはUnicodeエスケープ(例: \u221e) の両方が使えます。どちらを使うかは どちらがコードが読みやすい・理解しやすいようになるか の観点から決めます。Unicodeエスケープは、常に出力可能文字には使用しないことを推奨します。また、文字列リテラルやコメントの外での使用は推奨されません。

評価
val unitAbbrev = "μ's" 最高: コメントがなくても完全に明確です
val unitAbbrev = "\u03bcs" // μs 悪い: 出力可能文字でエスケープを使う理由はありません
val unitAbbrev = "\u03bcs" 悪い: 読む人はこれが何か分かりません
return "\ufeff" + content 良い: 出力できない文字にはエスケープを使い、必要ならコメントをつけてください

構造

.ktファイルは順番に以下のセクションを含みます

  • 著作権/ライセンスのヘッダ(オプション)
  • ファイルレベルのアノテーション
  • package文
  • import文
  • トップレベルの宣言

これらの各セクションは1つの空白行で区切られます。

著作権/ライセンスのヘッダ

著作権ヘッダやライセンスヘッダがファイルについている場合は、複数行コメントのすぐ上に記載する必要があります

OK
/*
 * Copyright 2017 Google, Inc.
 *
 * ...
 */

KDocスタイル や単一行スタイルのコメントは使用しないでください。

NG (KDocスタイルのコメント)
/**
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
NG (単一行スタイルのコメント)
// Copyright 2017 Google, Inc.
//
// ...

ファイルレベルのアノテーション

"file"ユーズサイトターゲットのアノテーションはヘッダーコメントとパッケージ宣言の間に配置します。

package文

package文は1行あたりの最大文字数制限を受けず、行は折り返されません。

import文

クラス・関数・プロパティのimport文は1つのリストにまとめられ、ASCII順にソートされます。
ワイルドカードを用いたimportはどのような種類の物でも 許可されていません
package文と同様に、import文は1行あたりの最大文字数制限を受けず、行は折り返されません。

トップレベルの宣言

.ktファイルでは、トップレベルで1つ以上の型・関数・プロパティ・型エイリアスを宣言することができます。
ファイルの内容は1つのテーマに関しての内容である必要があります。例として、1つのpublic型や、複数のレシーバーで同じ操作を実行する拡張機能をまとめたものがあります。関係のない宣言は別のファイルに分けられるべきであり、1つのファイルに含まれるpublic宣言は少なくするべきです。
ファイルに含まれる内容の数や順序に明確な制限はありません。
通常、ソースファイルは上から下に向かって読まれます。つまり、一般的に上にある宣言ほど内容が良く理解されます。ファイルが異なれば、内容の順序を変えることができます。同様に、1つのファイルに100個のプロパティ・10個の関数・さらに1つのクラスを含めることができます。

それぞれのクラスが 何らかの 論理的な順序に沿って並べることが重要です。論理的な順序とは、メンテナに要求されたら説明することができるものです。例えば、新しい関数をクラスの最後に追加する習慣を使ってはいけません。論理的な順序ではない、「追加された日付順」で並べられるからです。

クラスメンバーの順序

クラス内のメンバーの順序は、トップレベルの宣言と同じ規則に従って並べます。

フォーマット

中括弧

他にelse ifif分岐がなく、1行におさまるwhen分岐やif文の中身には、中括弧は必要ありません。

if (string.isEmpty()) return

when (value) {
    0 -> return
    // …
}

それ以外の場合は、中身が空や1つの文しか含まれていない場合でも、ifforwhen分岐・dowhile文には中括弧が必要です。

if (string.isEmpty())
    return  // NG

if (string.isEmpty()) {
    return  // OK
}

空でないブロック

空でないブロックやブロックのような構成要素での中括弧は、K&Rスタイル(エジプト括弧)に従います。

  • 開き中括弧の前に改行を置きません
  • 開き中括弧の後で改行します
  • 閉じ中括弧の前に改行します
  • 閉じ中括弧の後の改行は、その中括弧が文を終了するか、関数・コンストラクタ・名前付きクラスの本体を終了する場合のみ置きます。例えば、中括弧の後にelseやカンマが続く場合は、中括弧の後に改行を置きません
return Runnable {
    while (condition()) {
        foo()
    }
}

return object : MyClass() {
    override fun foo() {
        if (condition()) {
            try {
                something()
            } catch (e: ProblemException) {
                recover()
            }
        } else if (otherCondition()) {
            somethingElse()
        } else {
            lastThing()
        }
    }
}

enumクラスにはいくつかの例外があります。

空のブロック

空のブロックやブロックのような構成要素はK&Rスタイルでなければなりません。

try {
    doSomething()
} catch (e: Exception) {} // NG
try {
    doSomething()
} catch (e: Exception) {
} // OK

式として使用されるif/else条件は、式全体が1行におさまる場合にのみ中括弧を省略することができます。

val value = if (string.isEmpty()) 0 else 1  // OK
val value = if (string.isEmpty())  // NG
                0
            else
                1
val value = if (string.isEmpty()) { // OK
    0
} else {
    1
}

インデント

ブロックやブロックのような構成要素が開かれるたびに、スペース4つ分インデントします。ブロックが終了すると、インデントは前のインデントレベルに戻ります。インデントレベルはブロック全体のコードとコメントの両方に適用されます。

1行に1つの文

それぞれの文の後には改行が続きます。セミコロンは使用しません。

行の折り返し

コードの最大桁数は100文字です。以下の場合を除き、100文字を超える行は後述のように行を折り返す必要があります。

例外
* 100文字制限に従うことが不可能な行(例: KDocの長いURL)
* packageimport
* シェルにコピペできるコマンドを書いたコメント

折り返す位置

行の折り返しの主な条件は次の通りです。より高い構文レベルで分割することを優先します。

  • =以外の演算子で行が分割される場合は、その演算子の直前で折り返します
    • これは次の「演算子のような記号」にも当てはまります
    • ドット区切り記号 (.)
    • メンバー参照で使う2つのコロン (::)
  • =で行が分割される場合は、=の直後で折り返します
  • メソッド名やコンストラクタ名はその直後の開き括弧 (())と分割することはできません
  • カンマ (,)はその前のトークンに付加されたままです
  • ラムダ矢印 (->)は、その前の引数リストについたままです

行の折り返しの主な目的は、簡潔なコードを書くことです。それは必ずしも最小の行数に収まるコードを書くことと等価であるとは限りません。

関数

関数シグネチャが1行におさまらない場合は、各パラメータ宣言をそれぞれ1行に分割してください。このとき、それぞれのパラメータは1段階インデント(スペース4つ)するべきです。閉じ括弧 ()) と戻り値の型はインデントを増やさず、それぞれ別の行に配置されます。

fun <T> Iterable<T>.joinToString(
    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = ""
): String {
    // …
}
単一式関数

関数に1つの式しか含まれていない場合は、単一式関数として書くことができます。

通常の例
override fun toString(): String {
    return "Hey"
}
単一式関数を使った例
override fun toString(): String = "Hey"

ブロックを開いたときのみ単一式関数が複数に折り返されます。

fun main() = runBlocking {
  // …
}

ブロックを開いたとき以外で単一式関数が折り返しを必要とするようになった場合は、returnを使った通常の関数として書き、通常の折り返し規則を使用してください。

プロパティ

プロパティ初期化子が1行におさまらない場合は、イコール(=)の後で改行し、インデントしてください。

private val defaultCharset: Charset? =
        EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)

関数get/setを宣言するプロパティは、1段階インデント(スペース4つ)して、それぞれを独自の行に配置します。関数と同じ規則を使用します。

var directory: File? = null
    set(value) {
        // …
    }

読み取り専用プロパティは1行におさまる短い構文を使用できます。

val defaultExtension: String get() = "kt"

空白

垂直方向

次の場合は空白行を1行おきます

  • プロパティ・コンストラクタ・関数・子クラスなどの、クラス内の連続するメンバー間
    • 例外: 2つの連続したプロパティ間の空白行はオプションです。このような空白行は必要に応じてプロパティを論理的にグループ化したり、存在する場合はプロパティをそのバッキングプロパティに関連付けたりするために使われます。
    • 例外: 列挙型定数の間の空白行は下記の通りです。
  • 必要に応じて文を論理的なサブセクションに分割するため
  • 必要に応じて関数の最初の文の前、クラスの最初のメンバーの前、クラスの最後のメンバーの後 (推奨ではありません)
  • この文書の他のセクション(構造セクションなど)の内容に応じて

複数の連続した空白行は許容されますが、推奨されることも要求されることもありません。

水平方向

言語仕様や他のスタイル規則で要求されている場所・リテラル内・コメント・KDocを除いて、単一のASCIIスペースは次の場所にのみ置かれます。

  • ifforcatchなどの任意の予約語を、その後に続く開き中括弧({)から分離するため
// NG
for(i in 0..1) {
}
// OK
for (i in 0..1) {
}
  • elsecatchなどの任意の予約語を、その前にある閉じ中括弧(})から分離するため
// NG
}else {
}
// OK
} else {
}
  • 任意の開き中括弧({)の前
// NG
if (list.isEmpty()){
}
// OK
if (list.isEmpty()) {
}
  • 二項演算子の左右
// NG
val two = 1+1
// OK
val two = 1 + 1

これは次の「演算子のような記号」にも当てはまります

  • ラムダ式の矢印(->)
// NG
ints.map { value->value.toString() }
// OK
ints.map { value -> value.toString() }

しかし、以下の場合は当てはまりません

  • メンバー参照で使う2つのコロン(::)
// NG
val toString = Any :: toString
// OK
val toString = Any::toString
  • ドット区切り記号(.)
// NG
it . toString()
// OK
it.toString()
// OK
val toString = Any::toString
  • 範囲演算子(..)
// NG
for (i in 1 .. 4) print(i)
// OK
for (i in 1..4) print(i)
  • コロン(:)の前は基本クラスまたは基本インターフェースを指定するためのクラス宣言、またはジェネリクスのwhere説で使用されている場合に限ります
// NG
class Foo: Runnable
// OK
class Foo : Runnable
// NG
fun <T: Comparable> max(a: T, b: T)
// OK
fun <T : Comparable> max(a: T, b: T)
// NG
fun <T> max(a: T, b: T) where T: Comparable<T>
// OK
fun <T> max(a: T, b: T) where T : Comparable<T>
  • カンマ(,)・コロン(:)の後
// NG
val oneAndTwo = listOf(1,2)
// OK
val oneAndTwo = listOf(1, 2)
// NG
class Foo :Runnable
// OK
class Foo : Runnable
  • 1行コメントを開始する二重スラッシュ(//)の両側。ここでは複数のスペースを使用できますが、必須ではありません。
// NG
var debugging = false//disabled by default
// OK
var debugging = false // disabled by default

この規則は、行頭または行末にスペースを書くことを要求したり禁止したりするわけではありません。内部空間のみを対象とします。

特定の文脈

enumクラス

関数・その定数に関するドキュメントがないenumは、1行で書くことができます。

enum class Answer { YES, NO, MAYBE }

enum内の定数が別々の行に配置されている場合、それらが本体を定義する場合を除いて、空白行は必要ありません。

enum class Answer {
    YES,
    NO,

    MAYBE {
        override fun toString() = """¯\_(ツ)_/¯"""
    }
}

enumクラスはクラスなので、クラスの書式に関する他の全ての規則が適用されます。

アノテーション

メンバーまたは型のアノテーションは対象要素の直前の別の行に配置されます。

@Retention(SOURCE)
@Target(FUNCTION, PROPERTY_SETTER, FIELD)
annotation class Global

引数がないアノテーションは単一行に配置できます。

@JvmField @Volatile
var disposable: Disposable? = null

引数がないアノテーションが1つしかない場合は、宣言と同じ行にアノテーションを配置することができます。

@Volatile var disposable: Disposable? = null

@Test fun selectAll() {
    // …
}

暗黙の戻り値/プロパティ型

単一式関数本体またはプロパティの初期化子がスカラー値である場合・戻り値が明確に推測できる場合は、その型を省略できます。

override fun toString(): String = "Hey"
// ↓
override fun toString() = "Hey"
private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png")
// ↓
private val ICON = IconLoader.getIcon("/icons/kotlin.png")

ライブラリを作成するとき、それがパブリックAPIの一部である場合は型を明示的に指定してください。

命名

識別子にはASCII文字と数字だけを使用します。また、後述の一部のケースでは追加でアンダースコア(_)も使用します。従って、有効な識別子名は正規表現\w+で表現されます。

name_, mName, s_name, kNameなどの例に見られるような特別な接頭辞や接尾辞はバッキングプロパティを除いて使用されません。

パッケージ名

パッケージ名はすべて小文字で連続する単語はアンダースコアなしで単純に連結されます。

// OK
package com.example.deepspace
// NG
package com.example.deepSpace
// NG
package com.example.deep_space

型名

クラス名はPascalCaseで書かれ、通常は名詞または名詞句です。(例: Character, ImmutableList)。インターフェース名は名詞や名詞句(例: List)でもかまいませんが、形容詞や形容詞句(例: Readable)を使うこともあります。
テストクラスはテストしているクラスの名前で始まり、Testで終わる名前がつけられます。(例: HashTest, HashIntegrationTest)

関数名

関数名はcamelCaseで書かれ、通常は動詞または動詞句です。(例: sendMessage, stop)。
テスト関数名の論理構成要素を区切るためにアンダースコア(_)を使用できます。

@Test fun pop_emptyStack() {
    // …
}

定数名

定数名ではUPPER_SNAKE_CASEを使用します。すべて大文字で、単語はアンダースコア(_)で区切ります。しかし、本当は何が定数なのでしょうか?
定数はvalプロパティで、カスタムget関数を持たず、その内容は完全に不変です。関数には検出できる副作用はありません。これには、不変な型、不変な型への不変なコレクション、constとしてマークされているスカラー型と文字列が含まれます。インスタンスで、状態のいずれかが変化する可能性があり、それを検出できる場合は、それは定数ではありません。オブジェクトを絶対に変更しないようにするだけでは不十分です。

const val NUMBER = 5
val NAMES = listOf("Alice", "Bob")
val AGES = mapOf("Alice" to 35, "Bob" to 32)
val COMMA_JOINER = Joiner.on(',') // Joiner is immutable
val EMPTY_ARRAY = arrayOf()

定数の名前は通常は名詞または名詞句です。
定数値はobjectの内部またはトップレベルの宣言としてのみ定義できます。それ以外の場合は定数の要件を満たしていても、定数の名前は使用できません。
スカラー値である定数はconst修飾子を使わなければなりません。

非定数名

非定数名はcamelCaseで書かれます。これらはインスタンスプロパティ・ローカルプロパティ・パラメータ名に適用されます。

val variable = "var"
val nonConstScalar = "non-const"
val mutableCollection: MutableSet = HashSet()
val mutableElements = listOf(mutableInstance)
val mutableValues = mapOf("Alice" to mutableInstance, "Bob" to mutableInstance2)
val logger = Logger.getLogger(MyClass::class.java.name)
val nonEmptyArray = arrayOf("these", "can", "change")

これらの名前は通常は名詞または名詞句です。

バッキングプロパティ

バッキングプロパティが必要とされるときには、その名前はアンダースコア(_)で始まることを除いて必ず実際のプロパティ名と一致する必要があります。

private var _table: Map? = null

val table: Map
    get() {
        if (_table == null) {
            _table = HashMap()
        }
        return _table ?: throw AssertionError()
    }

型変数名

型変数は次の2スタイルのどちらかによって名前がつけられます。

  • 1文字の大文字に必要に応じて1文字の数字が後に続く(例: E, T, X, T2)
  • クラス名に使われる名前のあとにTをつける(例: RequestT, FooBarT)

キャメルケース

頭字語や「IPv6」・「iOS」のような変わった構成の単語が存在する場合など、英語のフレーズをキャメルケースに変換するための方法が複数あることがあります。予測可能性を向上させるために、以下の方法を使用してください。

散文形式の名前から始めます。

  1. フレーズをプレーンなASCIIに変換し、アポストロフィを削除します。例えば「Müller’s algorithm」は「Muellers algorithm」になります。
  2. この結果をスペースと残りの句読点(主にハイフン)で単語に分割します。
    推奨:
    一般的に使用されている単語の中に従来のキャメルケースの単語がある場合は、これを構成部分に分割します(例: "AdWords"→"ad words")

    「iOS」などの単語はそれ自体はキャメルケースではありません。この場合は他のどのような慣習にも反するので、このルールは適用されません。
  3. 頭字語を含む、全ての文字を小文字にしてから、次のいずれかを実行します
    • パスカルケースにする場合は、各単語の最初の文字を大文字にする
    • キャメルケースにする場合は、最初の単語を除いて、各単語の最初の文字を大文字にする
  4. 最後に、すべての単語を1つの識別子に結合します

元の単語の大文字と小文字の区別はほとんど無視されることに注意してください。

元の単語 正解 間違い
XML Http Request XmlHttpRequest XMLHTTPRequest
new customer ID newCustomerId newCustomerID
inner stopwatch innerStopwatch innerStopWatch
supports IPv6 on iOS supportsIpv6OnIos supportsIPv6OnIOS
YouTube importer YouTubeImporter YoutubeImporter 1

いくつかの単語は英語では曖昧なハイフンで連結されています。例えば、"noempty"と"non-empty"はどちらも正しい英語です。よってメソッド名もcheckNoemptycheckNonEmptyのどちらも正しいです。

ドキュメンテーション

書式

KDocブロックの基本的な書式はこのような形です

/**
 * Multiple lines of KDoc text are written here,
 * wrapped normally…
 */
fun method(arg: String) {
    // …
}

またはこのような1行で書くことができます

/** An especially short bit of KDoc. */

この基本形は常に受け入れ可能です。KDocブロック全体(コメントマーカーを含む)が1行におさまる場合は、単一行形式に置き換えることができます。単一行形式は@returnのようなブロックタグがない場合にのみ使えることに注意してください。

段落

1行の空白行(最初のアスタリスク(*)のみを含む行)は段落の区切り・サマリーフラグメントとブロックタグの間に書かれます。

ブロックタグ

ブロックタグは @constructor, @receiver, @param, @property, @return, @throws, @seeの順番で書かれます。説明文が空の場合は表示されません。ブロックタグが1行におさまらない場合は、2行目以降は@の位置からスペース4つインデントします。

サマリーフラグメント

KDocブロックは簡単な要約を書くフラグメントで始まります。このフラグメントは非常に重要です。ドキュメントでのクラスやメソッドの目次などの特定のコンテキストで現れる唯一のテキストです。
これは名詞句または動詞句の断片で、完全な分ではありません。Afoois a...This method returns...で始まっているわけでも、Save the record.のような完全な命令文を形成する必要もありません。ただし、フラグメントは大文字・小文字が区別され、完全文と同様に読まれます。

使い方

最低でも、KDocは全てのpublicな型、以下に述べるいくつかの例外を除いたpublicprotectedなメンバーに関して書く必要があります。

例外: 自明な関数
getFooやそのようなプロパティに代表される、単純で明白な関数
典型的な読者が知る必要があるかもしれない関連情報を省略することを正当化するためにこの例外を用いることは適切ではありません。たとえばgetCanonicalName関数やcanonicalNameプロパティの場合、一般的な読者が「CanonicalName」という用語が何を意味するか分からない場合は、そのドキュメントを省略したり、/** Returns the canonical name. */などのKDocを書かないでください。

例外: オーバーライド
スーパータイプメソッドをオーバーライドするメソッドに常にKDocがあるわけではありません。


  1. 許容されますが推奨されません 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2019年5月】{N} + ng + Firebase : 準備編

NativeScript + Angular で Firebase をバックエンドにしてみたい。

先月の記事 【2019年4月版】NativeScript+Angularで感じた新時代{N} + ng = ❤ に入門いたしましたが、バックエンドに Firebase 使えないのか?!ということで調べてみました。

あっさりと以下のように Firebase プラグインを発見いたしました。これだけでいけるのか?!

また以下の記事で NativeScript での Firebase 利用についてさっくりとご紹介いただいておりました。

これはいろいろと夢が膨らみますね!

さっそく手元で実際の構築手順を再現してみました。

いろいろ手間取りましたので本日は "Firebaseに接続する" ところまでの「準備編」といたします。

対象環境

OS : macOS High Sierra
Android SDK : platform 27 revision 3
実機 : Android 7.1.1
NativeScript-Angular : 7.2.0

前回とは違い、今回は macOS での構築です。

環境の構築手順

NativeScript のインストール

{N} のインストールには以下の公式サイトの構築手順に従ってセットアップを進めて行けばOKです。

yarn の対応はイマイチのようなので諦めて npm を使用します。

$ npm install -g nativescript

ここでは紹介しませんが以下のバージョンの java や Android SDK のインストールをお願いします。

  • JDK 8
  • Android SDK
  • Android Support Repository
  • Google Repository
  • Android SDK Build-tools 28.0.3 or a later stable official release
  • Android Studio

注意点は JDK 8Android SDK Build-tools 28.0.3 ですね。バージョンは確認しておきましょう。

またインストール中に iOS 用に xcode のインストールをしつこく勧めてきますが、Android だけの場合は Skip Step and Configure Manually で飛ばしてOKです。

念のため、以下のコマンドで Android SDK などが正しくインストールされているか確認しましょう。

$ tns doctor

プロジェクトの作成

以下のコマンドでプロジェクトを作成します。

$ tns create HelloWorld --template tns-template-blank-ng

生成される package.json の idorg.nativescript.HelloWorld とあると思います。

これがこのアプリケーションのIDとなります。

firebase のプロジェクト準備

firebase でのプロジェクト作成と、"Android" 用の gooogle-services.json の取得、app.gradle の修正をFirebaseの案内手順通りに行います。
上記のアプリケーションのIDを貼り付けてください。

  1. gooogle-services.jsonHelloWorld/App_Resources/Android/google-services.json に保存します。
  2. app.gradle を以下のように修正します。
app.gradle
dependencies {
  implementation 'com.google.firebase:firebase-core:16.0.1'
}

android {
  defaultConfig {
    minSdkVersion 17
    generatedDensities = []
  }
  aaptOptions {
    additionalParameters "--no-version-vectors"
  }
  project.ext {
    googlePlayServicesVersion = "+"
  }
}

apply plugin: 'com.google.gms.google-services'

具体的には dependenciesの追加、googlePlayServicesVersion = "+"の追加、"apply plugin:" の追加を行なっています。

firebase プラグインのインストール

以下の Readme のセットアップ手順でだいたいOKです。

肝は Firebase の google-services.json の準備が整ってから、firebase プラグインをインストールする、という点でしょう。
以下のコマンドで firebase プラグインをインストールします。

$ tns plugin add nativescript-plugin-firebase

ここで firebase の様々な機能について 有効にするか? と聞いてきます。
お好みに応じて y としておきましょう。

以上でプロジェクトの準備は完了です。

実装手順

今回は firebase への接続を確認するだけなので、app.component.ts を以下のように修正するだけです。

app.component.ts
import { Component, OnInit } from "@angular/core";

const firebase = require("nativescript-plugin-firebase");

@Component({
    moduleId: module.id,
    selector: "ns-app",
    templateUrl: "app.component.html"
})
export class AppComponent {
  // using async-await just for show
  ngOnInit() {
    try {
      firebase.init({
        persist: false
      });
      console.log(">>>>> Firebase initialized");
    } catch (err) {
      console.log(">>>>> Firebase init error: " + err);
    }
  }
}

これでアプリケーションの起動時に、コンソールのログで >>>>> Firebase initialized と出力されればまず、第一関門突破です。

いつものように preview ・・・?

AppComponent の生成がうまくいくか確かめたいので、例によってプレビューアプリで確認してみます。

$ tns preview

ところが起動時にエラーが発生して {N} Preview アプリにログが表示されてしまいます。。。

ログメッセージでググって見た結果、firebase plugin は {N} preview のアプリには含まれていないので、 tns preview コマンドでは動かないとのこと。。。

Like I said: it's not part of the Preview app, so you can't use that.

・・・はい。先に言ってね〜

Android 実機でのデバッグ

というわけで実機で動かす必要ありです。以下のコマンドのヘルプを参照しまして・・・

https://docs.nativescript.org/tooling/docs-cli/project/testing/debug-android

USB で実機を接続後、

$ tns device android

Connected devices & emulators
Searching for devices...
┌───┬─────────────┬──────────┬───────────────────┬────────┬───────────┐
│ # │ Device Name │ Platform │ Device Identifier │ Type   │ Status    │
│ 1 │ XXXXXXXX    │ Android  │ J5AXHM00S6047MX   │ Device │ Connected │
└───┴─────────────┴──────────┴───────────────────┴────────┴───────────┘

で認識されているか確認。Device Identifier をコピーして以下のコマンドで貼り付けます。

$ tns debug android --device J5AXHM00S6047MX

しばらくビルドが進んでおりましたが・・・以下のようにエラーが発生しました。

* What went wrong:
Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: 
  The number of method references in a .dex file cannot exceed 64K.
  Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html

・・・はい、ということで app.gradle を修正します。

app.gradle
android {
  defaultConfig {
    minSdkVersion 17
    generatedDensities = []
    multiDexEnabled true
  }
  aaptOptions {
    additionalParameters "--no-version-vectors"
  }
  project.ext {
    googlePlayServicesVersion = "+"
  }
}

multiDexEnabled true を追記いたしました。

再度、気を取り直して・・・

$ tns debug android --device J5AXHM00S6047MX
...
Project successfully built.
Installing on device J5AXHM00S6047MK...
Successfully installed on device with identifier 'J5AXHM00S6047MX'.
Restarting application on device J5AXHM00S6047MX...
JS: >>>>> Firebase initialized
JS: Angular is running in the development mode. Call enableProdMode() to enable the production mode.
device: J5AXHM00S6047MX debug port: 40000

To start debugging, open the following URL in Chrome:
chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:40000

Successfully synced application org.nativescript.HelloWorld on device J5AXHM00S6047MX.

動いたー!!!
いろいろありましたがついに >>>>> Firebase initialized のコメントを拝むことができました!!
これで firebase オブジェクトが使用可能になった模様です!

初回のビルドは例によって非常に時間かかります。その間、気長にお待ちください。
(私は実機の接続が必須と知ってから Android SDK 28 をインストールし始めたのでさらに時間がかかってしまいました。。。)

引き続き {N} + Firebase の開発で firebase の各種機能についていろいろ試してみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlin Coroutinesで非同期並列処理をする

非同期な処理を並列させて、それぞれが完了するまで待ち合わせます。onClick() を実行すると処理が始まります。

コード

並列処理の結果を使って処理を行います。

MainActivityViewModel.kt
class MainActivityViewModel : ViewModel(), LifecycleObserver {

    private val job = Job()
    private val scope = CoroutineScope(Dispatchers.Main + job)

    fun onClick() {
        scope.launch(Dispatchers.IO) {

            val t1 = async { task1() }
            val t2 = async { task2() }

            Timber.d("onComplete: %d", t1.await() + t2.await())
        }
    }

    private fun task1(): Int {
        Timber.d("task 1 START")
        Thread.sleep(2000)
        Timber.d("task 1 END")
        return 1
    }

    private fun task2(): Int {
        Timber.d("task 2 START")
        Thread.sleep(3000)
        Timber.d("task 2 END")
        return 2
    }

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }

実行結果

task 1 START
task 2 START
task 1 END
task 2 END
onComplete: 3

RxKotlin版もありますので参考にしてください。状況に応じてRxとcoroutinesを使い分けると良いでしょう。
RxKotlinで非同期並列処理をする - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WSLからadb接続する際にバージョンを揃えるのが面倒じゃない?

経緯

  • WSLからadbを使用する際に,Windows側のadbとのバージョンを揃えるのが面倒だった.
  • また,Windows側かWSL側のどちらかのadbを更新してしまうと,片方も更新する手間があり,更に面倒.

回避策

  • WSL側でWindows側のadb.exeを使う
adb.exe [command]
  • 毎回adb.exe と打つのは面倒なので,aliasを追加 ( 場所は.bashrcとかお好みで)
alias adb='adb.exe'
  • これでわざわざ揃えなくても,adbが使える!
  • ただし,wslでadbをinstallしている場合は,Removeしておいたほうがよかです.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RxKotlinで非同期並列処理をする

非同期な処理を並列させて、それぞれが完了するまで待ち合わせます。onClick() を実行すると処理が始まります。

並列処理をただ待ち合わせる場合(戻り値不要)

mergeオペレータを使います。アイテムは何も流さずにonCompleteで処理を行います。

MainActivityViewModel.kt
class MainActivityViewModel : ViewModel(), LifecycleObserver {

    private val compositeDisposable = CompositeDisposable()

    fun onClick() {
        Observable.merge(task1(), task2())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({}, {}, { 
                Timber.d("onComplete") 
                // do something...
            })
            .addTo(compositeDisposable)
    }

    private fun task1(): Observable<Unit> {
        return Completable.fromAction {
            Timber.d("task 1 START")
            Thread.sleep(2000)
            Timber.d("task 1 END")
        }.subscribeOn(Schedulers.io()).toObservable()
    }

    private fun task2(): Observable<Unit> {
        return Completable.fromAction {
            Timber.d("task 2 START")
            Thread.sleep(3000)
            Timber.d("task 2 END")
        }.subscribeOn(Schedulers.io()).toObservable()
    }

    override fun onCleared() {
        super.onCleared()
        compositeDisposable.clear()
    }
}

実行結果

task 1 START
task 2 START
task 1 END
task 2 END
onComplete

並列処理の結果を使う場合

zipオペレータを使います。

MainActivityViewModel.kt
class MainActivityViewModel : ViewModel(), LifecycleObserver {

    private val compositeDisposable = CompositeDisposable()

    fun onClick() {
        Observables.zip(task1(), task2()) { t1, t2 ->  t1 + t2 }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { 
                Timber.d("onComplete: %d", it)
                // do something...
            }
            .addTo(compositeDisposable)
    }

    private fun task1(): Observable<Int> {
        return Single.create<Int> { emitter ->
            Timber.d("task 1 START")
            Thread.sleep(2000)
            Timber.d("task 1 END")
            emitter.onSuccess(1)
        }.subscribeOn(Schedulers.io()).toObservable()
    }

    private fun task2(): Observable<Int> {
        return Single.create<Int> { emitter ->
            Timber.d("task 2 START")
            Thread.sleep(3000)
            Timber.d("task 2 END")
            emitter.onSuccess(2)
        }.subscribeOn(Schedulers.io()).toObservable()
    }

    override fun onCleared() {
        super.onCleared()
        compositeDisposable.clear()
    }
}

実行結果

task 1 START
task 2 START
task 1 END
task 2 END
onComplete: 3

RxKotlinでzipを使う場合は Observable ではなくて Observables を使うと BiFunction<> を省略することができます。

Kotlin Coroutines版もありますので参考にしてください。状況に応じてRxとcoroutinesを使い分けると良いでしょう。
Kotlin Coroutinesで非同期並列処理をする - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Q (beta) 挙動の変更 (targetSdkの指定によらない全appへの挙動の変更) 抄訳

https://developer.android.com/preview/behavior-changes-all
の抄訳です。

#途中

挙動の変更 : 全アプリ (Behavior changes: all apps)

The Android Q platform includes behavior changes that may affect your app. The following behavior changes apply to all apps when they run on Android Q, regardless of 'targetSdkVersion'. You should test your app and then modify it as needed to support these properly, where applicable.

Android Qプラットフォームは、あなたのアプリに影響を与えるかもしれない挙動の変更を含みます。以下の挙動の変化は targetSdkversion によらず Android Q上で動作するすべてのアプリに適用されるものです。

Make sure to also review the list of behavior changes that only affect apps targeting Android Q.

Android Qをtargetにするアプリにだけ影響する挙動の変更のリストもレビューも行ってください。

Note: In addition to the changes listed on this page, Android Q introduces a large number of changes and restrictions to enhance user privacy. To learn more, see the Privacy page.
Non-SDK interface restrictions

注意:このページの変更のリストに加え、Android Qは、多くの変更や制限を、ユーザーのプライバシーの拡張のために導入します。詳細は、プライバシーのページのNon-Sdkインターフェイスの制限を参照してください。

To help ensure app stability and compatibility, the platform started restricting which non-SDK interfaces your app can use in Android 9 (API level 28). Android Q includes updated lists of restricted non-SDK interfaces based on collaboration with Android developers and the latest internal testing. Our goal is to make sure that public alternatives are available before we restrict non-SDK interfaces.

アプリの安定性と互換性の担保のために、プラットフォームは、Android 9 (APIレベル29)上であなたがアプリを動作させるときに、非SDKインターフェイスへの制限を開始しました。Android Qは、Android開発者との協力と最新の内部テストに基づく、更新された非SDKインターフェイスのリストをもちます。私達のゴールは、非SDKインターフェイスの制限よりも前にだいたい公開インターフェイスを用意することです。

If you will not be targeting Android Q, some of these changes might not immediately affect you. However, while you can currently use non-SDK interfaces that are part of the greylist (depending on your app's target API level), using any non-SDK method or field always carries a high risk of breaking your app.

もしあなたがAndroid QをtargetSdkで指定しなければ、これらの変更のいくつかは、すぐには影響しないかもしれません。しかし、(targetSdkVersionに依存しますが)グレーリスト内の非Sdkインターフェースを現在利用している間は、どの非SDKメソッドやフィールドの利用は、常にあなたのアプリを壊すリスクがあります。

If you are unsure if your app uses non-SDK interfaces, you can test your app to find out. If your app relies on non-SDK interfaces, you should begin planning a migration to SDK alternatives. Nevertheless, we understand that some apps have valid use cases for using non-SDK interfaces. If you cannot find an alternative to using a non-SDK interface for a feature in your app, you should request a new public API.

もしあなたが、あなたのアプリが非SDKインターフェイスの利用をしているのか不確かであれば、あなたはあなたのアプリをテストして見つけることができます。もしあなたのアプリが非SDKインターフェイスに依存しているのであれば、あなたはSDK代替インターフェースへの移行の計画をするのをはじめるべきです。もちろん、いくつかのアプリは、非SDKインターフェイスを正当な利用をしていることは知っています。もし、非SDKインターフェイスが提供している機能に対する代替を見つけることができないならば、新しい公開APIをリクエストすべきです。

To learn more, see Updates to non-SDK interface restrictions in Android Q and see Restrictions on non-SDK interfaces.

詳細は、Android Qでの非SDKインターフェイス制限やその更新を参照してください。

NDK

Android Q includes the following NDK changes.

Shared objects cannot contain text relocations
Android 6.0 (API level 23) disallowed use of the use of text relocations in shared objects. Code must be loaded as-is, and must not be modified. This change improves app load times and security.

In Android Q Beta 1 and 2, SELinux enforces this restriction on apps targeting Android 8.0 (API level 26) and higher. If such apps continue to use shared objects that contain text relocations, they are at high risk of breaking.

Changes to Bionic libraries and dynamic linker paths
Starting in Android Q, several paths are no longer regular files but symbolic links. Apps that have been relying on the paths being regular files might break:

/system/lib/libc.so -> /apex/com.android.runtime/lib/bionic/libc.so
/system/lib/libm.so -> /apex/com.android.runtime/lib/bionic/libm.so
/system/lib/libdl.so -> /apex/com.android.runtime/lib/bionic/libdl.so
/system/bin/linker -> /apex/com.android.runtime/bin/linker
These changes apply to the 64-bit variants of the file as well, with lib/ replaced with lib64/.

For compatibility, new symlinks are provided at the old paths, for example /system/lib/libc.so is now a symlink to /apex/com.android.runtime/lib/bionic/libc.so, and so on. So dlopen(“/system/lib/libc.so”) continues to work, but apps will find the difference when they actually try to examine the loaded libraries by reading /proc/self/maps or similar, which is not usual but we’ve found that some apps do that as part of their anti-hacking process. If so, the new /apex/… paths should be added as valid paths for the Bionic files.

System binaries/libraries mapped to execute-only memory
Starting in Android Q, executable segments of system binaries and libraries are mapped into memory execute-only (non-readable) as a hardening technique against code-reuse attacks. Intentional and unintentional reads into memory segments marked as execute-only will throw a SIGSEGV -- whether from bug, vulnerability, or intentional memory introspection.

You can identify whether a crash was caused by this change by examining the related tombstone file in /data/tombstones/. An execute-only related crash will contain the following abort message:

Cause: execute-only (no-read) memory access error; likely due to data in .text.

To work around this issue, developers may mark execute-only segments as read+execute by calling mprotect(), for example to perform memory inspection. However, we strongly recommend setting it back to execute-only afterwards as this provides better protection for your app and users.

Calls to ptrace are unaffected, so ptrace debugging is not impacted.

Security
Android Q includes the following security change.

Removed execute permission for app home directory
Untrusted apps that target Android Q can no longer invoke exec() on files within the app's home directory. This execution of files from the writable app home directory is a W^X violation. Apps should load only the binary code that's embedded within an app's APK file.

In addition, apps that target Android Q cannot in-memory modify executable code from files which have been dlopen()ed. This includes any shared object (.so) files with text relocations.

Wi-Fi Direct broadcasts
On Android Q, the following broadcasts related to Wi-Fi Direct are no longer sticky:

WIFI_P2P_CONNECTION_CHANGED_ACTION
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
If your app has relied on receiving these broadcasts at registration because they had been sticky, use the appropriate get() method at initialization to obtain the information instead.

Wi-Fi Aware capabilities
Android Q adds support to ease TCP/UDP Socket creation using Wi-Fi Aware datapaths. To create a TCP/UDP socket connecting to a ServerSocket, the client device needs to know the IPv6 address and port of the server. This previously needed to be communicated out-of-band, such as by using BT or Wi-Fi Aware layer 2 messaging, or discovered in-band using other protocols, such as mDNS. With Android Q, the information can be communicated as part of the network setup.

The server can do either of the following:

Initialize a ServerSocket and either set or obtain the port to be used.
Specify the port information as part of the Wi-Fi Aware network request.
The following code sample shows how to specify port information as part of the network request:

KOTLIN
JAVA
val ss = ServerSocket()
val ns = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
.setPskPassphrase("some-password")
.setPort(ss.localPort)
.build()

val myNetworkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(ns)
.build()

The client then performs a Wi-Fi Aware network request to obtain the IPv6 and the port supplied by the server:

KOTLIN
JAVA

val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
...
}

override fun onLinkPropertiesChanged(network: Network,
linkProperties: LinkProperties) {
...
}

override fun onCapabilitiesChanged(network: Network,
networkCapabilities: NetworkCapabilities) {
...
val ti = networkCapabilities.transportInfo
if (ti is WifiAwareNetworkInfo) {
val peerAddress = ti.peerIpv6Addr
val peerPort = ti.port
}
}
override fun onLost(network: Network) {
...
}
};

connMgr.requestNetwork(networkRequest, callback)

SYSTEM_ALERT_WINDOW on Go devices
Apps running on Android Q (Go edition) devices cannot receive the SYSTEM_ALERT_WINDOW permission. This is because drawing overlay windows uses excessive memory, which is particularly harmful to the performance of low-memory Android devices.

If an app running on a Go edition device running Android 9 or lower receives the SYSTEM_ALERT_WINDOW permission, the app retains the permission even if the device is upgraded to Android Q. However, apps which do not already have that permission cannot be granted it after the device is upgraded.

If an app on a Go device sends an intent with the action ACTION_MANAGE_OVERLAY_PERMISSION, the system automatically denies the request, and takes the user to a Settings screen which says that the permission isn't allowed because it slows the device. If an app on a Go device calls Settings.canDrawOverlays(), the method always returns false. Again, these restrictions do not apply to apps which received the SYSTEM_ALERT_WINDOW permission before the device was upgraded to Q.

Warnings for apps targeting older Android versions
In Android Q, the platform will warn users the first time they run any app that targets a platform version lower than Android 6.0 (API level 23). If the app requires the user to grant permissions, the user is also given an opportunity to adjust the app's permissions before the app is allowed to run for the first time.

Due to Google Play's target API requirements, a user would only see these warnings if they run an app that has not been updated recently. For apps that are distributed through other stores, similar target API requirements are also being introduced in 2019. For more information about these requirements, see Expanding target API level requirements in 2019.

SHA-2 CBC cipher suites removed
The following SHA-2 CBC cipher suites have been removed from the platform:

TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
These cipher suites are less secure than the similar cipher suites that use GCM, and most servers either support both the GCM and CBC variants of these cipher suites or support neither of them.

Note: Apps and libraries should intersect their desired set of cipher suites with the return value from getSupportedCipherSuites() to future-proof cipher suite selection against future removals.
App usage
Android Q introduces the following behavior changes related to app usage:

UsageStats app usage improvements -- Android Q now accurately tracks app usage with UsageStats when apps are used in split-screen or picture-in-picture mode. Additionally, Android Q can now track instant app usage.

Per-app grayscale -- Android Q can now set apps to a grayscale display mode.

Per-app distraction state -- Android Q can now selectively set apps to a "distraction state" where their notifications are suppressed and they will not appear as suggested apps.

Suspension and playback -- In Android Q, suspended apps are no longer able to play audio.

android.preference library is now deprecated
The android.preference library is now deprecated. Developers should instead use the AndroidX preference library, part of Android Jetpack. For additional resources to aid in migration and development, check out the updated Settings Guide along with our public sample app and reference documentation.

Camera changes
Many camera-using apps assume that if the device is in a portrait configuration, then the physical device is also in the portrait orientation, as described in Camera orientation. This was a safe assumption in the past, but that has changed with the introduction of new form factors, such as foldables. That assumption on these devices can lead to either incorrectly rotated or scaled (or both) display of the camera viewfinder.

Applications that target API level 24 or above should explicitly set android:resizeableActivity and provide necessary functionality to handle multi-window operation.

Battery usage tracking
Beginning with Android Q, SystemHealthManager resets its battery usage statistics whenever the device is unplugged after a major charging event. Broadly speaking, a major charging event is either: The device has been fully charged, or the device has gone from mostly depleted to mostly charged.

Before Android Q, the battery usage statistics reset whenever the device was unplugged, no matter how little change there had been to the battery level.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidタブレットにUbuntuをインストールする

はじめに

タイトルは大仰ですが,UserLAndとXServer XSDLを突っ込んでUbuntu環境を作ろうというお話です.

今回は手持ちのZenPad 3S 10にインストールしてみました.

アプリのインストール

とりあえずUserLAndXServer XSDLをインストールしましょう.

あと,Hacker's Keyboardもあると便利でしょう.Bluetoothキーボードなどを使う場合には,ハードウェアキーボード配列変更 (+親指Ctrl) [英語配列]をインストールしておくと,CtrlとCaps Lockキーの入れ替えなどが設定できて便利です.

UserLAndの設定

UserLAndを起動すると,アプリの一覧が表示されます.

Screenshot_20190510-170113.jpg

今回は[Distribution]から[Ubuntu]を選択します.

Screenshot_20190510-170708.jpg

Usernameとパスワードを2つ決めて[CONTINUE]を押します.

Screenshot_20190511-161217.jpg

システムへの接続方法を決めます.ここではSSHにしておきます.[CONTINUE]を押すとファイル等の展開が始まりますので,しばらく待ちます.

Screenshot_20190511-161906.jpg

セットアップが終了するとログインブロンプトが表示されますので,先ほど決めたパスワードでログインしましょう.

Ubuntuの設定

Ubuntu 自体を使いやすくするために設定していきます.まずはシステムを最新の状態に更新します.

sudo apt update 
sudo apt upgrade 

途中で続けるかどうかの確認がありますので「y」を押します.

終わったら,必要なアプリケーションをインストールしていきます.まずは何はなくともtcshをインストールして,ログインシェルを変更しましょう.

sudo apt install tcsh
which tcsh 

どうやら/usr/binにインストールされているようなので注意しましょう.

chsh 
/usr/bin/tcsh 

エディタはee…が使えないので,nanoとかいうのをインストールすることにします.調べてみるとnano-tinyというパッケージがあるようです.

sudo apt search nano 
sudo apt install nano-tiny 

大物パッケージをインストールしていきます.

sudo apt install emacs fonts-not-cjk git uim-mozc xfce4 

XServerの設定

Xfce4を使うためにはXServerの設定をしなければ行けません.インストールしてあるXServer XSDLを実行します.

Screenshot_20190511-164125.jpg

画面の指示に従って,DISPLAYとPULSE_SERVERという環境変数を指定します.

setenv DISPLAY :0
setenv PULSE_SERVER tcp:127.0.0.1:4712

Xfce4を実行します.

startxfce4

Screenshot_20190511-165831.jpg

うまく表示されました.

Screenshot_20190511-172455.jpg

しかし,フォントが小さいので使いにくそうですね.TerminalもXfce4-terminalがインストールされていないようです.

sudo apt install xfce4-terminal

Screenshot_20190512-004548.jpg

メニューの[Setting] - [Appearance] - [Font]からフォントサイズを変更してやることでだいぶ見やすくなりました.ここまでくれば,後はgitで設定ファイルを持ってくるなり何なりでどうとでもできますね.

おわりに

ということで,しばらくUbuntuでLinuxの勉強をしてみようかと思います.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む