diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 32d88b2097048..c0239e8910fa8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -9085,6 +9085,25 @@ public class Activity extends ContextThemeWrapper state, sourceSpec, targetSpec, viewIds, uiTranslationSpec); } + /** + * If set, any activity launch in the same task will be overridden to the locale of activity + * that started the task. + * + *

Currently, Android supports per app languages, and system apps are able to start + * activities of another package on the same task, which may cause users to set different + * languages in different apps and display two different languages in one app.

+ * + *

The + * activity embedding feature will align the locale with root activity automatically, but + * it doesn't land on the phone yet. If activity embedding land on the phone in the future, + * please consider adapting activity embedding directly.

+ * + * @hide + */ + public void enableTaskLocaleOverride() { + ActivityClient.getInstance().enableTaskLocaleOverride(mToken); + } + class HostCallbacks extends FragmentHostCallback { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index 558dae5666886..aa868a768747f 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -563,6 +563,14 @@ public class ActivityClient { } } + void enableTaskLocaleOverride(IBinder token) { + try { + getActivityClientController().enableTaskLocaleOverride(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /** * Shows or hides a Camera app compat toggle for stretched issues with the requested state. * diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index ecea46a01f92b..03646c693831e 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -171,4 +171,10 @@ interface IActivityClientController { */ oneway void requestCompatCameraControl(in IBinder token, boolean showControl, boolean transformationApplied, in ICompatCameraControlCallback callback); + + /** + * If set, any activity launch in the same task will be overridden to the locale of activity + * that started the task. + */ + void enableTaskLocaleOverride(in IBinder token); } diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index a16e659610135..4428be7458f94 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1644,4 +1644,19 @@ class ActivityClientController extends IActivityClientController.Stub { } return false; } + + @Override + public void enableTaskLocaleOverride(IBinder token) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { + // Only allow system to align locale. + return; + } + + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + if (r != null) { + r.getTask().mAlignActivityLocaleWithTask = true; + } + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 841094221d99b..9a7b16516bb48 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -298,6 +298,7 @@ import android.os.Bundle; import android.os.Debug; import android.os.IBinder; import android.os.IRemoteCallback; +import android.os.LocaleList; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; @@ -7998,6 +7999,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } super.resolveOverrideConfiguration(newParentConfiguration); final Configuration resolvedConfig = getResolvedOverrideConfiguration(); + + applyLocaleOverrideIfNeeded(resolvedConfig); + if (isFixedRotationTransforming()) { // The resolved configuration is applied with rotated display configuration. If this // activity matches its parent (the following resolving procedures are no-op), then it @@ -10160,6 +10164,35 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + private void applyLocaleOverrideIfNeeded(Configuration resolvedConfig) { + // We always align the locale for ActivityEmbedding apps. System apps or some apps which + // has set known cert apps can embed across different uid activity. + boolean shouldAlignLocale = isEmbedded() + || (task != null && task.mAlignActivityLocaleWithTask); + if (!shouldAlignLocale) { + return; + } + + boolean differentPackage = task != null + && task.realActivity != null + && !task.realActivity.getPackageName().equals(packageName); + if (!differentPackage) { + return; + } + + LocaleList locale; + final ActivityTaskManagerInternal.PackageConfig appConfig = + mAtmService.mPackageConfigPersister.findPackageConfiguration( + task.realActivity.getPackageName(), mUserId); + // if there is no app locale for the package, clear the target activity's locale. + if (appConfig == null || appConfig.mLocales == null || appConfig.mLocales.isEmpty()) { + locale = LocaleList.getEmptyLocaleList(); + } else { + locale = appConfig.mLocales; + } + resolvedConfig.setLocales(locale); + } + /** * Whether we should send fake focus when the activity is resumed. This is done because some * game engines wait to get focus before drawing the content of the app. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3b5b5a903544b..b1e0798cda47a 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -612,6 +612,8 @@ class Task extends TaskFragment { boolean mLastSurfaceShowing = true; + boolean mAlignActivityLocaleWithTask = false; + private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,