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.annotation.NonNull; 20 import android.app.ActivityManager; 21 import android.app.LocaleConfig; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.os.LocaleList; 28 import android.util.FeatureFlagUtils; 29 import android.util.Log; 30 31 import com.android.settings.R; 32 33 import java.util.List; 34 35 /** This class provides methods that help dealing with per app locale. */ 36 public class AppLocaleUtil { 37 private static final String TAG = AppLocaleUtil.class.getSimpleName(); 38 39 public static final Intent LAUNCHER_ENTRY_INTENT = 40 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER); 41 42 /** 43 * Decides the UI display of per app locale. 44 */ canDisplayLocaleUi( @onNull Context context, @NonNull String packageName, @NonNull List<ResolveInfo> infos)45 public static boolean canDisplayLocaleUi( 46 @NonNull Context context, 47 @NonNull String packageName, 48 @NonNull List<ResolveInfo> infos) { 49 boolean isDisallowedPackage = isDisallowedPackage(context, packageName); 50 boolean hasLauncherEntry = hasLauncherEntry(packageName, infos); 51 boolean isSignedWithPlatformKey = isSignedWithPlatformKey(context, packageName); 52 boolean canDisplay = !isDisallowedPackage 53 && !isSignedWithPlatformKey 54 && hasLauncherEntry 55 && isAppLocaleSupported(context, packageName); 56 57 Log.i(TAG, "Can display preference - [" + packageName + "] :" 58 + " isDisallowedPackage : " + isDisallowedPackage 59 + " / isSignedWithPlatformKey : " + isSignedWithPlatformKey 60 + " / hasLauncherEntry : " + hasLauncherEntry 61 + " / canDisplay : " + canDisplay); 62 return canDisplay; 63 } 64 isDisallowedPackage(Context context, String packageName)65 private static boolean isDisallowedPackage(Context context, String packageName) { 66 final String[] disallowedPackages = context.getResources().getStringArray( 67 R.array.config_disallowed_app_localeChange_packages); 68 for (String disallowedPackage : disallowedPackages) { 69 if (packageName.equals(disallowedPackage)) { 70 return true; 71 } 72 } 73 return false; 74 } 75 isSignedWithPlatformKey(Context context, String packageName)76 private static boolean isSignedWithPlatformKey(Context context, String packageName) { 77 PackageInfo packageInfo = null; 78 PackageManager packageManager = context.getPackageManager(); 79 ActivityManager activityManager = context.getSystemService(ActivityManager.class); 80 try { 81 packageInfo = packageManager.getPackageInfoAsUser( 82 packageName, /* flags= */ 0, 83 activityManager.getCurrentUser()); 84 } catch (PackageManager.NameNotFoundException ex) { 85 Log.e(TAG, "package not found: " + packageName); 86 } 87 if (packageInfo == null) { 88 return false; 89 } 90 return packageInfo.applicationInfo.isSignedWithPlatformKey(); 91 } 92 hasLauncherEntry(String packageName, List<ResolveInfo> infos)93 private static boolean hasLauncherEntry(String packageName, List<ResolveInfo> infos) { 94 return infos.stream() 95 .anyMatch(info -> info.activityInfo.packageName.equals(packageName)); 96 } 97 98 /** 99 * Check the function of per app language is supported by current application. 100 */ isAppLocaleSupported(Context context, String packageName)101 public static boolean isAppLocaleSupported(Context context, String packageName) { 102 LocaleList localeList = getPackageLocales(context, packageName); 103 if (localeList != null) { 104 return localeList.size() > 0; 105 } 106 107 if (FeatureFlagUtils.isEnabled( 108 context, FeatureFlagUtils.SETTINGS_APP_LOCALE_OPT_IN_ENABLED)) { 109 return false; 110 } 111 112 return getAssetLocales(context, packageName).length > 0; 113 } 114 115 /** 116 * Get locales fron AssetManager. 117 */ getAssetLocales(Context context, String packageName)118 public static String[] getAssetLocales(Context context, String packageName) { 119 try { 120 PackageManager packageManager = context.getPackageManager(); 121 String[] locales = packageManager.getResourcesForApplication( 122 packageManager.getPackageInfo(packageName, PackageManager.MATCH_ALL) 123 .applicationInfo).getAssets().getNonSystemLocales(); 124 if (locales == null) { 125 Log.i(TAG, "[" + packageName + "] locales are null."); 126 } 127 if (locales.length <= 0) { 128 Log.i(TAG, "[" + packageName + "] locales length is 0."); 129 return new String[0]; 130 } 131 String locale = locales[0]; 132 Log.i(TAG, "First asset locale - [" + packageName + "] " + locale); 133 return locales; 134 } catch (PackageManager.NameNotFoundException e) { 135 Log.w(TAG, "Can not found the package name : " + packageName + " / " + e); 136 } 137 return new String[0]; 138 } 139 140 /** 141 * Get locales from LocaleConfig. 142 */ getPackageLocales(Context context, String packageName)143 public static LocaleList getPackageLocales(Context context, String packageName) { 144 try { 145 LocaleConfig localeConfig = 146 new LocaleConfig(context.createPackageContext(packageName, 0)); 147 if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { 148 return localeConfig.getSupportedLocales(); 149 } 150 } catch (PackageManager.NameNotFoundException e) { 151 Log.w(TAG, "Can not found the package name : " + packageName + " / " + e); 152 } 153 return null; 154 } 155 } 156