First kotlin
Convert one class to kotlin and provide a slight amount of guidance.
Test: existing tests
Change-Id: Ie8659765b674ac7b2d82ed3d343f387195c07d83
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index ae42882..fbbc2a3 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -15,3 +15,5 @@
strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT}
hidden_api_txt_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
+
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c9ba268..0913503 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -32,6 +32,7 @@
android_library {
name: "SystemUI-core",
srcs: [
+ "src/**/*.kt",
"src/**/*.java",
"src/**/I*.aidl",
],
@@ -73,6 +74,59 @@
],
}
+android_library {
+ name: "SystemUI-tests",
+ manifest: "tests/AndroidManifest.xml",
+ resource_dirs: [
+ "tests/res",
+ "res-keyguard",
+ "res",
+ ],
+ srcs: [
+ "tests/src/**/*.kt",
+ "tests/src/**/*.java",
+ "src/**/*.kt",
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ],
+ static_libs: [
+ "SystemUIPluginLib",
+ "SystemUISharedLib",
+ "SettingsLib",
+ "androidx.car_car",
+ "androidx.legacy_legacy-support-v4",
+ "androidx.recyclerview_recyclerview",
+ "androidx.preference_preference",
+ "androidx.appcompat_appcompat",
+ "androidx.mediarouter_mediarouter",
+ "androidx.palette_palette",
+ "androidx.legacy_legacy-preference-v14",
+ "androidx.leanback_leanback",
+ "androidx.slice_slice-core",
+ "androidx.slice_slice-view",
+ "androidx.slice_slice-builders",
+ "androidx.arch.core_core-runtime",
+ "androidx.lifecycle_lifecycle-extensions",
+ "SystemUI-tags",
+ "SystemUI-proto",
+ "metrics-helper-lib",
+ "android-support-test",
+ "mockito-target-inline-minus-junit4",
+ "testables",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.runner",
+ "telephony-common",
+ "android.car",
+ "android.test.base",
+ ],
+ aaptflags: [
+ "--extra-packages",
+ "com.android.keyguard:com.android.systemui",
+ ],
+}
+
android_app {
name: "SystemUI",
static_libs: [
diff --git a/packages/SystemUI/docs/kotlin-in-sysui.md b/packages/SystemUI/docs/kotlin-in-sysui.md
new file mode 100644
index 0000000..1bf24f6
--- /dev/null
+++ b/packages/SystemUI/docs/kotlin-in-sysui.md
@@ -0,0 +1,22 @@
+# Kotlin in SystemUI
+
+Queue "it's happening" gif.
+
+Kotlin is probably going to be a bit of a wild west for a while, but please
+try to follow these guidelines as much as possible.
+
+ - No semi-colons: they are optional, we probably don't want them in the
+ future, so let's just not add them.
+ - No DSLs: sysui is complicated enough as is, let's not add more layers at
+ the moment.
+ - Only use extension functions for keeping complex code locality: Don't use
+ extension functions to add methods to android classes that you always wished
+ were there, instead add them directly to the class and save us the extension.
+ - inline, reified, and de-compisition can all be great things: just make sure
+ you know what they do and why you are using them.
+
+# Recommended reading
+
+ - [Kotlin](https://kotlinlang.org/)
+ - [AndroidX-KTX](https://www.youtube.com/watch?v=st1XVfkDWqk)
+ - [Performance and Kotlin tricks](https://www.youtube.com/watch?v=6P20npkvcb8)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
deleted file mode 100644
index 9d40f17..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.content.om.IOverlayManager;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.os.LocaleList;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-
-import com.android.systemui.ConfigurationChangedReceiver;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
-public class ConfigurationControllerImpl implements ConfigurationController,
- ConfigurationChangedReceiver {
-
- private final ArrayList<ConfigurationListener> mListeners = new ArrayList<>();
- private final Configuration mLastConfig = new Configuration();
- private int mDensity;
- private float mFontScale;
- private boolean mInCarMode;
- private int mUiMode;
- private LocaleList mLocaleList;
-
- public ConfigurationControllerImpl(Context context) {
- Configuration currentConfig = context.getResources().getConfiguration();
- mFontScale = currentConfig.fontScale;
- mDensity = currentConfig.densityDpi;
- mInCarMode = (currentConfig.uiMode & Configuration.UI_MODE_TYPE_MASK)
- == Configuration.UI_MODE_TYPE_CAR;
- mUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
- mLocaleList = currentConfig.getLocales();
- }
-
- @Override
- public void notifyThemeChanged() {
- ArrayList<ConfigurationListener> listeners = new ArrayList<>(mListeners);
-
- listeners.forEach(l -> {
- if (mListeners.contains(l)) {
- l.onThemeChanged();
- }
- });
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- // Avoid concurrent modification exception
- ArrayList<ConfigurationListener> listeners = new ArrayList<>(mListeners);
-
- listeners.forEach(l -> {
- if (mListeners.contains(l)) {
- l.onConfigChanged(newConfig);
- }
- });
- final float fontScale = newConfig.fontScale;
- final int density = newConfig.densityDpi;
- int uiMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
- boolean uiModeChanged = uiMode != mUiMode;
- if (density != mDensity || fontScale != mFontScale
- || (mInCarMode && uiModeChanged)) {
- listeners.forEach(l -> {
- if (mListeners.contains(l)) {
- l.onDensityOrFontScaleChanged();
- }
- });
- mDensity = density;
- mFontScale = fontScale;
- }
-
- final LocaleList localeList = newConfig.getLocales();
- if (!localeList.equals(mLocaleList)) {
- mLocaleList = localeList;
- listeners.forEach(l -> {
- if (mListeners.contains(l)) {
- l.onLocaleListChanged();
- }
- });
- }
-
- if (uiModeChanged) {
- mUiMode = uiMode;
- listeners.forEach(l -> {
- if (mListeners.contains(l)) {
- l.onUiModeChanged();
- }
- });
- }
-
- if ((mLastConfig.updateFrom(newConfig) & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
- listeners.forEach(l -> {
- if (mListeners.contains(l)) {
- l.onOverlayChanged();
- }
- });
- }
- }
-
- @Override
- public void addCallback(ConfigurationListener listener) {
- mListeners.add(listener);
- listener.onDensityOrFontScaleChanged();
- }
-
- @Override
- public void removeCallback(ConfigurationListener listener) {
- mListeners.remove(listener);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
new file mode 100644
index 0000000..81b596c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
+import android.os.LocaleList
+
+import com.android.systemui.ConfigurationChangedReceiver
+import com.android.systemui.statusbar.policy.ConfigurationController
+
+import java.util.ArrayList
+
+class ConfigurationControllerImpl(context: Context)
+ : ConfigurationController, ConfigurationChangedReceiver {
+
+ private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList()
+ private val lastConfig = Configuration()
+ private var density: Int = 0
+ private var fontScale: Float = 0.toFloat()
+ private val inCarMode: Boolean
+ private var uiMode: Int = 0
+ private var localeList: LocaleList? = null
+
+ init {
+ val currentConfig = context.resources.configuration
+ fontScale = currentConfig.fontScale
+ density = currentConfig.densityDpi
+ inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
+ Configuration.UI_MODE_TYPE_CAR
+ uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
+ localeList = currentConfig.locales
+ }
+
+ override fun notifyThemeChanged() {
+ val listeners = ArrayList(listeners)
+
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onThemeChanged()
+ }
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ // Avoid concurrent modification exception
+ val listeners = ArrayList(listeners)
+
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onConfigChanged(newConfig)
+ }
+ val fontScale = newConfig.fontScale
+ val density = newConfig.densityDpi
+ val uiMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
+ val uiModeChanged = uiMode != this.uiMode
+ if (density != this.density || fontScale != this.fontScale ||
+ inCarMode && uiModeChanged) {
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onDensityOrFontScaleChanged()
+ }
+ this.density = density
+ this.fontScale = fontScale
+ }
+
+ val localeList = newConfig.locales
+ if (localeList != this.localeList) {
+ this.localeList = localeList
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onLocaleListChanged()
+ }
+ }
+
+ if (uiModeChanged) {
+ this.uiMode = uiMode
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onUiModeChanged()
+ }
+ }
+
+ if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onOverlayChanged()
+ }
+ }
+ }
+
+ override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
+ listeners.add(listener)
+ listener.onDensityOrFontScaleChanged()
+ }
+
+ override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
+ listeners.remove(listener)
+ }
+}
+
+// This could be done with a Collection.filter and Collection.forEach, but Collection.filter
+// creates a new array to store them in and we really don't need that here, so this provides
+// a little more optimized inline version.
+inline fun <T> Collection<T>.filterForEach(f: (T) -> Boolean, execute: (T) -> Unit) {
+ forEach {
+ if (f.invoke(it)) {
+ execute.invoke(it)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 9ee5532..6057614 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -21,28 +21,12 @@
LOCAL_JACK_FLAGS := --multi-dex native
LOCAL_DX_FLAGS := --multi-dex
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/..
-LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
-
LOCAL_PACKAGE_NAME := SystemUITests
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
- $(call all-Iaidl-files-under, src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
LOCAL_STATIC_ANDROID_LIBRARIES := \
- SystemUI-core
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- metrics-helper-lib \
- android-support-test \
- mockito-target-inline-minus-junit4 \
- testables \
- truth-prebuilt \
+ SystemUI-tests
LOCAL_MULTILIB := both
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 64f96da..8b1324a 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -72,6 +72,7 @@
tools:replace="android:authorities"
android:authorities="${applicationId}.lifecycle-tests"
android:exported="false"
+ android:enabled="false"
android:multiprocess="true" />
<provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
android:authorities="com.android.systemui.test.keyguard.disabled"
@@ -83,6 +84,7 @@
android:name="androidx.core.content.FileProvider"
android:authorities="com.android.systemui.test.fileprovider"
android:exported="false"
+ android:enabled="false"
tools:replace="android:authorities"
android:grantUriPermissions="true" />
</application>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
new file mode 100644
index 0000000..0d13e97
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.support.test.filters.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ConfigurationControllerImplTest : SysuiTestCase() {
+
+ private val mConfigurationController =
+ com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext)
+
+ @Test
+ fun testThemeChange() {
+ val listener = mock(ConfigurationListener::class.java)
+ mConfigurationController.addCallback(listener)
+
+ mConfigurationController.notifyThemeChanged()
+ verify(listener).onThemeChanged()
+ }
+
+ @Test
+ fun testRemoveListenerDuringCallback() {
+ val listener = mock(ConfigurationListener::class.java)
+ mConfigurationController.addCallback(listener)
+ val listener2 = mock(ConfigurationListener::class.java)
+ mConfigurationController.addCallback(listener2)
+
+ doAnswer {
+ mConfigurationController.removeCallback(listener2)
+ null
+ }.`when`(listener).onThemeChanged()
+
+ mConfigurationController.notifyThemeChanged()
+ verify(listener).onThemeChanged()
+ verify(listener2, never()).onThemeChanged()
+ }
+}