First kotlin

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

Test: existing tests
Change-Id: Ie8659765b674ac7b2d82ed3d343f387195c07d83
This commit is contained in:
Jason Monk
2018-08-22 16:56:58 -04:00
parent d5796dd4f1
commit ae7ced2f95
8 changed files with 258 additions and 145 deletions

View File

@@ -15,3 +15,5 @@ api_lint_hook = ${REPO_ROOT}/frameworks/base/tools/apilint/apilint_sha.sh ${PREU
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}

View File

@@ -32,6 +32,7 @@ java_library {
android_library {
name: "SystemUI-core",
srcs: [
"src/**/*.kt",
"src/**/*.java",
"src/**/I*.aidl",
],
@@ -73,6 +74,59 @@ android_library {
],
}
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: [

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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)
}
}
}

View File

@@ -21,28 +21,12 @@ LOCAL_MODULE_TAGS := tests
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

View File

@@ -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>

View File

@@ -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()
}
}