summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp2
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/app/GrammaticalInflectionManager.java53
-rw-r--r--core/java/android/app/IGrammaticalInflectionManager.aidl12
-rw-r--r--core/java/android/app/OWNERS3
-rw-r--r--core/java/android/app/grammatical_inflection_manager.aconfig8
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java173
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java166
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/OWNERS1
9 files changed, 402 insertions, 17 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 08a09e1b1a73..b1f587e45a6d 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -334,7 +334,7 @@ java_aconfig_library {
aconfig_declarations {
name: "android.app.flags-aconfig",
package: "android.app",
- srcs: ["core/java/android/app/activity_manager.aconfig"],
+ srcs: ["core/java/android/app/*.aconfig"],
}
java_aconfig_library {
diff --git a/core/api/current.txt b/core/api/current.txt
index f640ea25a22b..0c10c45f3570 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5952,6 +5952,7 @@ package android.app {
public class GrammaticalInflectionManager {
method public int getApplicationGrammaticalGender();
+ method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
method public void setRequestedApplicationGrammaticalGender(int);
}
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 1905b6a46d7e..bc6fe6146764 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,12 +16,15 @@
package android.app;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -31,11 +34,15 @@ import java.util.Set;
*/
@SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE)
public class GrammaticalInflectionManager {
- private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList(
+
+ /** @hide */
+ @NonNull
+ public static final Set<Integer> VALID_GRAMMATICAL_GENDER_VALUES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
Configuration.GRAMMATICAL_GENDER_NEUTRAL,
Configuration.GRAMMATICAL_GENDER_FEMININE,
- Configuration.GRAMMATICAL_GENDER_MASCULINE));
+ Configuration.GRAMMATICAL_GENDER_MASCULINE)));
private final Context mContext;
private final IGrammaticalInflectionManager mService;
@@ -79,7 +86,7 @@ public class GrammaticalInflectionManager {
*/
public void setRequestedApplicationGrammaticalGender(
@Configuration.GrammaticalGender int grammaticalGender) {
- if (!VALID_GENDER_VALUES.contains(grammaticalGender)) {
+ if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
throw new IllegalArgumentException("Unknown grammatical gender");
}
@@ -90,4 +97,44 @@ public class GrammaticalInflectionManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the current grammatical gender for all privileged applications. The value will be
+ * stored in an encrypted file at {@link android.os.Environment#getDataSystemCeDirectory(int)
+ *
+ * @param grammaticalGender the terms of address the user preferred in system.
+ *
+ * @see Configuration#getGrammaticalGender
+ * @hide
+ */
+ public void setSystemWideGrammaticalGender(
+ @Configuration.GrammaticalGender int grammaticalGender) {
+ if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
+ throw new IllegalArgumentException("Unknown grammatical gender");
+ }
+
+ try {
+ mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the current grammatical gender of privileged application from the encrypted file,
+ * which is stored under {@link android.os.Environment#getDataSystemCeDirectory(int)}.
+ *
+ * @return the value of grammatical gender
+ *
+ * @see Configuration#getGrammaticalGender
+ */
+ @FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
+ @Configuration.GrammaticalGender
+ public int getSystemGrammaticalGender() {
+ try {
+ return mService.getSystemGrammaticalGender(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 9366a45551da..48a48416d592 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -16,4 +16,14 @@ package android.app;
* Sets a specified app’s app-specific grammatical gender.
*/
void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender);
- } \ No newline at end of file
+
+ /**
+ * Sets the grammatical gender to system.
+ */
+ void setSystemWideGrammaticalGender(int userId, int gender);
+
+ /**
+ * Gets the grammatical gender from system.
+ */
+ int getSystemGrammaticalGender(int userId);
+ }
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e1c45d98e678..9cf54e3b9063 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -54,6 +54,9 @@ per-file IBackupAgent.aidl = file:/services/backup/OWNERS
per-file Broadcast* = file:/BROADCASTS_OWNERS
per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
+# GrammaticalInflectionManager
+per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
+
# KeyguardManager
per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
new file mode 100644
index 000000000000..989ce61337a3
--- /dev/null
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ name: "system_terms_of_address_enabled"
+ namespace: "grammatical_gender"
+ description: "Feature flag for System Terms of Address"
+ bug: "297798866"
+}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 41053e908a00..68848a2ad426 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -16,35 +16,65 @@
package com.android.server.grammaticalinflection;
+import static android.app.Flags.systemTermsOfAddressEnabled;
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
import android.annotation.Nullable;
+import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
-import android.os.IBinder;
+import android.os.Environment;
import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.SystemProperties;
+import android.util.AtomicFile;
import android.util.Log;
+import android.util.SparseIntArray;
+import android.util.Xml;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
/**
* The implementation of IGrammaticalInflectionManager.aidl.
*
* <p>This service is API entry point for storing app-specific grammatical inflection.
*/
public class GrammaticalInflectionService extends SystemService {
- private final String TAG = "GrammaticalInflection";
+ private static final String TAG = "GrammaticalInflection";
+ private static final String ATTR_NAME = "grammatical_gender";
+ private static final String USER_SETTINGS_FILE_NAME = "user_settings.xml";
+ private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection";
+ private static final String GRAMMATICAL_INFLECTION_ENABLED =
+ "i18n.grammatical_Inflection.enabled";
+
private final GrammaticalInflectionBackupHelper mBackupHelper;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ private final Object mLock = new Object();
+ private final SparseIntArray mGrammaticalGenderCache = new SparseIntArray();
+
private PackageManagerInternal mPackageManagerInternal;
- private static final String GRAMMATICAL_INFLECTION_ENABLED =
- "i18n.grammatical_Inflection.enabled";
+ private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
/**
* Initializes the system service.
@@ -62,22 +92,46 @@ public class GrammaticalInflectionService extends SystemService {
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mBackupHelper = new GrammaticalInflectionBackupHelper(
this, context.getPackageManager());
+ mBinderService = new GrammaticalInflectionBinderService();
}
@Override
public void onStart() {
- publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
+ publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mBinderService);
LocalServices.addService(GrammaticalInflectionManagerInternal.class,
new GrammaticalInflectionManagerInternalImpl());
}
- private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
+ private final class GrammaticalInflectionBinderService extends
+ IGrammaticalInflectionManager.Stub {
@Override
public void setRequestedApplicationGrammaticalGender(
String appPackageName, int userId, int gender) {
GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender(
appPackageName, userId, gender);
}
+
+ @Override
+ public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+ checkCallerIsSystem();
+ checkSystemTermsOfAddressIsEnabled();
+ GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
+ userId);
+ }
+
+ @Override
+ public int getSystemGrammaticalGender(int userId) {
+ checkSystemTermsOfAddressIsEnabled();
+ return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new GrammaticalInflectionShellCommand(mBinderService))
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
};
private final class GrammaticalInflectionManagerInternalImpl
@@ -94,12 +148,6 @@ public class GrammaticalInflectionService extends SystemService {
public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
}
-
- private void checkCallerIsSystem() {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Caller is not system.");
- }
- }
}
protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -137,4 +185,105 @@ public class GrammaticalInflectionService extends SystemService {
updater.setGrammaticalGender(gender).commit();
}
+
+ protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
+ if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+ grammaticalGender)) {
+ throw new IllegalArgumentException("Unknown grammatical gender");
+ }
+
+ synchronized (mLock) {
+ final File file = getGrammaticalGenderFile(userId);
+ final AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream stream = null;
+ try {
+ stream = atomicFile.startWrite();
+ stream.write(toXmlByteArray(grammaticalGender, stream));
+ atomicFile.finishWrite(stream);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write file " + atomicFile, e);
+ if (stream != null) {
+ atomicFile.failWrite(stream);
+ }
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
+ // gender.
+ public int getSystemGrammaticalGender(int userId) {
+ synchronized (mLock) {
+ final File file = getGrammaticalGenderFile(userId);
+ if (!file.exists()) {
+ Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
+ try {
+ InputStream in = new FileInputStream(file);
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+ }
+ }
+ return mGrammaticalGenderCache.get(userId);
+ }
+ }
+
+ private File getGrammaticalGenderFile(int userId) {
+ final File dir = new File(Environment.getDataSystemCeDirectory(userId),
+ TAG_GRAMMATICAL_INFLECTION);
+ return new File(dir, USER_SETTINGS_FILE_NAME);
+ }
+
+ private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) {
+
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
+ out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+ out.startDocument(/* encoding= */ null, /* standalone= */ true);
+ out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
+ out.attributeInt(null, ATTR_NAME, grammaticalGender);
+ out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
+ out.endDocument();
+
+ return outputStream.toByteArray();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (TAG_GRAMMATICAL_INFLECTION.equals(tagName)) {
+ return parser.getAttributeInt(null, ATTR_NAME);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ private void checkCallerIsSystem() {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID) {
+ throw new SecurityException("Caller is not system and shell.");
+ }
+ }
+
+ private void checkSystemTermsOfAddressIsEnabled() {
+ if (!systemTermsOfAddressEnabled()) {
+ throw new RuntimeException("The flag must be enabled to allow calling the API.");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
new file mode 100644
index 000000000000..d22372860ead
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.grammaticalinflection;
+
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
+import android.app.ActivityManager;
+import android.app.GrammaticalInflectionManager;
+import android.app.IGrammaticalInflectionManager;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell commands for {@link GrammaticalInflectionService}
+ */
+class GrammaticalInflectionShellCommand extends ShellCommand {
+
+ private static final SparseArray<String> GRAMMATICAL_GENDER_MAP = new SparseArray<>();
+ static {
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ "Not specified (0)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NEUTRAL, "Neuter (1)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_FEMININE, "Feminine (2)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_MASCULINE, "Masculine (3)");
+ }
+
+ private final IGrammaticalInflectionManager mBinderService;
+
+ GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+ mBinderService = grammaticalInflectionManager;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ switch (cmd) {
+ case "set-system-grammatical-gender":
+ return runSetSystemWideGrammaticalGender();
+ case "get-system-grammatical-gender":
+ return runGetSystemGrammaticalGender();
+ default: {
+ return handleDefaultCommands(cmd);
+ }
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Grammatical inflection manager (grammatical_inflection) shell commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(
+ " set-system-grammatical-gender [--user <USER_ID>] [--grammaticalGender "
+ + "<GRAMMATICAL_GENDER>]");
+ pw.println(" Set the system grammatical gender for system.");
+ pw.println(" --user <USER_ID>: apply for the given user, "
+ + "the current user is used when unspecified.");
+ pw.println(
+ " --grammaticalGender <GRAMMATICAL_GENDER>: The terms of address the user "
+ + "preferred in system, not specified (0) is used when unspecified.");
+ pw.println(
+ " eg. 0 = not_specified, 1 = neuter, 2 = feminine, 3 = masculine"
+ + ".");
+ pw.println(
+ " get-system-grammatical-gender [--user <USER_ID>]");
+ pw.println(" Get the system grammatical gender for system.");
+ pw.println(" --user <USER_ID>: apply for the given user, "
+ + "the current user is used when unspecified.");
+ }
+
+ private int runSetSystemWideGrammaticalGender() {
+ int userId = ActivityManager.getCurrentUser();
+ int grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ switch (option) {
+ case "--user": {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ }
+ case "-g":
+ case "--grammaticalGender": {
+ grammaticalGender = parseGrammaticalGender();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ }
+ } while (true);
+
+ try {
+ mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ return 0;
+ }
+
+ private int runGetSystemGrammaticalGender() {
+ int userId = ActivityManager.getCurrentUser();
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ switch (option) {
+ case "--user": {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ }
+ } while (true);
+
+ try {
+ int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+ getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ return 0;
+ }
+
+ private int parseGrammaticalGender() {
+ String arg = getNextArg();
+ if (arg == null) {
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ } else {
+ int grammaticalGender = Integer.parseInt(arg);
+ if (GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+ grammaticalGender)) {
+ return grammaticalGender;
+ } else {
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/grammaticalinflection/OWNERS b/services/core/java/com/android/server/grammaticalinflection/OWNERS
index 5f16ba9123b7..41d079ed9e75 100644
--- a/services/core/java/com/android/server/grammaticalinflection/OWNERS
+++ b/services/core/java/com/android/server/grammaticalinflection/OWNERS
@@ -2,3 +2,4 @@
allenwtsu@google.com
goldmanj@google.com
calvinpan@google.com
+zoeychen@google.com