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

Commit e5706787 authored by Sandeep Siddhartha's avatar Sandeep Siddhartha
Browse files

Initial support for reading the enrollment metadata

- Defines schema for enrollment apps to publish metadata for the search
  keyphrase. This metadata consists of an ID, a user visible keyphrase,
  and the target package to handle the keyphrase, when it triggers.
- Reads the metadata and populates the KeyphraseInfo object for access via
  the VoiceInteractionService
- Adds permission and intent action for the enrollment app.
  The enrollment app needs to be a system app protected with the
  MANAGE_VOICE_KEYPHRASES permission and the activity performing the
  enrollment needs to handle the ACTION_MANAGE_VOICE_KEYPHRASES
- The keyphrase info currently stores an ID - that's for internal
  bookkeeping only, a keyphrase text - that's used by the public APIs,
  and a list of supported locales, which isn't exposed but is used while
  indicating if hardware hotword is available for a particular keyphrase
  in a language or not.

Change-Id: Ibe6c52a5a3eecfd74c4a8382713a35eb88d38df9
parent 6123315a
Loading
Loading
Loading
Loading
+167 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.service.voice;

import android.Manifest;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.List;

/** @hide */
public class KeyphraseEnrollmentInfo {
    private static final String TAG = "KeyphraseEnrollmentInfo";
    /**
     * Name under which a Hotword enrollment component publishes information about itself.
     * This meta-data should reference an XML resource containing a
     * <code>&lt;{@link
     * android.R.styleable#VoiceEnrollmentApplication
     * voice-enrollment-application}&gt;</code> tag.
     */
    private static final String VOICE_KEYPHRASE_META_DATA = "android.voice_enrollment";

    /**
     * Activity Action: Show activity for managing the keyphrases for hotword detection.
     * This needs to be defined by an activity that supports enrolling users for hotword/keyphrase
     * detection.
     */
    public static final String ACTION_MANAGE_VOICE_KEYPHRASES =
            "com.android.intent.action.MANAGE_VOICE_KEYPHRASES";

    private KeyphraseInfo[] mKeyphrases;
    private String mParseError;

    public KeyphraseEnrollmentInfo(PackageManager pm) {
        // Find the apps that supports enrollment for hotword keyhphrases,
        // Pick a privileged app and obtain the information about the supported keyphrases
        // from its metadata.
        List<ResolveInfo> ris = pm.queryIntentActivities(
                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";
            return;
        }

        boolean found = false;
        ApplicationInfo ai = null;
        for (ResolveInfo ri : ris) {
            try {
                ai = pm.getApplicationInfo(
                        ri.activityInfo.packageName, PackageManager.GET_META_DATA);
                if ((ai.flags & ApplicationInfo.FLAG_PRIVILEGED) == 0) {
                    // The application isn't privileged (/system/priv-app).
                    // The enrollment application needs to be a privileged system app.
                    continue;
                }
                if (!Manifest.permission.MANAGE_VOICE_KEYPHRASES.equals(ai.permission)) {
                    // The application trying to manage keyphrases doesn't
                    // require the MANAGE_VOICE_KEYPHRASES permission.
                    continue;
                }
                found = true;
                break;
            } catch (PackageManager.NameNotFoundException e) {
                Log.w(TAG, "error parsing voice enrollment meta-data", e);
            }
        }

        if (!found) {
            mParseError = "No suitable enrollment application found";
            return;
        }

        XmlResourceParser parser = 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;
            }

            Resources res = pm.getResourcesForApplication(ai);
            AttributeSet attrs = Xml.asAttributeSet(parser);

            int type;
            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                    && type != XmlPullParser.START_TAG) {
            }

            String nodeName = parser.getName();
            if (!"voice-enrollment-application".equals(nodeName)) {
                mParseError = "Meta-data does not start with voice-enrollment-application tag";
                return;
            }

            TypedArray array = res.obtainAttributes(attrs,
                    com.android.internal.R.styleable.VoiceEnrollmentApplication);
            int searchKeyphraseId = array.getInt(
                    com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId,
                    -1);
            if (searchKeyphraseId != -1) {
                String searchKeyphrase = array.getString(com.android.internal.R.styleable
                        .VoiceEnrollmentApplication_searchKeyphrase);
                String searchKeyphraseSupportedLocales =
                        array.getString(com.android.internal.R.styleable
                                .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales);
                String[] supportedLocales = new String[0];
                // Get all the supported locales from the comma-delimted string.
                if (searchKeyphraseSupportedLocales != null
                        && !searchKeyphraseSupportedLocales.isEmpty()) {
                    supportedLocales = searchKeyphraseSupportedLocales.split(",");
                }
                mKeyphrases = new KeyphraseInfo[1];
                mKeyphrases[0] = new KeyphraseInfo(
                        searchKeyphraseId, searchKeyphrase, supportedLocales);
            } else {
                mParseError = "searchKeyphraseId not specified in meta-data";
                return;
            }
        } catch (XmlPullParserException e) {
            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
            Log.w(TAG, "error parsing keyphrase enrollment meta-data", e);
            return;
        } catch (IOException e) {
            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
            Log.w(TAG, "error parsing keyphrase enrollment meta-data", e);
            return;
        } catch (PackageManager.NameNotFoundException e) {
            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
            Log.w(TAG, "error parsing keyphrase enrollment meta-data", e);
            return;
        } finally {
            if (parser != null) parser.close();
        }
    }

    public String getParseError() {
        return mParseError;
    }
}
+19 −0
Original line number Diff line number Diff line
package android.service.voice;

import android.util.ArraySet;

/** @hide */
public class KeyphraseInfo {
    public final int id;
    public final String keyphrase;
    public final ArraySet<String> supportedLocales;

    public KeyphraseInfo(int id, String keyphrase, String[] supportedLocales) {
        this.id = id;
        this.keyphrase = keyphrase;
        this.supportedLocales = new ArraySet<String>(supportedLocales.length);
        for (String locale : supportedLocales) {
            this.supportedLocales.add(locale);
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;

import com.android.internal.app.IVoiceInteractionManagerService;

/**
+8 −0
Original line number Diff line number Diff line
@@ -2070,6 +2070,14 @@
        android:description="@string/permdesc_bindVoiceInteraction"
        android:protectionLevel="signature" />

    <!-- Must be required by hotword enrollment application,
         to ensure that only the system can interact with it.
         @hide <p>Not for use by third-party applications.</p> -->
    <permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
        android:label="@string/permlab_manageVoiceKeyphrases"
        android:description="@string/permdesc_manageVoiceKeyphrases"
        android:protectionLevel="signature|system" />

    <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
         to ensure that only the system can bind to it.
         @hide -->
+10 −0
Original line number Diff line number Diff line
@@ -6394,6 +6394,16 @@
        <attr name="settingsActivity" />
    </declare-styleable>

    <!-- Use <code>voice-enrollment-application</code>
         as the root tag of the XML resource that escribes the supported keyphrases (hotwords)
         by the enrollment application.
         Described here are the attributes that can be included in that tag. -->
    <declare-styleable name="VoiceEnrollmentApplication">
        <attr name="searchKeyphraseId" format="integer" />
        <attr name="searchKeyphrase" format="string" />
        <attr name="searchKeyphraseSupportedLocales" format="string" />
    </declare-styleable>

    <!-- Attributes used to style the Action Bar. -->
    <declare-styleable name="ActionBar">
        <!-- The type of navigation to use. -->
Loading