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.localepicker; 18 19 import static com.android.settings.flags.Flags.localeNotificationEnabled; 20 21 import android.app.FragmentTransaction; 22 import android.app.LocaleManager; 23 import android.app.NotificationChannel; 24 import android.app.NotificationManager; 25 import android.app.PendingIntent; 26 import android.app.settings.SettingsEnums; 27 import android.content.Intent; 28 import android.content.pm.PackageManager; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.LocaleList; 32 import android.os.SystemClock; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.view.MenuItem; 36 import android.view.View; 37 import android.widget.FrameLayout; 38 import android.widget.ListView; 39 40 import androidx.annotation.Nullable; 41 import androidx.core.app.NotificationCompat; 42 import androidx.core.view.ViewCompat; 43 44 import com.android.internal.app.LocalePickerWithRegion; 45 import com.android.internal.app.LocaleStore; 46 import com.android.settings.R; 47 import com.android.settings.applications.AppLocaleUtil; 48 import com.android.settings.applications.appinfo.AppLocaleDetails; 49 import com.android.settings.core.SettingsBaseActivity; 50 import com.android.settings.overlay.FeatureFactory; 51 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 52 53 @Deprecated 54 public class AppLocalePickerActivity extends SettingsBaseActivity 55 implements LocalePickerWithRegion.LocaleSelectedListener, MenuItem.OnActionExpandListener { 56 private static final String TAG = AppLocalePickerActivity.class.getSimpleName(); 57 private static final String CHANNEL_ID_SUGGESTION = "suggestion"; 58 private static final String CHANNEL_ID_SUGGESTION_TO_USER = "Locale suggestion"; 59 private static final int SIM_LOCALE = 1 << 0; 60 private static final int SYSTEM_LOCALE = 1 << 1; 61 private static final int APP_LOCALE = 1 << 2; 62 private static final int IME_LOCALE = 1 << 3; 63 static final String EXTRA_APP_LOCALE = "app_locale"; 64 static final String EXTRA_NOTIFICATION_ID = "notification_id"; 65 66 private String mPackageName; 67 private LocalePickerWithRegion mLocalePickerWithRegion; 68 private AppLocaleDetails mAppLocaleDetails; 69 private View mAppLocaleDetailContainer; 70 private NotificationController mNotificationController; 71 private MetricsFeatureProvider mMetricsFeatureProvider; 72 @Nullable private String mParentLocale; 73 74 @Override onCreate(Bundle savedInstanceState)75 public void onCreate(Bundle savedInstanceState) { 76 super.onCreate(savedInstanceState); 77 Uri data = getIntent().getData(); 78 if (data == null) { 79 Log.d(TAG, "There is no uri data."); 80 finish(); 81 return; 82 } 83 mPackageName = data.getSchemeSpecificPart(); 84 if (TextUtils.isEmpty(mPackageName)) { 85 Log.d(TAG, "There is no package name."); 86 finish(); 87 return; 88 } 89 90 if (!canDisplayLocaleUi()) { 91 Log.w(TAG, "Not allow to display Locale Settings UI."); 92 finish(); 93 return; 94 } 95 96 setTitle(R.string.app_locale_picker_title); 97 getActionBar().setDisplayHomeAsUpEnabled(true); 98 mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); 99 mNotificationController = NotificationController.getInstance(this); 100 101 mLocalePickerWithRegion = LocalePickerWithRegion.createLanguagePicker( 102 this, 103 this, 104 false /* translate only */, 105 null, 106 mPackageName, 107 this); 108 mAppLocaleDetails = AppLocaleDetails.newInstance(mPackageName, getUserId()); 109 mAppLocaleDetailContainer = launchAppLocaleDetailsPage(); 110 // Launch Locale picker part. 111 launchLocalePickerPage(); 112 } 113 114 @Override onOptionsItemSelected(MenuItem item)115 public boolean onOptionsItemSelected(MenuItem item) { 116 if (item.getItemId() == android.R.id.home) { 117 super.onBackPressed(); 118 return true; 119 } 120 return super.onOptionsItemSelected(item); 121 } 122 123 @Override onLocaleSelected(LocaleStore.LocaleInfo localeInfo)124 public void onLocaleSelected(LocaleStore.LocaleInfo localeInfo) { 125 if (localeInfo == null || localeInfo.getLocale() == null || localeInfo.isSystemLocale()) { 126 setAppDefaultLocale(""); 127 } else { 128 logLocaleSource(localeInfo); 129 setAppDefaultLocale(localeInfo.getLocale().toLanguageTag()); 130 broadcastAppLocaleChange(localeInfo); 131 } 132 finish(); 133 } 134 135 @Override onParentLocaleSelected(LocaleStore.LocaleInfo localeInfo)136 public void onParentLocaleSelected(LocaleStore.LocaleInfo localeInfo) { 137 mParentLocale = localeInfo.getFullNameNative(); 138 } 139 140 @Override onMenuItemActionCollapse(MenuItem item)141 public boolean onMenuItemActionCollapse(MenuItem item) { 142 mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/); 143 ViewCompat.setNestedScrollingEnabled(mAppLocaleDetails.getListView(), true); 144 ViewCompat.setNestedScrollingEnabled(mLocalePickerWithRegion.getListView(), true); 145 return true; 146 } 147 148 @Override onMenuItemActionExpand(MenuItem item)149 public boolean onMenuItemActionExpand(MenuItem item) { 150 mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/); 151 ViewCompat.setNestedScrollingEnabled(mAppLocaleDetails.getListView(), false); 152 ViewCompat.setNestedScrollingEnabled(mLocalePickerWithRegion.getListView(), false); 153 return true; 154 } 155 156 /** Sets the app's locale to the supplied language tag */ setAppDefaultLocale(String languageTag)157 private void setAppDefaultLocale(String languageTag) { 158 Log.d(TAG, "setAppDefaultLocale: " + languageTag); 159 LocaleManager localeManager = getSystemService(LocaleManager.class); 160 if (localeManager == null) { 161 Log.w(TAG, "LocaleManager is null, cannot set default app locale"); 162 return; 163 } 164 localeManager.setApplicationLocales(mPackageName, LocaleList.forLanguageTags(languageTag)); 165 } 166 broadcastAppLocaleChange(LocaleStore.LocaleInfo localeInfo)167 private void broadcastAppLocaleChange(LocaleStore.LocaleInfo localeInfo) { 168 if (!localeNotificationEnabled()) { 169 Log.w(TAG, "Locale notification is not enabled"); 170 return; 171 } 172 if (localeInfo.isAppCurrentLocale()) { 173 return; 174 } 175 try { 176 String localeTag = localeInfo.getLocale().toLanguageTag(); 177 int uid = getPackageManager().getApplicationInfo(mPackageName, 178 PackageManager.GET_META_DATA).uid; 179 boolean launchNotification = mNotificationController.shouldTriggerNotification( 180 uid, localeTag); 181 if (launchNotification) { 182 triggerNotification( 183 mNotificationController.getNotificationId(localeTag), 184 getString(R.string.title_system_locale_addition, 185 localeInfo.getFullNameNative()), 186 getString(R.string.desc_system_locale_addition), 187 localeTag); 188 mMetricsFeatureProvider.action(this, 189 SettingsEnums.ACTION_NOTIFICATION_FOR_SYSTEM_LOCALE); 190 } 191 } catch (PackageManager.NameNotFoundException e) { 192 Log.e(TAG, "Unable to find info for package: " + mPackageName); 193 } 194 } 195 triggerNotification( int notificationId, String title, String description, String localeTag)196 private void triggerNotification( 197 int notificationId, 198 String title, 199 String description, 200 String localeTag) { 201 NotificationManager notificationManager = getSystemService(NotificationManager.class); 202 final boolean channelExist = 203 notificationManager.getNotificationChannel(CHANNEL_ID_SUGGESTION) != null; 204 205 // Create an alert channel if it does not exist 206 if (!channelExist) { 207 NotificationChannel channel = 208 new NotificationChannel( 209 CHANNEL_ID_SUGGESTION, 210 CHANNEL_ID_SUGGESTION_TO_USER, 211 NotificationManager.IMPORTANCE_DEFAULT); 212 channel.setSound(/* sound */ null, /* audioAttributes */ null); // silent notification 213 notificationManager.createNotificationChannel(channel); 214 } 215 final NotificationCompat.Builder builder = 216 new NotificationCompat.Builder(this, CHANNEL_ID_SUGGESTION) 217 .setSmallIcon(R.drawable.ic_settings_language) 218 .setAutoCancel(true) 219 .setContentTitle(title) 220 .setContentText(description) 221 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) 222 .setContentIntent( 223 createPendingIntent(localeTag, notificationId, false)) 224 .setDeleteIntent( 225 createPendingIntent(localeTag, notificationId, true)); 226 notificationManager.notify(notificationId, builder.build()); 227 } 228 createPendingIntent(String locale, int notificationId, boolean isDeleteIntent)229 private PendingIntent createPendingIntent(String locale, int notificationId, 230 boolean isDeleteIntent) { 231 Intent intent = isDeleteIntent 232 ? new Intent(this, NotificationCancelReceiver.class) 233 : new Intent(this, NotificationActionActivity.class) 234 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 235 236 intent.putExtra(EXTRA_APP_LOCALE, locale) 237 .putExtra(EXTRA_NOTIFICATION_ID, notificationId); 238 int flag = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT; 239 int elapsedTime = (int) SystemClock.elapsedRealtimeNanos(); 240 241 return isDeleteIntent 242 ? PendingIntent.getBroadcast(this, elapsedTime, intent, flag) 243 : PendingIntent.getActivity(this, elapsedTime, intent, flag); 244 } 245 launchAppLocaleDetailsPage()246 private View launchAppLocaleDetailsPage() { 247 FrameLayout appLocaleDetailsContainer = new FrameLayout(this); 248 appLocaleDetailsContainer.setId(R.id.layout_app_locale_details); 249 getSupportFragmentManager() 250 .beginTransaction() 251 .replace(R.id.layout_app_locale_details, mAppLocaleDetails) 252 .commit(); 253 return appLocaleDetailsContainer; 254 } 255 launchLocalePickerPage()256 private void launchLocalePickerPage() { 257 // LocalePickerWithRegion use android.app.ListFragment. Thus, it can not use 258 // getSupportFragmentManager() to add this into container. 259 android.app.FragmentManager fragmentManager = getFragmentManager(); 260 fragmentManager.registerFragmentLifecycleCallbacks( 261 new android.app.FragmentManager.FragmentLifecycleCallbacks() { 262 @Override 263 public void onFragmentViewCreated( 264 android.app.FragmentManager fm, 265 android.app.Fragment f, View v, Bundle s) { 266 super.onFragmentViewCreated(fm, f, v, s); 267 ListView listView = (ListView) v.findViewById(android.R.id.list); 268 if (listView != null) { 269 if (mParentLocale != null) { 270 mAppLocaleDetails = AppLocaleDetails.newInstance(mPackageName, 271 getUserId()); 272 mAppLocaleDetailContainer = launchAppLocaleDetailsPage(); 273 mAppLocaleDetails.setParentLocale(mParentLocale); 274 } 275 listView.addHeaderView(mAppLocaleDetailContainer); 276 } 277 } 278 }, true); 279 fragmentManager.beginTransaction() 280 .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) 281 .replace(R.id.content_frame, mLocalePickerWithRegion) 282 .commit(); 283 } 284 canDisplayLocaleUi()285 private boolean canDisplayLocaleUi() { 286 try { 287 PackageManager packageManager = getPackageManager(); 288 return AppLocaleUtil.canDisplayLocaleUi(this, 289 packageManager.getApplicationInfo(mPackageName, 0), 290 packageManager.queryIntentActivities(AppLocaleUtil.LAUNCHER_ENTRY_INTENT, 291 PackageManager.GET_META_DATA)); 292 } catch (PackageManager.NameNotFoundException e) { 293 Log.e(TAG, "Unable to find info for package: " + mPackageName); 294 } 295 296 return false; 297 } 298 logLocaleSource(LocaleStore.LocaleInfo localeInfo)299 private void logLocaleSource(LocaleStore.LocaleInfo localeInfo) { 300 if (!localeInfo.isSuggested() || localeInfo.isAppCurrentLocale()) { 301 return; 302 } 303 int localeSource = 0; 304 if (hasSuggestionType(localeInfo, 305 LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE)) { 306 localeSource |= SYSTEM_LOCALE; 307 } 308 if (hasSuggestionType(localeInfo, 309 LocaleStore.LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE)) { 310 localeSource |= APP_LOCALE; 311 } 312 if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE)) { 313 localeSource |= IME_LOCALE; 314 } 315 if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_SIM)) { 316 localeSource |= SIM_LOCALE; 317 } 318 mMetricsFeatureProvider.action(this, 319 SettingsEnums.ACTION_CHANGE_APP_LANGUAGE_FROM_SUGGESTED, localeSource); 320 } 321 hasSuggestionType(LocaleStore.LocaleInfo localeInfo, int suggestionType)322 private static boolean hasSuggestionType(LocaleStore.LocaleInfo localeInfo, 323 int suggestionType) { 324 return localeInfo.isSuggestionOfType(suggestionType); 325 } 326 } 327