diff --git a/core/api/current.txt b/core/api/current.txt index 1ba99f995ec70..319551d835984 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5638,6 +5638,11 @@ package android.app { field public static final int MODE_UNKNOWN = 0; // 0x0 } + public class GrammaticalInflectionManager { + method public int getApplicationGrammaticalGender(); + method public void setRequestedApplicationGrammaticalGender(int); + } + public class Instrumentation { ctor public Instrumentation(); method public android.os.TestLooperManager acquireLooperManager(android.os.Looper); @@ -9955,6 +9960,7 @@ package android.content { field public static final String FILE_INTEGRITY_SERVICE = "file_integrity"; field public static final String FINGERPRINT_SERVICE = "fingerprint"; field public static final String GAME_SERVICE = "game"; + field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection"; field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties"; field public static final String HEALTHCONNECT_SERVICE = "healthconnect"; field public static final String INPUT_METHOD_SERVICE = "input_method"; @@ -11166,6 +11172,7 @@ package android.content.pm { field public static final int CONFIG_DENSITY = 4096; // 0x1000 field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000 field public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 268435456; // 0x10000000 + field public static final int CONFIG_GRAMMATICAL_GENDER = 32768; // 0x8000 field public static final int CONFIG_KEYBOARD = 16; // 0x10 field public static final int CONFIG_KEYBOARD_HIDDEN = 32; // 0x20 field public static final int CONFIG_LAYOUT_DIRECTION = 8192; // 0x2000 @@ -12772,6 +12779,7 @@ package android.content.res { method public int diff(android.content.res.Configuration); method public boolean equals(android.content.res.Configuration); method @NonNull public static android.content.res.Configuration generateDelta(@NonNull android.content.res.Configuration, @NonNull android.content.res.Configuration); + method public int getGrammaticalGender(); method public int getLayoutDirection(); method @NonNull public android.os.LocaleList getLocales(); method public boolean isLayoutSizeAtLeast(int); @@ -12801,6 +12809,10 @@ package android.content.res { field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0 field public static final int FONT_WEIGHT_ADJUSTMENT_UNDEFINED = 2147483647; // 0x7fffffff + field public static final int GRAMMATICAL_GENDER_FEMININE = 3; // 0x3 + field public static final int GRAMMATICAL_GENDER_MASCULINE = 4; // 0x4 + field public static final int GRAMMATICAL_GENDER_NEUTRAL = 2; // 0x2 + field public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0; // 0x0 field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1 field public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0; // 0x0 field public static final int HARDKEYBOARDHIDDEN_YES = 2; // 0x2 diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java new file mode 100644 index 0000000000000..1905b6a46d7e8 --- /dev/null +++ b/core/java/android/app/GrammaticalInflectionManager.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 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 android.app; + +import android.annotation.SystemService; +import android.content.Context; +import android.content.res.Configuration; +import android.os.RemoteException; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * This class allow applications to control granular grammatical inflection settings (such as + * per-app grammatical gender). + */ +@SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE) +public class GrammaticalInflectionManager { + private static final Set VALID_GENDER_VALUES = new HashSet<>(Arrays.asList( + Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED, + Configuration.GRAMMATICAL_GENDER_NEUTRAL, + Configuration.GRAMMATICAL_GENDER_FEMININE, + Configuration.GRAMMATICAL_GENDER_MASCULINE)); + + private final Context mContext; + private final IGrammaticalInflectionManager mService; + + /** @hide Instantiated by ContextImpl */ + public GrammaticalInflectionManager(Context context, IGrammaticalInflectionManager service) { + mContext = context; + mService = service; + } + + /** + * Returns the current grammatical gender for the calling app. A new value can be requested via + * {@link #setRequestedApplicationGrammaticalGender(int)} and will be updated with a new + * configuration change. The method always returns the value received with the last received + * configuration change. + * + * @return the value of grammatical gender + * @see Configuration#getGrammaticalGender + */ + @Configuration.GrammaticalGender + public int getApplicationGrammaticalGender() { + return mContext.getApplicationContext() + .getResources() + .getConfiguration() + .getGrammaticalGender(); + } + + /** + * Sets the current grammatical gender for the calling app (keyed by package name and user ID + * retrieved from the calling pid). + * + *

Note: Changes to app grammatical gender will result in a configuration change (and + * potentially an Activity re-creation) being applied to the specified application. For more + * information, see the section on + * handling configuration changes. The set grammatical gender are persisted across + * application restarts; they are backed up if the user has enabled Backup & Restore.` + * + * @param grammaticalGender the terms of address the user preferred in an application. + * @see Configuration#getGrammaticalGender + */ + public void setRequestedApplicationGrammaticalGender( + @Configuration.GrammaticalGender int grammaticalGender) { + if (!VALID_GENDER_VALUES.contains(grammaticalGender)) { + throw new IllegalArgumentException("Unknown grammatical gender"); + } + + try { + mService.setRequestedApplicationGrammaticalGender( + mContext.getPackageName(), mContext.getUserId(), grammaticalGender); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl new file mode 100644 index 0000000000000..9366a45551da1 --- /dev/null +++ b/core/java/android/app/IGrammaticalInflectionManager.aidl @@ -0,0 +1,19 @@ +package android.app; + + +/** + * Internal interface used to control app-specific gender. + * + *

Use the {@link android.app.GrammarInflectionManager} class rather than going through + * this Binder interface directly. See {@link android.app.GrammarInflectionManager} for + * more complete documentation. + * + * @hide + */ + interface IGrammaticalInflectionManager { + + /** + * Sets a specified app’s app-specific grammatical gender. + */ + void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender); + } \ No newline at end of file diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 3730a6f9b428e..9e22d465a41df 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1546,6 +1546,7 @@ public final class SystemServiceRegistry { IAmbientContextManager.Stub.asInterface(iBinder); return new AmbientContextManager(ctx.getOuterContext(), manager); }}); + registerService(Context.WEARABLE_SENSING_SERVICE, WearableSensingManager.class, new CachedServiceFetcher() { @Override @@ -1558,6 +1559,18 @@ public final class SystemServiceRegistry { return new WearableSensingManager(ctx.getOuterContext(), manager); }}); + registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class, + new CachedServiceFetcher() { + @Override + public GrammaticalInflectionManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return new GrammaticalInflectionManager(ctx, + IGrammaticalInflectionManager.Stub.asInterface( + ServiceManager.getServiceOrThrow( + Context.GRAMMATICAL_INFLECTION_SERVICE))); + }}); + + sInitializing = true; try { // Note: the following functions need to be @SystemApis, once they become mainline diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 382e2bb6ee433..e6c5937f1d324 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -40,6 +40,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.BroadcastOptions; import android.app.GameManager; +import android.app.GrammaticalInflectionManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; @@ -3940,6 +3941,8 @@ public abstract class Context { CREDENTIAL_SERVICE, DEVICE_LOCK_SERVICE, VIRTUALIZATION_SERVICE, + GRAMMATICAL_INFLECTION_SERVICE, + }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -6136,6 +6139,14 @@ public abstract class Context { @SystemApi public static final String VIRTUALIZATION_SERVICE = "virtualization"; + /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link GrammaticalInflectionManager}. + * + * @see #getSystemService(String) + */ + public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection"; + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index dab57fda61066..68a84e8304920 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -807,6 +807,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { CONFIG_LAYOUT_DIRECTION, CONFIG_COLOR_MODE, CONFIG_FONT_SCALE, + CONFIG_GRAMMATICAL_GENDER, }) @Retention(RetentionPolicy.SOURCE) public @interface Config {} @@ -915,6 +916,12 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * range. Set from the {@link android.R.attr#configChanges} attribute. */ public static final int CONFIG_COLOR_MODE = 0x4000; + /** + * Bit in {@link #configChanges} that indicates that the activity + * can itself handle the change to gender. Set from the + * {@link android.R.attr#configChanges} attribute. + */ + public static final int CONFIG_GRAMMATICAL_GENDER = 0x8000; /** * Bit in {@link #configChanges} that indicates that the activity * can itself handle asset path changes. Set from the {@link android.R.attr#configChanges} @@ -946,7 +953,6 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * not a core resource configuration, but a higher-level value, so its * constant starts at the high bits. */ - public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 0x10000000; /** @hide diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index f47c1e0723b51..96aa6249245c3 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -46,6 +46,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.GrammaticalInflectionManager; import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; import android.content.LocaleProto; @@ -141,6 +142,44 @@ public final class Configuration implements Parcelable, Comparable CREATOR @@ -2155,6 +2206,8 @@ public final class Configuration implements Parcelable, ComparableThis service is API entry point for storing app-specific grammatical inflection. + */ +public class GrammaticalInflectionService extends SystemService { + + private final ActivityTaskManagerInternal mActivityTaskManagerInternal; + + /** + * Initializes the system service. + *

+ * Subclasses must define a single argument constructor that accepts the context + * and passes it to super. + *

+ * + * @param context The system server context. + * + * @hide + */ + public GrammaticalInflectionService(Context context) { + super(context); + mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); + } + + @Override + public void onStart() { + publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService); + } + + private final IBinder mService = new IGrammaticalInflectionManager.Stub() { + @Override + public void setRequestedApplicationGrammaticalGender( + String appPackageName, int userId, int gender) { + GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender( + appPackageName, userId, gender); + } + }; + + private void setRequestedApplicationGrammaticalGender( + String appPackageName, int userId, int gender) { + final ActivityTaskManagerInternal.PackageConfigurationUpdater updater = + mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName, + userId); + + updater.setGrammaticalGender(gender).commit(); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 509d75bf9b76c..8f6208c16d7b1 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -130,6 +130,7 @@ import com.android.server.display.DisplayManagerService; import com.android.server.display.color.ColorDisplayService; import com.android.server.dreams.DreamManagerService; import com.android.server.emergency.EmergencyAffordanceService; +import com.android.server.grammaticalinflection.GrammaticalInflectionService; import com.android.server.gpu.GpuService; import com.android.server.graphics.fonts.FontManagerService; import com.android.server.hdmi.HdmiControlService; @@ -1753,6 +1754,14 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + t.traceBegin("StartGrammarInflectionService"); + try { + mSystemServiceManager.startService(GrammaticalInflectionService.class); + } catch (Throwable e) { + reportWtf("starting GrammarInflectionService service", e); + } + t.traceEnd(); + t.traceBegin("UpdatePackagesIfNeeded"); try { Watchdog.getInstance().pauseWatchingCurrentThread("dexopt");