First kotlin

Convert one class to kotlin and provide a slight amount of guidance.

Test: existing tests
Change-Id: Ie8659765b674ac7b2d82ed3d343f387195c07d83
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()
+    }
+}