1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.applications;
18 
19 import android.app.LocaleConfig;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.os.LocaleList;
26 import android.text.TextUtils;
27 import android.util.FeatureFlagUtils;
28 import android.util.Log;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.VisibleForTesting;
32 
33 import com.android.settings.R;
34 
35 import java.util.List;
36 
37 /**
38  * This class provides methods that help dealing with per app locale.
39  */
40 public class AppLocaleUtil {
41     private static final String TAG = AppLocaleUtil.class.getSimpleName();
42 
43     public static final Intent LAUNCHER_ENTRY_INTENT =
44             new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
45 
46     @VisibleForTesting
47     static LocaleConfig sLocaleConfig;
48 
49     /**
50      * Decides the UI display of per app locale.
51      */
canDisplayLocaleUi( @onNull Context context, @NonNull ApplicationInfo app, @NonNull List<ResolveInfo> infos)52     public static boolean canDisplayLocaleUi(
53             @NonNull Context context,
54             @NonNull ApplicationInfo app,
55             @NonNull List<ResolveInfo> infos) {
56         boolean isDisallowedPackage = isDisallowedPackage(context, app.packageName);
57         boolean hasLauncherEntry = hasLauncherEntry(app.packageName, infos);
58         boolean isSignedWithPlatformKey = app.isSignedWithPlatformKey();
59         boolean canDisplay = !isDisallowedPackage
60                 && !isSignedWithPlatformKey
61                 && hasLauncherEntry
62                 && isAppLocaleSupported(context, app.packageName);
63 
64         Log.i(TAG, "Can display preference - [" + app.packageName + "] :"
65                 + " isDisallowedPackage : " + isDisallowedPackage
66                 + " / isSignedWithPlatformKey : " + isSignedWithPlatformKey
67                 + " / hasLauncherEntry : " + hasLauncherEntry
68                 + " / canDisplay : " + canDisplay + " / 1.1");
69         return canDisplay;
70     }
71 
isDisallowedPackage(Context context, String packageName)72     private static boolean isDisallowedPackage(Context context, String packageName) {
73         final String[] disallowedPackages = context.getResources().getStringArray(
74                 R.array.config_disallowed_app_localeChange_packages);
75         for (String disallowedPackage : disallowedPackages) {
76             if (TextUtils.equals(packageName, disallowedPackage)) {
77                 return true;
78             }
79         }
80         return false;
81     }
82 
hasLauncherEntry(String packageName, List<ResolveInfo> infos)83     private static boolean hasLauncherEntry(String packageName, List<ResolveInfo> infos) {
84         return infos.stream()
85                 .anyMatch(info -> info.activityInfo.packageName.equals(packageName));
86     }
87 
88     /**
89      * Check the function of per app language is supported by current application.
90      */
isAppLocaleSupported(Context context, String packageName)91     public static boolean isAppLocaleSupported(Context context, String packageName) {
92         LocaleList localeList;
93         if (sLocaleConfig != null) {
94             localeList = getPackageLocales(sLocaleConfig);
95         } else {
96             localeList = getPackageLocales(context, packageName);
97         }
98 
99         if (localeList != null) {
100             return localeList.size() > 0;
101         }
102 
103         if (FeatureFlagUtils.isEnabled(
104                 context, FeatureFlagUtils.SETTINGS_APP_LOCALE_OPT_IN_ENABLED)) {
105             return false;
106         }
107 
108         return getAssetLocales(context, packageName).length > 0;
109     }
110 
111     /**
112      * Get locales fron AssetManager.
113      */
getAssetLocales(Context context, String packageName)114     public static String[] getAssetLocales(Context context, String packageName) {
115         try {
116             PackageManager packageManager = context.getPackageManager();
117             String[] locales = packageManager.getResourcesForApplication(packageName)
118                     .getAssets().getNonSystemLocales();
119             if (locales == null) {
120                 Log.i(TAG, "[" + packageName + "] locales are null.");
121             }
122             if (locales.length <= 0) {
123                 Log.i(TAG, "[" + packageName + "] locales length is 0.");
124                 return new String[0];
125             }
126             String locale = locales[0];
127             Log.i(TAG, "First asset locale - [" + packageName + "] " + locale);
128             return locales;
129         } catch (PackageManager.NameNotFoundException e) {
130             Log.w(TAG, "Can not found the package name : " + packageName + " / " + e);
131         }
132         return new String[0];
133     }
134 
135     @VisibleForTesting
getPackageLocales(LocaleConfig localeConfig)136     static LocaleList getPackageLocales(LocaleConfig localeConfig) {
137         if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) {
138             return localeConfig.getSupportedLocales();
139         }
140         return null;
141     }
142 
143     /**
144      * Get locales from LocaleConfig.
145      */
getPackageLocales(Context context, String packageName)146     public static LocaleList getPackageLocales(Context context, String packageName) {
147         try {
148             LocaleConfig localeConfig =
149                     new LocaleConfig(context.createPackageContext(packageName, 0));
150             return getPackageLocales(localeConfig);
151         } catch (PackageManager.NameNotFoundException e) {
152             Log.w(TAG, "Can not found the package name : " + packageName + " / " + e);
153         }
154         return null;
155     }
156 }
157