Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1cba0696 authored by Chris Thornton's avatar Chris Thornton
Browse files

Support multiple Enrollment APKs

Previously, the KeyphraseEnrollmentInfo would iterate through the list
of Enrollment APKs and just use the first one for a list of supported
keywords (of which there only could be one). Now, all APKs will be
enumerated and the enrollment APK that provides the given
keyphrase/locale will be used for the enrollment intent.

Change-Id: I78b0ef758c6c024b5787603bba5907a646f3c3f1
parent b02ffa5f
Loading
Loading
Loading
Loading
+96 −55
Original line number Diff line number Diff line
@@ -35,9 +35,11 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Enrollment information about the different available keyphrases.
@@ -82,8 +84,16 @@ public class KeyphraseEnrollmentInfo {
    public static final String EXTRA_VOICE_KEYPHRASE_LOCALE =
            "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE";

    private KeyphraseMetadata[] mKeyphrases;
    private String mEnrollmentPackage;
    /**
     * List of available keyphrases.
     */
    final private KeyphraseMetadata[] mKeyphrases;

    /**
     * Map between KeyphraseMetadata and the package name of the enrollment app that provides it.
     */
    final private Map<KeyphraseMetadata, String> mKeyphrasePackageMap;

    private String mParseError;

    public KeyphraseEnrollmentInfo(PackageManager pm) {
@@ -94,15 +104,17 @@ public class KeyphraseEnrollmentInfo {
                new Intent(ACTION_MANAGE_VOICE_KEYPHRASES), PackageManager.MATCH_DEFAULT_ONLY);
        if (ris == null || ris.isEmpty()) {
            // No application capable of enrolling for voice keyphrases is present.
            mParseError = "No enrollment application found";
            mParseError = "No enrollment applications found";
            mKeyphrasePackageMap = null;
            mKeyphrases = null;
            return;
        }

        boolean found = false;
        ApplicationInfo ai = null;
        List<String> parseErrors = new LinkedList<String>();
        mKeyphrasePackageMap = new HashMap<KeyphraseMetadata, String>();
        for (ResolveInfo ri : ris) {
            try {
                ai = pm.getApplicationInfo(
                ApplicationInfo ai = pm.getApplicationInfo(
                        ri.activityInfo.packageName, PackageManager.GET_META_DATA);
                if ((ai.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) == 0) {
                    // The application isn't privileged (/system/priv-app).
@@ -116,27 +128,45 @@ public class KeyphraseEnrollmentInfo {
                    Slog.w(TAG, ai.packageName + " does not require MANAGE_VOICE_KEYPHRASES");
                    continue;
                }
                mEnrollmentPackage = ai.packageName;
                found = true;
                break;

                mKeyphrasePackageMap.put(
                        getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors),
                        ai.packageName);
            } catch (PackageManager.NameNotFoundException e) {
                Slog.w(TAG, "error parsing voice enrollment meta-data", e);
                String error = "error parsing voice enrollment meta-data for "
                        + ri.activityInfo.packageName;
                parseErrors.add(error + ": " + e);
                Slog.w(TAG, error, e);
            }
        }

        if (!found) {
        if (mKeyphrasePackageMap.isEmpty()) {
            String error = "No suitable enrollment application found";
            parseErrors.add(error);
            Slog.w(TAG, error);
            mKeyphrases = null;
            mParseError = "No suitable enrollment application found";
            return;
        } else {
            mKeyphrases = mKeyphrasePackageMap.keySet().toArray(
                    new KeyphraseMetadata[mKeyphrasePackageMap.size()]);
        }

        if (!parseErrors.isEmpty()) {
            mParseError = TextUtils.join("\n", parseErrors);
        }
    }

    private KeyphraseMetadata getKeyphraseMetadataFromApplicationInfo(PackageManager pm,
            ApplicationInfo ai, List<String> parseErrors) {
        XmlResourceParser parser = null;
        String packageName = ai.packageName;
        KeyphraseMetadata keyphraseMetadata = null;
        try {
            parser = ai.loadXmlMetaData(pm, VOICE_KEYPHRASE_META_DATA);
            if (parser == null) {
                mParseError = "No " + VOICE_KEYPHRASE_META_DATA + " meta-data for "
                        + ai.packageName;
                return;
                String error = "No " + VOICE_KEYPHRASE_META_DATA + " meta-data for " + packageName;
                parseErrors.add(error);
                Slog.w(TAG, error);
                return null;
            }

            Resources res = pm.getResourcesForApplication(ai);
@@ -149,48 +179,55 @@ public class KeyphraseEnrollmentInfo {

            String nodeName = parser.getName();
            if (!"voice-enrollment-application".equals(nodeName)) {
                mParseError = "Meta-data does not start with voice-enrollment-application tag";
                return;
                String error = "Meta-data does not start with voice-enrollment-application tag for "
                        + packageName;
                parseErrors.add(error);
                Slog.w(TAG, error);
                return null;
            }

            TypedArray array = res.obtainAttributes(attrs,
                    com.android.internal.R.styleable.VoiceEnrollmentApplication);
            initializeKeyphrasesFromTypedArray(array);
            keyphraseMetadata = getKeyphraseFromTypedArray(array, packageName, parseErrors);
            array.recycle();
        } catch (XmlPullParserException e) {
            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
            Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
            return;
            String error = "Error parsing keyphrase enrollment meta-data for " + packageName;
            parseErrors.add(error + ": " + e);
            Slog.w(TAG, error, e);
        } catch (IOException e) {
            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
            Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
            return;
            String error = "Error parsing keyphrase enrollment meta-data for " + packageName;
            parseErrors.add(error + ": " + e);
            Slog.w(TAG, error, e);
        } catch (PackageManager.NameNotFoundException e) {
            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
            Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
            return;
            String error = "Error parsing keyphrase enrollment meta-data for " + packageName;
            parseErrors.add(error + ": " + e);
            Slog.w(TAG, error, e);
        } finally {
            if (parser != null) parser.close();
        }
        return keyphraseMetadata;
    }

    private void initializeKeyphrasesFromTypedArray(TypedArray array) {
    private KeyphraseMetadata getKeyphraseFromTypedArray(TypedArray array, String packageName,
            List<String> parseErrors) {
        // Get the keyphrase ID.
        int searchKeyphraseId = array.getInt(
                com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId, -1);
        if (searchKeyphraseId <= 0) {
            mParseError = "No valid searchKeyphraseId specified in meta-data";
            Slog.w(TAG, mParseError);
            return;
            String error = "No valid searchKeyphraseId specified in meta-data for " + packageName;
            parseErrors.add(error);
            Slog.w(TAG, error);
            return null;
        }

        // Get the keyphrase text.
        String searchKeyphrase = array.getString(
                com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphrase);
        if (searchKeyphrase == null) {
            mParseError = "No valid searchKeyphrase specified in meta-data";
            Slog.w(TAG, mParseError);
            return;
            String error = "No valid searchKeyphrase specified in meta-data for " + packageName;
            parseErrors.add(error);
            Slog.w(TAG, error);
            return null;
        }

        // Get the supported locales.
@@ -198,9 +235,11 @@ public class KeyphraseEnrollmentInfo {
                com.android.internal.R.styleable
                        .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales);
        if (searchKeyphraseSupportedLocales == null) {
            mParseError = "No valid searchKeyphraseSupportedLocales specified in meta-data";
            Slog.w(TAG, mParseError);
            return;
            String error = "No valid searchKeyphraseSupportedLocales specified in meta-data for "
                    + packageName;
            parseErrors.add(error);
            Slog.w(TAG, error);
            return null;
        }
        ArraySet<Locale> locales = new ArraySet<>();
        // Try adding locales if the locale string is non-empty.
@@ -214,9 +253,11 @@ public class KeyphraseEnrollmentInfo {
                // We catch a generic exception here because we don't want the system service
                // to be affected by a malformed metadata because invalid locales were specified
                // by the system application.
                mParseError = "Error reading searchKeyphraseSupportedLocales from meta-data";
                Slog.w(TAG, mParseError, ex);
                return;
                String error = "Error reading searchKeyphraseSupportedLocales from meta-data for "
                        + packageName;
                parseErrors.add(error);
                Slog.w(TAG, error);
                return null;
            }
        }

@@ -224,13 +265,13 @@ public class KeyphraseEnrollmentInfo {
        int recognitionModes = array.getInt(com.android.internal.R.styleable
                .VoiceEnrollmentApplication_searchKeyphraseRecognitionFlags, -1);
        if (recognitionModes < 0) {
            mParseError = "No valid searchKeyphraseRecognitionFlags specified in meta-data";
            Slog.w(TAG, mParseError);
            return;
            String error = "No valid searchKeyphraseRecognitionFlags specified in meta-data for "
                    + packageName;
            parseErrors.add(error);
            Slog.w(TAG, error);
            return null;
        }
        mKeyphrases = new KeyphraseMetadata[1];
        mKeyphrases[0] = new KeyphraseMetadata(searchKeyphraseId, searchKeyphrase, locales,
                recognitionModes);
        return new KeyphraseMetadata(searchKeyphraseId, searchKeyphrase, locales, recognitionModes);
    }

    public String getParseError() {
@@ -259,14 +300,15 @@ public class KeyphraseEnrollmentInfo {
     *         given keyphrase/locale combination isn't possible.
     */
    public Intent getManageKeyphraseIntent(int action, String keyphrase, Locale locale) {
        if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) {
        if (mKeyphrasePackageMap == null || mKeyphrasePackageMap.isEmpty()) {
            Slog.w(TAG, "No enrollment application exists");
            return null;
        }

        if (getKeyphraseMetadata(keyphrase, locale) != null) {
        KeyphraseMetadata keyphraseMetadata = getKeyphraseMetadata(keyphrase, locale);
        if (keyphraseMetadata != null) {
            Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES)
                    .setPackage(mEnrollmentPackage)
                    .setPackage(mKeyphrasePackageMap.get(keyphraseMetadata))
                    .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase)
                    .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale.toLanguageTag())
                    .putExtra(EXTRA_VOICE_KEYPHRASE_ACTION, action);
@@ -298,14 +340,13 @@ public class KeyphraseEnrollmentInfo {
                return keyphraseMetadata;
            }
        }
        Slog.w(TAG, "Enrollment application doesn't support the given keyphrase/locale");
        Slog.w(TAG, "No Enrollment application supports the given keyphrase/locale");
        return null;
    }

    @Override
    public String toString() {
        return "KeyphraseEnrollmentInfo [Keyphrases=" + Arrays.toString(mKeyphrases)
                + ", EnrollmentPackage=" + mEnrollmentPackage + ", ParseError=" + mParseError
                + "]";
        return "KeyphraseEnrollmentInfo [Keyphrases=" + mKeyphrasePackageMap.toString()
                + ", ParseError=" + mParseError + "]";
    }
}