Merge "Integrate system language filtering functionality" am: 2df94f9b15
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1362798 Change-Id: I98c76c6070490f970eed25a5233e1a3331959575
This commit is contained in:
committed by
Automerger Merge Worker
commit
db00e424d9
@@ -456,6 +456,7 @@ java_library {
|
|||||||
|
|
||||||
"com.android.sysprop.apex",
|
"com.android.sysprop.apex",
|
||||||
"com.android.sysprop.init",
|
"com.android.sysprop.init",
|
||||||
|
"com.android.sysprop.localization",
|
||||||
"PlatformProperties",
|
"PlatformProperties",
|
||||||
],
|
],
|
||||||
sdk_version: "core_platform",
|
sdk_version: "core_platform",
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.internal.app;
|
package com.android.internal.app;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.IActivityManager;
|
import android.app.IActivityManager;
|
||||||
import android.app.ListFragment;
|
import android.app.ListFragment;
|
||||||
@@ -28,6 +30,7 @@ import android.os.Bundle;
|
|||||||
import android.os.LocaleList;
|
import android.os.LocaleList;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.sysprop.LocalizationProperties;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -43,6 +46,9 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
public class LocalePicker extends ListFragment {
|
public class LocalePicker extends ListFragment {
|
||||||
private static final String TAG = "LocalePicker";
|
private static final String TAG = "LocalePicker";
|
||||||
@@ -92,7 +98,38 @@ public class LocalePicker extends ListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String[] getSupportedLocales(Context context) {
|
public static String[] getSupportedLocales(Context context) {
|
||||||
return context.getResources().getStringArray(R.array.supported_locales);
|
String[] allLocales = context.getResources().getStringArray(R.array.supported_locales);
|
||||||
|
|
||||||
|
Predicate<String> localeFilter = getLocaleFilter();
|
||||||
|
if (localeFilter == null) {
|
||||||
|
return allLocales;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> result = new ArrayList<>(allLocales.length);
|
||||||
|
for (String locale : allLocales) {
|
||||||
|
if (localeFilter.test(locale)) {
|
||||||
|
result.add(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int localeCount = result.size();
|
||||||
|
return (localeCount == allLocales.length) ? allLocales
|
||||||
|
: result.toArray(new String[localeCount]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Predicate<String> getLocaleFilter() {
|
||||||
|
try {
|
||||||
|
return LocalizationProperties.locale_filter()
|
||||||
|
.map(filter -> Pattern.compile(filter).asPredicate())
|
||||||
|
.orElse(null);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.e(TAG, "Failed to read locale filter.", e);
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
Log.e(TAG, "Bad locale filter format (\"" + e.getPattern() + "\"), skipping.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
|
public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
|
||||||
@@ -265,6 +302,11 @@ public class LocalePicker extends ListFragment {
|
|||||||
*/
|
*/
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
public static void updateLocales(LocaleList locales) {
|
public static void updateLocales(LocaleList locales) {
|
||||||
|
if (locales != null) {
|
||||||
|
locales = removeExcludedLocales(locales);
|
||||||
|
}
|
||||||
|
// Note: the empty list case is covered by Configuration.setLocales().
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final IActivityManager am = ActivityManager.getService();
|
final IActivityManager am = ActivityManager.getService();
|
||||||
final Configuration config = am.getConfiguration();
|
final Configuration config = am.getConfiguration();
|
||||||
@@ -280,6 +322,26 @@ public class LocalePicker extends ListFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static LocaleList removeExcludedLocales(@NonNull LocaleList locales) {
|
||||||
|
Predicate<String> localeFilter = getLocaleFilter();
|
||||||
|
if (localeFilter == null) {
|
||||||
|
return locales;
|
||||||
|
}
|
||||||
|
|
||||||
|
int localeCount = locales.size();
|
||||||
|
ArrayList<Locale> filteredLocales = new ArrayList<>(localeCount);
|
||||||
|
for (int i = 0; i < localeCount; ++i) {
|
||||||
|
Locale locale = locales.get(i);
|
||||||
|
if (localeFilter.test(locale.toString())) {
|
||||||
|
filteredLocales.add(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (localeCount == filteredLocales.size()) ? locales
|
||||||
|
: new LocaleList(filteredLocales.toArray(new Locale[0]));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the locale list.
|
* Get the locale list.
|
||||||
*
|
*
|
||||||
|
|||||||
21
core/sysprop/Android.bp
Normal file
21
core/sysprop/Android.bp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (C) 2020 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.
|
||||||
|
|
||||||
|
sysprop_library {
|
||||||
|
name: "com.android.sysprop.localization",
|
||||||
|
srcs: ["LocalizationProperties.sysprop"],
|
||||||
|
property_owner: "Platform",
|
||||||
|
api_packages: ["android.sysprop"],
|
||||||
|
vendor_available: false,
|
||||||
|
}
|
||||||
24
core/sysprop/LocalizationProperties.sysprop
Normal file
24
core/sysprop/LocalizationProperties.sysprop
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Copyright (C) 2020 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.
|
||||||
|
|
||||||
|
module: "android.sysprop.LocalizationProperties"
|
||||||
|
owner: Platform
|
||||||
|
|
||||||
|
prop {
|
||||||
|
api_name: "locale_filter"
|
||||||
|
type: String
|
||||||
|
prop_name: "ro.localization.locale_filter"
|
||||||
|
scope: Internal
|
||||||
|
access: Readonly
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
props {
|
||||||
|
module: "android.sysprop.LocalizationProperties"
|
||||||
|
prop {
|
||||||
|
api_name: "locale_filter"
|
||||||
|
type: String
|
||||||
|
scope: Internal
|
||||||
|
prop_name: "ro.localization.locale_filter"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
props {
|
||||||
|
module: "android.sysprop.LocalizationProperties"
|
||||||
|
prop {
|
||||||
|
api_name: "locale_filter"
|
||||||
|
type: String
|
||||||
|
scope: Internal
|
||||||
|
prop_name: "ro.localization.locale_filter"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
tests/LocalizationTest/Android.bp
Normal file
41
tests/LocalizationTest/Android.bp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (C) 2020 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.
|
||||||
|
|
||||||
|
android_test {
|
||||||
|
name: "LocalizationTest",
|
||||||
|
srcs: ["java/**/*.kt"],
|
||||||
|
libs: [
|
||||||
|
"android.test.runner",
|
||||||
|
"android.test.base",
|
||||||
|
"android.test.mock",
|
||||||
|
],
|
||||||
|
static_libs: [
|
||||||
|
"androidx.test.core",
|
||||||
|
"androidx.test.ext.junit",
|
||||||
|
"androidx.test.rules",
|
||||||
|
"mockito-target-extended-minus-junit4",
|
||||||
|
"truth-prebuilt",
|
||||||
|
],
|
||||||
|
jni_libs: [
|
||||||
|
// For mockito extended
|
||||||
|
"libdexmakerjvmtiagent",
|
||||||
|
"libstaticjvmtiagent",
|
||||||
|
],
|
||||||
|
certificate: "platform",
|
||||||
|
platform_apis: true,
|
||||||
|
test_suites: ["device-tests"],
|
||||||
|
optimize: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
29
tests/LocalizationTest/AndroidManifest.xml
Normal file
29
tests/LocalizationTest/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2020 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.
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.android.android.internal.app">
|
||||||
|
|
||||||
|
<application android:debuggable="true" android:testOnly="true">
|
||||||
|
<uses-library android:name="android.test.runner" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<instrumentation
|
||||||
|
android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
android:targetPackage="com.android.android.internal.app"
|
||||||
|
android:label="Localization Tests" />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
34
tests/LocalizationTest/AndroidTest.xml
Normal file
34
tests/LocalizationTest/AndroidTest.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2020 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.
|
||||||
|
-->
|
||||||
|
<configuration description="Localization Tests.">
|
||||||
|
<option name="test-suite-tag" value="apct" />
|
||||||
|
<option name="test-suite-tag" value="apct-instrumentation" />
|
||||||
|
|
||||||
|
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||||
|
<option name="cleanup-apks" value="true" />
|
||||||
|
<option name="install-arg" value="-t" />
|
||||||
|
<option name="test-file-name" value="LocalizationTest.apk" />
|
||||||
|
</target_preparer>
|
||||||
|
|
||||||
|
<option name="test-tag" value="LocalizationTest" />
|
||||||
|
|
||||||
|
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||||
|
<option name="package" value="com.android.android.internal.app" />
|
||||||
|
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
|
||||||
|
<option name="hidden-api-checks" value="false"/>
|
||||||
|
</test>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.android.internal.app
|
||||||
|
|
||||||
|
import android.os.SystemProperties
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
|
||||||
|
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
|
||||||
|
import com.android.internal.R
|
||||||
|
import com.android.internal.app.LocalePicker
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentMatchers.eq
|
||||||
|
import org.mockito.MockitoSession
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class LocalizationTest {
|
||||||
|
private val mContext = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
private val mUnfilteredLocales =
|
||||||
|
mContext.getResources().getStringArray(R.array.supported_locales)
|
||||||
|
|
||||||
|
private lateinit var mMockitoSession: MockitoSession
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mMockitoSession = mockitoSession()
|
||||||
|
.initMocks(this)
|
||||||
|
.spyStatic(SystemProperties::class.java)
|
||||||
|
.startMocking()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
mMockitoSession.finishMocking()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetSupportedLocales_noFilter() {
|
||||||
|
// Filter not set.
|
||||||
|
setTestLocaleFilter(null)
|
||||||
|
|
||||||
|
val locales1 = LocalePicker.getSupportedLocales(mContext)
|
||||||
|
|
||||||
|
assertThat(locales1).isEqualTo(mUnfilteredLocales)
|
||||||
|
|
||||||
|
// Empty filter.
|
||||||
|
setTestLocaleFilter("")
|
||||||
|
|
||||||
|
val locales2 = LocalePicker.getSupportedLocales(mContext)
|
||||||
|
|
||||||
|
assertThat(locales2).isEqualTo(mUnfilteredLocales)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetSupportedLocales_invalidFilter() {
|
||||||
|
setTestLocaleFilter("**")
|
||||||
|
|
||||||
|
val locales = LocalePicker.getSupportedLocales(mContext)
|
||||||
|
|
||||||
|
assertThat(locales).isEqualTo(mUnfilteredLocales)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetSupportedLocales_inclusiveFilter() {
|
||||||
|
setTestLocaleFilter("^(de-AT|de-DE|en|ru).*")
|
||||||
|
|
||||||
|
val locales = LocalePicker.getSupportedLocales(mContext)
|
||||||
|
|
||||||
|
assertThat(locales).isEqualTo(
|
||||||
|
mUnfilteredLocales
|
||||||
|
.filter { it.startsWithAnyOf("de-AT", "de-DE", "en", "ru") }
|
||||||
|
.toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetSupportedLocales_exclusiveFilter() {
|
||||||
|
setTestLocaleFilter("^(?!de-IT|es|fr).*")
|
||||||
|
|
||||||
|
val locales = LocalePicker.getSupportedLocales(mContext)
|
||||||
|
|
||||||
|
assertThat(locales).isEqualTo(
|
||||||
|
mUnfilteredLocales
|
||||||
|
.filter { !it.startsWithAnyOf("de-IT", "es", "fr") }
|
||||||
|
.toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTestLocaleFilter(localeFilter: String?) {
|
||||||
|
doReturn(localeFilter).`when` { SystemProperties.get(eq("ro.localization.locale_filter")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.startsWithAnyOf(vararg prefixes: String): Boolean {
|
||||||
|
prefixes.forEach {
|
||||||
|
if (startsWith(it)) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user