Merge "Integrate system language filtering functionality" am: 2df94f9b15 am: db00e424d9 am: 654ed1df03 am: 2f5ddb2fc6
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1362798 Change-Id: I0c3b27d5319e9403a5f637fcecaf319aa5810bc9
This commit is contained in:
committed by
Automerger Merge Worker
commit
1048aa0326
@@ -460,6 +460,7 @@ java_library {
|
||||
|
||||
"com.android.sysprop.apex",
|
||||
"com.android.sysprop.init",
|
||||
"com.android.sysprop.localization",
|
||||
"PlatformProperties",
|
||||
],
|
||||
sdk_version: "core_platform",
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.ListFragment;
|
||||
@@ -28,6 +30,7 @@ import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.Settings;
|
||||
import android.sysprop.LocalizationProperties;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -43,6 +46,9 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
public class LocalePicker extends ListFragment {
|
||||
private static final String TAG = "LocalePicker";
|
||||
@@ -92,7 +98,38 @@ public class LocalePicker extends ListFragment {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -265,6 +302,11 @@ public class LocalePicker extends ListFragment {
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public static void updateLocales(LocaleList locales) {
|
||||
if (locales != null) {
|
||||
locales = removeExcludedLocales(locales);
|
||||
}
|
||||
// Note: the empty list case is covered by Configuration.setLocales().
|
||||
|
||||
try {
|
||||
final IActivityManager am = ActivityManager.getService();
|
||||
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.
|
||||
*
|
||||
|
||||
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