diff options
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 |