Merge "PowerManager: Adds the Sustained performance API."
diff --git a/Android.mk b/Android.mk
index 1d797c4..8daa1aa 100644
--- a/Android.mk
+++ b/Android.mk
@@ -288,6 +288,7 @@
core/java/com/android/internal/app/IBatteryStats.aidl \
core/java/com/android/internal/app/IEphemeralResolver.aidl \
core/java/com/android/internal/app/IProcessStats.aidl \
+ core/java/com/android/internal/app/ISoundTriggerService.aidl \
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \
core/java/com/android/internal/app/IVoiceInteractor.aidl \
diff --git a/api/system-current.txt b/api/system-current.txt
index 77ae1c9..dc081bf 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7607,6 +7607,8 @@
method public void flushPendingScanResults(android.bluetooth.le.ScanCallback);
method public void startScan(android.bluetooth.le.ScanCallback);
method public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
+ method public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
+ method public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
method public void startTruncatedScan(java.util.List<android.bluetooth.le.TruncatedFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
method public void stopScan(android.bluetooth.le.ScanCallback);
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index fc1a355..5eed781 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -17,6 +17,7 @@
package android.app;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.ISoundTriggerService;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.os.IDropBoxManagerService;
@@ -61,6 +62,7 @@
import android.media.midi.MidiManager;
import android.media.projection.MediaProjectionManager;
import android.media.session.MediaSessionManager;
+import android.media.soundtrigger.SoundTriggerManager;
import android.media.tv.ITvInputManager;
import android.media.tv.TvInputManager;
import android.net.ConnectivityManager;
@@ -708,12 +710,22 @@
public RadioManager createService(ContextImpl ctx) {
return new RadioManager(ctx);
}});
+
registerService(Context.HARDWARE_PROPERTIES_SERVICE, HardwarePropertiesManager.class,
new CachedServiceFetcher<HardwarePropertiesManager>() {
@Override
public HardwarePropertiesManager createService(ContextImpl ctx) {
return new HardwarePropertiesManager();
}});
+
+ registerService(Context.SOUND_TRIGGER_SERVICE, SoundTriggerManager.class,
+ new CachedServiceFetcher<SoundTriggerManager>() {
+ @Override
+ public SoundTriggerManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE);
+ Log.i(TAG, "Creating new instance of SoundTriggerManager object.");
+ return new SoundTriggerManager(ctx, ISoundTriggerService.Stub.asInterface(b));
+ }});
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1c1526f..768da6a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2770,6 +2770,45 @@
}
/**
+ * Called by a device owner to set whether all users created on the device should be ephemeral.
+ *
+ * <p>The system user is exempt from this policy - it is never ephemeral.
+ *
+ * <p>The calling device admin must be the device owner. If it is not, a security exception will
+ * be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param forceEphemeralUsers If true, all the existing users will be deleted and all
+ * subsequently created users will be ephemeral.
+ * @hide
+ */
+ public void setForceEphemeralUsers(
+ @NonNull ComponentName admin, boolean forceEphemeralUsers) {
+ if (mService != null) {
+ try {
+ mService.setForceEphemeralUsers(admin, forceEphemeralUsers);
+ } catch (RemoteException e) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+ }
+ }
+ }
+
+ /**
+ * @return true if all users are created ephemeral.
+ * @hide
+ */
+ public boolean getForceEphemeralUsers(@NonNull ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getForceEphemeralUsers(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+ }
+ }
+ return false;
+ }
+
+ /**
* Called by an application that is administering the device to disable keyguard customizations,
* such as widgets. After setting this, keyguard features will be disabled according to the
* provided feature list.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index a80ed9b..20d4a29 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -230,6 +230,9 @@
void setAutoTimeRequired(in ComponentName who, boolean required);
boolean getAutoTimeRequired();
+ void setForceEphemeralUsers(in ComponentName who, boolean forceEpehemeralUsers);
+ boolean getForceEphemeralUsers(in ComponentName who);
+
boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle);
void setUserIcon(in ComponentName admin, in Bitmap icon);
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 3660be7..6b5f77f 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -23,6 +23,7 @@
import android.bluetooth.le.ScanSettings;
import android.bluetooth.le.ResultStorageDescriptor;
import android.os.ParcelUuid;
+import android.os.WorkSource;
import android.bluetooth.IBluetoothGattCallback;
import android.bluetooth.IBluetoothGattServerCallback;
@@ -35,8 +36,8 @@
List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
void startScan(in int appIf, in boolean isServer, in ScanSettings settings,
- in List<ScanFilter> filters,
- in List scanStorages, in String callingPackage);
+ in List<ScanFilter> filters, in WorkSource workSource, in List scanStorages,
+ in String callingPackage);
void stopScan(in int appIf, in boolean isServer);
void flushPendingBatchResults(in int appIf, in boolean isServer);
void startMultiAdvertising(in int appIf,
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 2ba87744..03449cc 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -29,6 +29,7 @@
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.RemoteException;
+import android.os.WorkSource;
import android.util.Log;
import java.util.ArrayList;
@@ -89,9 +90,6 @@
*/
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public void startScan(final ScanCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback is null");
- }
startScan(null, new ScanSettings.Builder().build(), callback);
}
@@ -112,14 +110,53 @@
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public void startScan(List<ScanFilter> filters, ScanSettings settings,
final ScanCallback callback) {
- startScan(filters, settings, callback, null);
+ startScan(filters, settings, null, callback, null);
+ }
+
+ /**
+ * Start Bluetooth LE scan. Same as {@link #startScan(ScanCallback)} but allows the caller to
+ * specify on behalf of which application(s) the work is being done.
+ *
+ * @param workSource {@link WorkSource} identifying the application(s) for which to blame for
+ * the scan.
+ * @param callback Callback used to deliver scan results.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS })
+ public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) {
+ startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback);
+ }
+
+ /**
+ * Start Bluetooth LE scan. Same as {@link #startScan(List, ScanSettings, ScanCallback)} but
+ * allows the caller to specify on behalf of which application(s) the work is being done.
+ *
+ * @param filters {@link ScanFilter}s for finding exact BLE devices.
+ * @param settings Settings for the scan.
+ * @param workSource {@link WorkSource} identifying the application(s) for which to blame for
+ * the scan.
+ * @param callback Callback used to deliver scan results.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS })
+ public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings,
+ final WorkSource workSource, final ScanCallback callback) {
+ startScan(filters, settings, workSource, callback, null);
}
private void startScan(List<ScanFilter> filters, ScanSettings settings,
- final ScanCallback callback, List<List<ResultStorageDescriptor>> resultStorages) {
+ final WorkSource workSource, final ScanCallback callback,
+ List<List<ResultStorageDescriptor>> resultStorages) {
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
- if (settings == null || callback == null) {
- throw new IllegalArgumentException("settings or callback is null");
+ if (callback == null) {
+ throw new IllegalArgumentException("callback is null");
+ }
+ if (settings == null) {
+ throw new IllegalArgumentException("settings is null");
}
synchronized (mLeScanClients) {
if (mLeScanClients.containsKey(callback)) {
@@ -152,7 +189,7 @@
return;
}
BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters,
- settings, callback, resultStorages);
+ settings, workSource, callback, resultStorages);
wrapper.startRegisteration();
}
}
@@ -215,7 +252,7 @@
scanFilters.add(filter.getFilter());
scanStorages.add(filter.getStorageDescriptors());
}
- startScan(scanFilters, settings, callback, scanStorages);
+ startScan(scanFilters, settings, null, callback, scanStorages);
}
/**
@@ -235,6 +272,7 @@
private final ScanCallback mScanCallback;
private final List<ScanFilter> mFilters;
+ private final WorkSource mWorkSource;
private ScanSettings mSettings;
private IBluetoothGatt mBluetoothGatt;
private List<List<ResultStorageDescriptor>> mResultStorages;
@@ -246,10 +284,12 @@
public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
List<ScanFilter> filters, ScanSettings settings,
- ScanCallback scanCallback, List<List<ResultStorageDescriptor>> resultStorages) {
+ WorkSource workSource, ScanCallback scanCallback,
+ List<List<ResultStorageDescriptor>> resultStorages) {
mBluetoothGatt = bluetoothGatt;
mFilters = filters;
mSettings = settings;
+ mWorkSource = workSource;
mScanCallback = scanCallback;
mClientIf = 0;
mResultStorages = resultStorages;
@@ -322,7 +362,9 @@
mClientIf = clientIf;
try {
mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters,
- mResultStorages, ActivityThread.currentOpPackageName());
+ mWorkSource, mResultStorages,
+ ActivityThread.currentOpPackageName());
+
} catch (RemoteException e) {
Log.e(TAG, "fail to start le scan: " + e);
mClientIf = -1;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 27cdd50..3142b40 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2613,6 +2613,7 @@
MIDI_SERVICE,
RADIO_SERVICE,
HARDWARE_PROPERTIES_SERVICE,
+ //@hide: SOUND_TRIGGER_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -3195,6 +3196,16 @@
public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
/**
+ * Use with {@link #getSystemService} to access the
+ * {@link com.android.server.voiceinteraction.SoundTriggerService}.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
+
+
+ /**
* Use with {@link #getSystemService} to retrieve an
* {@link android.app.backup.IBackupManager IBackupManager} for communicating
* with the backup mechanism.
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index 2f6dbe7..597efa5 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -25,9 +25,12 @@
/**
* Called when the keyphrase is spoken.
*
- * @param data Optional trigger audio data, if it was requested and is available.
+ * @param recognitionEvent Object containing data relating to the
+ * recognition event such as trigger audio data, if it was requested
+ * and is available.
*/
- void onDetected(in SoundTrigger.KeyphraseRecognitionEvent recognitionEvent);
+ void onDetected(in SoundTrigger.RecognitionEvent recognitionEvent);
+
/**
* Called when the detection fails due to an error.
*
@@ -42,4 +45,4 @@
* Called when the recognition is resumed after it was temporarily paused.
*/
void onRecognitionResumed();
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
index e16ea71..fec64ea 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
@@ -18,8 +18,11 @@
parcelable SoundTrigger.ConfidenceLevel;
parcelable SoundTrigger.Keyphrase;
+parcelable SoundTrigger.RecognitionEvent;
parcelable SoundTrigger.KeyphraseRecognitionEvent;
+parcelable SoundTrigger.GenericSoundRecognitionEvent;
parcelable SoundTrigger.KeyphraseRecognitionExtra;
parcelable SoundTrigger.KeyphraseSoundModel;
+parcelable SoundTrigger.GenericSoundModel;
parcelable SoundTrigger.ModuleProperties;
parcelable SoundTrigger.RecognitionConfig;
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index d490409..882908a 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -195,6 +195,12 @@
/** Keyphrase sound model */
public static final int TYPE_KEYPHRASE = 0;
+ /**
+ * A generic sound model. Use this type only for non-keyphrase sound models such as
+ * ones that match a particular sound pattern.
+ */
+ public static final int TYPE_GENERIC_SOUND = 1;
+
/** Unique sound model identifier */
public final UUID uuid;
@@ -458,6 +464,63 @@
}
}
+
+ /*****************************************************************************
+ * A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound
+ * patterns.
+ ****************************************************************************/
+ public static class GenericSoundModel extends SoundModel implements Parcelable {
+
+ public static final Parcelable.Creator<GenericSoundModel> CREATOR
+ = new Parcelable.Creator<GenericSoundModel>() {
+ public GenericSoundModel createFromParcel(Parcel in) {
+ return GenericSoundModel.fromParcel(in);
+ }
+
+ public GenericSoundModel[] newArray(int size) {
+ return new GenericSoundModel[size];
+ }
+ };
+
+ public GenericSoundModel(UUID uuid, UUID vendorUuid, byte[] data) {
+ super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private static GenericSoundModel fromParcel(Parcel in) {
+ UUID uuid = UUID.fromString(in.readString());
+ UUID vendorUuid = null;
+ int length = in.readInt();
+ if (length >= 0) {
+ vendorUuid = UUID.fromString(in.readString());
+ }
+ byte[] data = in.readBlob();
+ return new GenericSoundModel(uuid, vendorUuid, data);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(uuid.toString());
+ if (vendorUuid == null) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(vendorUuid.toString().length());
+ dest.writeString(vendorUuid.toString());
+ }
+ dest.writeBlob(data);
+ }
+
+ @Override
+ public String toString() {
+ return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid
+ + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+ }
+ }
+
/**
* Modes for key phrase recognition
*/
@@ -1019,6 +1082,21 @@
}
/**
+ * Sub-class of RecognitionEvent specifically for sound-trigger based sound
+ * models(non-keyphrase). Currently does not contain any additional fields.
+ */
+ public static class GenericRecognitionEvent extends RecognitionEvent {
+ public GenericRecognitionEvent(int status, int soundModelHandle,
+ boolean captureAvailable, int captureSession, int captureDelayMs,
+ int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat,
+ byte[] data) {
+ super(status, soundModelHandle, captureAvailable, captureSession,
+ captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
+ data);
+ }
+ }
+
+ /**
* Status codes for {@link SoundModelEvent}
*/
/** Sound Model was updated */
@@ -1118,7 +1196,7 @@
public static final int SERVICE_STATE_DISABLED = 1;
/**
- * Returns a list of descriptors for all harware modules loaded.
+ * Returns a list of descriptors for all hardware modules loaded.
* @param modules A ModuleProperties array where the list will be returned.
* @return - {@link #STATUS_OK} in case of success
* - {@link #STATUS_ERROR} in case of unspecified error
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index f765336..58a0269 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -91,4 +91,19 @@
* the icon is in this method.
*/
public abstract void setUserIcon(int userId, Bitmap bitmap);
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to inform the
+ * user manager whether all users should be created ephemeral.
+ */
+ public abstract void setForceEphemeralUsers(boolean forceEphemeralUsers);
+
+ /**
+ * Switches to the system user and deletes all other users.
+ *
+ * <p>Called by the {@link com.android.server.devicepolicy.DevicePolicyManagerService} when
+ * the force-ephemeral-users policy is toggled on to make sure there are no pre-existing
+ * non-ephemeral users left.
+ */
+ public abstract void removeAllUsers();
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index ac7d539..76a401d 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -31,6 +31,7 @@
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
import android.media.AudioFormat;
import android.os.AsyncTask;
import android.os.Handler;
@@ -616,7 +617,11 @@
}
@Override
- public void onDetected(KeyphraseRecognitionEvent event) {
+ public void onDetected(RecognitionEvent event) {
+ if (! (event instanceof KeyphraseRecognitionEvent)) {
+ Slog.e(TAG, "onDetected() called for a soundtrigger event.");
+ return;
+ }
if (DBG) {
Slog.d(TAG, "onDetected(" + event + ")");
} else {
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 3b9b8db..ec53a2e 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -121,4 +121,7 @@
void setBatteryState(int status, int health, int plugType, int level, int temp, int volt);
long getAwakeTimeBattery();
long getAwakeTimePlugged();
+
+ void noteBleScanStarted(in WorkSource ws);
+ void noteBleScanStopped(in WorkSource ws);
}
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
new file mode 100644
index 0000000..9de4a6c
--- /dev/null
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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 com.android.internal.app;
+
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.os.ParcelUuid;
+
+/**
+ * Service interface for a generic sound recognition model.
+ * @hide
+ */
+interface ISoundTriggerService {
+
+
+ SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId);
+
+ void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel);
+
+ void deleteSoundModel(in ParcelUuid soundModelId);
+
+ void startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+
+ /**
+ * Stops recognition.
+ */
+ void stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 56c3fc8..5a2f601 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2860,6 +2860,12 @@
<permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"
android:protectionLevel="signature|privileged" />
+ <!-- Must be required by system/priv apps when accessing the sound trigger
+ APIs given by {@link SoundTriggerManager}.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
+ android:protectionLevel="signature|privileged" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
new file mode 100644
index 0000000..ebba343
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -0,0 +1,180 @@
+/**
+ * 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.media.soundtrigger;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.app.ISoundTriggerService;
+
+import java.io.PrintWriter;
+import java.util.UUID;
+
+/**
+ * A class that allows interaction with the actual sound trigger detection on the system.
+ * Sound trigger detection refers to a detectors that match generic sound patterns that are
+ * not voice-based. The voice-based recognition models should utilize the {@link
+ * VoiceInteractionService} instead. Access to this class is protected by a permission
+ * granted only to system or privileged apps.
+ *
+ * @hide
+ */
+public final class SoundTriggerDetector {
+ private static final boolean DBG = false;
+ private static final String TAG = "SoundTriggerDetector";
+
+ private final Object mLock = new Object();
+
+ private final ISoundTriggerService mSoundTriggerService;
+ private final UUID mSoundModelId;
+ private final Callback mCallback;
+ private final Handler mHandler;
+ private final RecognitionCallback mRecognitionCallback;
+
+ public abstract class Callback {
+ /**
+ * Called when the availability of the sound model changes.
+ */
+ public abstract void onAvailabilityChanged(int status);
+
+ /**
+ * Called when the sound model has triggered (such as when it matched a
+ * given sound pattern).
+ */
+ public abstract void onDetected();
+
+ /**
+ * Called when the detection fails due to an error.
+ */
+ public abstract void onError();
+
+ /**
+ * Called when the recognition is paused temporarily for some reason.
+ * This is an informational callback, and the clients shouldn't be doing anything here
+ * except showing an indication on their UI if they have to.
+ */
+ public abstract void onRecognitionPaused();
+
+ /**
+ * Called when the recognition is resumed after it was temporarily paused.
+ * This is an informational callback, and the clients shouldn't be doing anything here
+ * except showing an indication on their UI if they have to.
+ */
+ public abstract void onRecognitionResumed();
+ }
+
+ /**
+ * This class should be constructed by the {@link SoundTriggerManager}.
+ * @hide
+ */
+ SoundTriggerDetector(ISoundTriggerService soundTriggerService, UUID soundModelId,
+ @NonNull Callback callback, @Nullable Handler handler) {
+ mSoundTriggerService = soundTriggerService;
+ mSoundModelId = soundModelId;
+ mCallback = callback;
+ if (handler == null) {
+ mHandler = new Handler();
+ } else {
+ mHandler = handler;
+ }
+ mRecognitionCallback = new RecognitionCallback();
+ }
+
+ /**
+ * Starts recognition on the associated sound model. Result is indicated via the
+ * {@link Callback}.
+ * @return Indicates whether the call succeeded or not.
+ */
+ public boolean startRecognition() {
+ if (DBG) {
+ Slog.d(TAG, "startRecognition()");
+ }
+ try {
+ mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
+ mRecognitionCallback);
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Stops recognition for the associated model.
+ */
+ public boolean stopRecognition() {
+ try {
+ mSoundTriggerService.stopRecognition(new ParcelUuid(mSoundModelId),
+ mRecognitionCallback);
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ synchronized (mLock) {
+ // TODO: Dump useful debug information.
+ }
+ }
+
+ /**
+ * Callback that handles events from the lower sound trigger layer.
+ * @hide
+ */
+ private static class RecognitionCallback extends
+ IRecognitionStatusCallback.Stub {
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onDetected(SoundTrigger.RecognitionEvent event) {
+ Slog.e(TAG, "onDetected()" + event);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onError(int status) {
+ Slog.e(TAG, "onError()" + status);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onRecognitionPaused() {
+ Slog.e(TAG, "onRecognitionPaused()");
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onRecognitionResumed() {
+ Slog.e(TAG, "onRecognitionResumed()");
+ }
+ }
+}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
new file mode 100644
index 0000000..4ae8e72
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -0,0 +1,172 @@
+/**
+ * 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.media.soundtrigger;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.HashMap;
+import java.util.UUID;
+
+/**
+ * This class provides management of non-voice (general sound trigger) based sound recognition
+ * models. Usage of this class is restricted to system or signature applications only. This allows
+ * OEMs to write apps that can manage non-voice based sound trigger models.
+ *
+ * @hide
+ * TODO: Mark this as a SystemApi and get approval.
+ */
+public final class SoundTriggerManager {
+ private static final boolean DBG = false;
+ private static final String TAG = "SoundTriggerManager";
+
+ private final Context mContext;
+ private final ISoundTriggerService mSoundTriggerService;
+
+ // Stores a mapping from the sound model UUID to the SoundTriggerInstance created by
+ // the createSoundTriggerDetector() call.
+ private final HashMap<UUID, SoundTriggerDetector> mReceiverInstanceMap;
+
+ /**
+ * @hide
+ */
+ public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService ) {
+ if (DBG) {
+ Slog.i(TAG, "SoundTriggerManager created.");
+ }
+ mSoundTriggerService = soundTriggerService;
+ mContext = context;
+ mReceiverInstanceMap = new HashMap<UUID, SoundTriggerDetector>();
+ }
+
+ /**
+ * Updates the given sound trigger model.
+ */
+ public void updateModel(Model model) {
+ try {
+ mSoundTriggerService.updateSoundModel(model.getGenericSoundModel());
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Returns the sound trigger model represented by the given UUID. An instance of {@link Model}
+ * is returned.
+ */
+ public Model getModel(UUID soundModelId) {
+ try {
+ return new Model(mSoundTriggerService.getSoundModel(
+ new ParcelUuid(soundModelId)));
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Deletes the sound model represented by the provided UUID.
+ */
+ public void deleteModel(UUID soundModelId) {
+ try {
+ mSoundTriggerService.deleteSoundModel(new ParcelUuid(soundModelId));
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Creates an instance of {@link SoundTriggerDetector} which can be used to start/stop
+ * recognition on the model and register for triggers from the model. Note that this call
+ * invalidates any previously returned instances for the same sound model Uuid.
+ *
+ * @param soundModelId UUID of the sound model to create the receiver object for.
+ * @param callback Instance of the {@link SoundTriggerDetector#Callback} object for the
+ * callbacks for the given sound model.
+ * @param handler The Handler to use for the callback operations. A null value will use the
+ * current thread's Looper.
+ * @return Instance of {@link SoundTriggerDetector} or null on error.
+ */
+ @Nullable
+ public SoundTriggerDetector createSoundTriggerDetector(UUID soundModelId,
+ @NonNull SoundTriggerDetector.Callback callback, @Nullable Handler handler) {
+ if (soundModelId == null) {
+ return null;
+ }
+
+ SoundTriggerDetector oldInstance = mReceiverInstanceMap.get(soundModelId);
+ if (oldInstance != null) {
+ // Shutdown old instance.
+ }
+ SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerService,
+ soundModelId, callback, handler);
+ mReceiverInstanceMap.put(soundModelId, newInstance);
+ return newInstance;
+ }
+
+ /**
+ * Class captures the data and fields that represent a non-keyphrase sound model. Use the
+ * factory constructor {@link Model#create()} to create an instance.
+ */
+ // We use encapsulation to expose the SoundTrigger.GenericSoundModel as a SystemApi. This
+ // prevents us from exposing SoundTrigger.GenericSoundModel as an Api.
+ public static class Model {
+
+ private SoundTrigger.GenericSoundModel mGenericSoundModel;
+
+ /**
+ * @hide
+ */
+ Model(SoundTrigger.GenericSoundModel soundTriggerModel) {
+ mGenericSoundModel = soundTriggerModel;
+ }
+
+ /**
+ * Factory constructor to create a SoundModel instance for use with methods in this
+ * class.
+ */
+ public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) {
+ return new Model(new SoundTrigger.GenericSoundModel(modelUuid,
+ vendorUuid, data));
+ }
+
+ public UUID getModelUuid() {
+ return mGenericSoundModel.uuid;
+ }
+
+ public UUID getVendorUuid() {
+ return mGenericSoundModel.vendorUuid;
+ }
+
+ public byte[] getModelData() {
+ return mGenericSoundModel.data;
+ }
+
+ /**
+ * @hide
+ */
+ SoundTrigger.GenericSoundModel getGenericSoundModel() {
+ return mGenericSoundModel;
+ }
+ }
+}
diff --git a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
new file mode 100644
index 0000000..b4144a3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2016 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="40.0"
+ android:viewportHeight="40.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M19.0,6.41L17.59,5.0 12.0,10.59 6.41,5.0 5.0,6.41 10.59,12.0 5.0,17.59 6.41,19.0 12.0,13.41 17.59,19.0 19.0,17.59 13.41,12.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml b/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml
new file mode 100644
index 0000000..4e2a024
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2016 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17.0dp"
+ android:height="17.0dp"
+ android:viewportWidth="40.0"
+ android:viewportHeight="40.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M19.0,6.41L17.59,5.0 12.0,10.59 6.41,5.0 5.0,6.41 10.59,12.0 5.0,17.59 6.41,19.0 12.0,13.41 17.59,19.0 19.0,17.59 13.41,12.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 02158ac..a490635 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -359,6 +359,9 @@
<!-- Content description of the data connection with no SIM for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_no_sim">No SIM.</string>
+ <!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_cell_data_off">Cellular Data Off</string>
+
<!-- Content description of the bluetooth tethering icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_tether">Bluetooth tethering.</string>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index ad8e3bd..8fd4d9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -206,7 +206,8 @@
// Show icon in QS when we are connected or need to show roaming.
boolean showDataIcon = mCurrentState.dataConnected
- || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
+ || mCurrentState.iconGroup == TelephonyIcons.ROAMING
+ || mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED;
IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,
getCurrentIconId(), contentDescription);
@@ -227,7 +228,8 @@
&& !mCurrentState.carrierNetworkChangeMode
&& mCurrentState.activityOut;
showDataIcon &= mCurrentState.isDefault
- || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
+ || mCurrentState.iconGroup == TelephonyIcons.ROAMING
+ || mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED;
int typeIcon = showDataIcon ? icons.mDataType : 0;
mCallbackHandler.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
activityIn, activityOut, dataContentDescription, description, icons.mIsWide,
@@ -385,6 +387,8 @@
mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
} else if (isRoaming()) {
mCurrentState.iconGroup = TelephonyIcons.ROAMING;
+ } else if (isDataDisabled()) {
+ mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
}
if (isEmergencyOnly() != mCurrentState.isEmergency) {
mCurrentState.isEmergency = isEmergencyOnly();
@@ -399,6 +403,10 @@
notifyListenersIfNecessary();
}
+ private boolean isDataDisabled() {
+ return !mPhone.getDataEnabled(mSubscriptionInfo.getSubscriptionId());
+ }
+
@VisibleForTesting
void setActivity(int activity) {
mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 83e0446..6ff8f77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -208,6 +208,8 @@
static final int ICON_CARRIER_NETWORK_CHANGE =
R.drawable.stat_sys_signal_carrier_network_change_animation;
+ static final int ICON_DATA_DISABLED = R.drawable.stat_sys_data_disabled;
+
static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte;
static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g;
static final int QS_ICON_4G = R.drawable.ic_qs_signal_4g;
@@ -215,6 +217,8 @@
static final int QS_ICON_CARRIER_NETWORK_CHANGE =
R.drawable.ic_qs_signal_carrier_network_change_animation;
+ static final int QS_ICON_DATA_DISABLED = R.drawable.ic_qs_data_disabled;
+
static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
"CARRIER_NETWORK_CHANGE",
TelephonyIcons.TELEPHONY_CARRIER_NETWORK_CHANGE,
@@ -373,5 +377,20 @@
false,
TelephonyIcons.QS_DATA_R
);
+
+ static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
+ "DataDisabled",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_cell_data_off,
+ TelephonyIcons.ICON_DATA_DISABLED,
+ false,
+ TelephonyIcons.QS_ICON_DATA_DISABLED
+ );
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 3c13630..c3c3e3f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -745,7 +745,7 @@
mTestMode = SystemProperties.get("cm.test.mode").equals("true")
&& SystemProperties.get("ro.build.type").equals("eng");
- mTethering = new Tethering(mContext, mNetd, statsService, mHandler.getLooper());
+ mTethering = new Tethering(mContext, mNetd, statsService);
mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 82862e8..0f7dff27 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -855,6 +855,18 @@
}
}
+ @Override
+ public void noteBleScanStarted(WorkSource ws) {
+ enforceCallingPermission();
+ Slog.d(TAG, "BLE scan started for " + ws);
+ }
+
+ @Override
+ public void noteBleScanStopped(WorkSource ws) {
+ enforceCallingPermission();
+ Slog.d(TAG, "BLE scan stopped for " + ws);
+ }
+
public boolean isOnBattery() {
return mStats.isOnBattery();
}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 6648efd..a73a67a 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -153,11 +153,10 @@
// when RNDIS is enabled
public Tethering(Context context, INetworkManagementService nmService,
- INetworkStatsService statsService, Looper looper) {
+ INetworkStatsService statsService) {
mContext = context;
mNMService = nmService;
mStatsService = statsService;
- mLooper = looper;
mPublicSync = new Object();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fc79849..0f614ca 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,9 +25,11 @@
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -302,6 +304,12 @@
private final LockPatternUtils mLockPatternUtils;
+ /**
+ * Whether all users should be created ephemeral.
+ */
+ @GuardedBy("mUsersLock")
+ private boolean mForceEphemeralUsers;
+
private static UserManagerService sInstance;
public static UserManagerService getInstance() {
@@ -1836,23 +1844,25 @@
}
}
- // Add ephemeral flag to guests if required. Also inherit it from parent.
+ userId = getNextAvailableId();
+ Environment.getUserSystemDirectory(userId).mkdirs();
boolean ephemeralGuests = Resources.getSystem()
.getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
- if ((isGuest && ephemeralGuests)
- || (parent != null && parent.info.isEphemeral())) {
- flags |= UserInfo.FLAG_EPHEMERAL;
- }
- userId = getNextAvailableId();
- userInfo = new UserInfo(userId, name, null, flags);
- userInfo.serialNumber = mNextSerialNumber++;
- long now = System.currentTimeMillis();
- userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
- userInfo.partial = true;
- userData = new UserData();
- userData.info = userInfo;
- Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+
synchronized (mUsersLock) {
+ // Add ephemeral flag to guests/users if required. Also inherit it from parent.
+ if ((isGuest && ephemeralGuests) || mForceEphemeralUsers
+ || (parent != null && parent.info.isEphemeral())) {
+ flags |= UserInfo.FLAG_EPHEMERAL;
+ }
+
+ userInfo = new UserInfo(userId, name, null, flags);
+ userInfo.serialNumber = mNextSerialNumber++;
+ long now = System.currentTimeMillis();
+ userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
+ userInfo.partial = true;
+ userData = new UserData();
+ userData.info = userInfo;
mUsers.put(userId, userData);
}
writeUserListLP();
@@ -2914,6 +2924,61 @@
Binder.restoreCallingIdentity(ident);
}
}
+
+ @Override
+ public void setForceEphemeralUsers(boolean forceEphemeralUsers) {
+ synchronized (mUsersLock) {
+ mForceEphemeralUsers = forceEphemeralUsers;
+ }
+ }
+
+ @Override
+ public void removeAllUsers() {
+ if (UserHandle.USER_SYSTEM == ActivityManager.getCurrentUser()) {
+ // Remove the non-system users straight away.
+ removeNonSystemUsers();
+ } else {
+ // Switch to the system user first and then remove the other users.
+ BroadcastReceiver userSwitchedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int userId =
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId != UserHandle.USER_SYSTEM) {
+ return;
+ }
+ mContext.unregisterReceiver(this);
+ removeNonSystemUsers();
+ }
+ };
+ IntentFilter userSwitchedFilter = new IntentFilter();
+ userSwitchedFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiver(
+ userSwitchedReceiver, userSwitchedFilter, null, mHandler);
+
+ // Switch to the system user.
+ ActivityManager am =
+ (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ am.switchUser(UserHandle.USER_SYSTEM);
+ }
+ }
+ }
+
+ /* Remove all the users except of the system one. */
+ private void removeNonSystemUsers() {
+ ArrayList<UserInfo> usersToRemove = new ArrayList<>();
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i).info;
+ if (ui.id != UserHandle.USER_SYSTEM) {
+ usersToRemove.add(ui);
+ }
+ }
+ }
+ for (UserInfo ui: usersToRemove) {
+ removeUser(ui.id);
+ }
}
private class Shell extends ShellCommand {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4fd8990..fe62d15 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -471,6 +471,7 @@
private static final String TAG_DISABLE_SCREEN_CAPTURE = "disable-screen-capture";
private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management";
private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time";
+ private static final String TAG_FORCE_EPHEMERAL_USERS = "force_ephemeral_users";
private static final String TAG_ACCOUNT_TYPE = "account-type";
private static final String TAG_PERMITTED_ACCESSIBILITY_SERVICES
= "permitted-accessiblity-services";
@@ -559,6 +560,7 @@
boolean disableBluetoothContactSharing = true;
boolean disableScreenCapture = false; // Can only be set by a device/profile owner.
boolean requireAutoTime = false; // Can only be set by a device owner.
+ boolean forceEphemeralUsers = false; // Can only be set by a device owner.
ActiveAdmin parentAdmin;
final boolean isParent;
@@ -749,6 +751,11 @@
out.attribute(null, ATTR_VALUE, Boolean.toString(requireAutoTime));
out.endTag(null, TAG_REQUIRE_AUTO_TIME);
}
+ if (forceEphemeralUsers) {
+ out.startTag(null, TAG_FORCE_EPHEMERAL_USERS);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(forceEphemeralUsers));
+ out.endTag(null, TAG_FORCE_EPHEMERAL_USERS);
+ }
if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
out.startTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
out.attribute(null, ATTR_VALUE, Integer.toString(disabledKeyguardFeatures));
@@ -919,7 +926,10 @@
disableScreenCapture = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_REQUIRE_AUTO_TIME.equals(tag)) {
- requireAutoTime= Boolean.parseBoolean(
+ requireAutoTime = Boolean.parseBoolean(
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_FORCE_EPHEMERAL_USERS.equals(tag)) {
+ forceEphemeralUsers = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) {
disabledKeyguardFeatures = Integer.parseInt(
@@ -1150,6 +1160,8 @@
pw.println(disableScreenCapture);
pw.print(prefix); pw.print("requireAutoTime=");
pw.println(requireAutoTime);
+ pw.print(prefix); pw.print("forceEphemeralUsers=");
+ pw.println(forceEphemeralUsers);
pw.print(prefix); pw.print("disabledKeyguardFeatures=");
pw.println(disabledKeyguardFeatures);
pw.print(prefix); pw.print("crossProfileWidgetProviders=");
@@ -2408,6 +2420,14 @@
if (packageList != null) {
mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
}
+
+ synchronized (this) {
+ // push the force-ephemeral-users policy to the user manager.
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null) {
+ mUserManagerInternal.setForceEphemeralUsers(deviceOwner.forceEphemeralUsers);
+ }
+ }
}
private void ensureDeviceOwnerUserStarted() {
@@ -4789,6 +4809,46 @@
}
}
+ @Override
+ public void setForceEphemeralUsers(ComponentName who, boolean forceEphemeralUsers) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ boolean removeAllUsers = false;
+ synchronized (this) {
+ final ActiveAdmin deviceOwner =
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ if (deviceOwner.forceEphemeralUsers != forceEphemeralUsers) {
+ deviceOwner.forceEphemeralUsers = forceEphemeralUsers;
+ saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+ mUserManagerInternal.setForceEphemeralUsers(forceEphemeralUsers);
+ removeAllUsers = forceEphemeralUsers;
+ }
+ }
+ if (removeAllUsers) {
+ long identitity = mInjector.binderClearCallingIdentity();
+ try {
+ mUserManagerInternal.removeAllUsers();
+ } finally {
+ mInjector.binderRestoreCallingIdentity(identitity);
+ }
+ }
+ }
+
+ @Override
+ public boolean getForceEphemeralUsers(ComponentName who) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ synchronized (this) {
+ final ActiveAdmin deviceOwner =
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ return deviceOwner.forceEphemeralUsers;
+ }
+ }
+
private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException {
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
@@ -5319,6 +5379,8 @@
if (admin != null) {
admin.disableCamera = false;
admin.userRestrictions = null;
+ admin.forceEphemeralUsers = false;
+ mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers);
}
clearUserPoliciesLocked(new UserHandle(UserHandle.USER_SYSTEM));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c186a12..361c251 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -84,6 +84,7 @@
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.restrictions.RestrictionsManagerService;
+import com.android.server.soundtrigger.SoundTriggerService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
@@ -959,6 +960,8 @@
mSystemServiceManager.startService(JobSchedulerService.class);
+ mSystemServiceManager.startService(SoundTriggerService.class);
+
if (!disableNonCoreServices) {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
new file mode 100644
index 0000000..18a5d59
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
@@ -0,0 +1,144 @@
+/**
+ * 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 com.android.server.soundtrigger;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.util.Locale;
+import java.util.UUID;
+
+/**
+ * Helper to manage the database of the sound models that have been registered on the device.
+ *
+ * @hide
+ */
+public class SoundTriggerDbHelper extends SQLiteOpenHelper {
+ static final String TAG = "SoundTriggerDbHelper";
+ static final boolean DBG = false;
+
+ private static final String NAME = "st_sound_model.db";
+ private static final int VERSION = 1;
+
+ // Sound trigger-based sound models.
+ public static interface GenericSoundModelContract {
+ public static final String TABLE = "st_sound_model";
+ public static final String KEY_MODEL_UUID = "model_uuid";
+ public static final String KEY_VENDOR_UUID = "vendor_uuid";
+ public static final String KEY_DATA = "data";
+ }
+
+
+ // Table Create Statement for the sound trigger table
+ private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
+ + GenericSoundModelContract.TABLE + "("
+ + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+ + GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
+
+
+ public SoundTriggerDbHelper(Context context) {
+ super(context, NAME, null, VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // creating required tables
+ db.execSQL(CREATE_TABLE_ST_SOUND_MODEL);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO: For now, drop older tables and recreate new ones.
+ db.execSQL("DROP TABLE IF EXISTS " + GenericSoundModelContract.TABLE);
+ onCreate(db);
+ }
+
+ /**
+ * Updates the given sound trigger model, adds it, if it doesn't already exist.
+ *
+ */
+ public boolean updateGenericSoundModel(GenericSoundModel soundModel) {
+ synchronized(this) {
+ SQLiteDatabase db = getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString());
+ values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString());
+ values.put(GenericSoundModelContract.KEY_DATA, soundModel.data);
+
+ try {
+ return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE) != -1;
+ } finally {
+ db.close();
+ }
+
+ }
+ }
+
+ public GenericSoundModel getGenericSoundModel(UUID model_uuid) {
+ synchronized(this) {
+
+ // Find the corresponding sound model ID for the keyphrase.
+ String selectQuery = "SELECT * FROM " + GenericSoundModelContract.TABLE
+ + " WHERE " + GenericSoundModelContract.KEY_MODEL_UUID + "= '" +
+ model_uuid + "'";
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery(selectQuery, null);
+ try {
+ if (c.moveToFirst()) {
+ do {
+ byte[] data = c.getBlob(c.getColumnIndex(
+ GenericSoundModelContract.KEY_DATA));
+ String vendor_uuid = c.getString(
+ c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID));
+ return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid),
+ data);
+ } while (c.moveToNext());
+ }
+ } finally {
+ c.close();
+ db.close();
+ }
+ }
+ return null;
+ }
+
+ public boolean deleteGenericSoundModel(UUID model_uuid) {
+ synchronized(this) {
+ GenericSoundModel soundModel = getGenericSoundModel(model_uuid);
+ if (soundModel == null) {
+ return false;
+ }
+ // Delete all sound models for the given keyphrase and specified user.
+ SQLiteDatabase db = getWritableDatabase();
+ String soundModelClause = GenericSoundModelContract.KEY_MODEL_UUID
+ + "='" + soundModel.uuid.toString() + "'";
+ try {
+ return db.delete(GenericSoundModelContract.TABLE, soundModelClause, null) != 0;
+ } finally {
+ db.close();
+ }
+ }
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
similarity index 99%
rename from services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
rename to services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 31d859f..597f915ec 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.voiceinteraction;
+package com.android.server.soundtrigger;
import android.content.BroadcastReceiver;
import android.content.Context;
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
new file mode 100644
index 0000000..0a06bfa
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -0,0 +1,84 @@
+/**
+ * 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 com.android.server.soundtrigger;
+
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
+import android.hardware.soundtrigger.SoundTriggerModule;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Provides a local service for managing voice-related recoginition models. This is primarily used
+ * by the {@link VoiceInteractionManagerService}.
+ */
+public abstract class SoundTriggerInternal {
+ /**
+ * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
+ * IRecognitionStatusCallback, RecognitionConfig)},
+ * {@link #stopRecognition(int, IRecognitionStatusCallback)}
+ */
+ public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
+ public static final int STATUS_OK = SoundTrigger.STATUS_OK;
+
+ /** The {@link ModuleProperties} for the system, or null if none exists. */
+ private ModuleProperties moduleProperties;
+
+ /**
+ * Starts recognition for the given keyphraseId.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * the recognition is to be started.
+ * @param soundModel The sound model to use for recognition.
+ * @param listener The listener for the recognition events related to the given keyphrase.
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ public abstract int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
+ IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig);
+
+ /**
+ * Stops recognition for the given {@link Keyphrase} if a recognition is
+ * currently active.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * the recognition is to be stopped.
+ * @param listener The listener for the recognition events related to the given keyphrase.
+ *
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ public abstract int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener);
+
+ /**
+ * Stops all recognitions active currently and clears the internal state.
+ */
+ public abstract void stopAllRecognitions();
+
+ public ModuleProperties getModuleProperties() {
+ return moduleProperties;
+ }
+
+ public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
new file mode 100644
index 0000000..10c3395
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -0,0 +1,194 @@
+/*
+ * 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 com.android.server.soundtrigger;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.Manifest;
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.hardware.soundtrigger.SoundTrigger.SoundTriggerModel;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+import com.android.internal.app.ISoundTriggerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.UUID;
+
+/**
+ * A single SystemService to manage all sound/voice-based sound models on the DSP.
+ * This services provides apis to manage sound trigger-based sound models via
+ * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
+ * the functionality provided by {@link SoundTriggerHelper} for use by
+ * {@link VoiceInteractionManagerService}.
+ *
+ * @hide
+ */
+public class SoundTriggerService extends SystemService {
+ static final String TAG = "SoundTriggerService";
+ static final boolean DEBUG = false;
+
+ final Context mContext;
+ private final SoundTriggerServiceStub mServiceStub;
+ private final LocalSoundTriggerService mLocalSoundTriggerService;
+ private SoundTriggerDbHelper mDbHelper;
+
+ public SoundTriggerService(Context context) {
+ super(context);
+ mContext = context;
+ mServiceStub = new SoundTriggerServiceStub();
+ mLocalSoundTriggerService = new LocalSoundTriggerService(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
+ publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (PHASE_SYSTEM_SERVICES_READY == phase) {
+ mLocalSoundTriggerService.initSoundTriggerHelper();
+ } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
+ mDbHelper = new SoundTriggerDbHelper(mContext);
+ }
+ }
+
+ @Override
+ public void onStartUser(int userHandle) {
+ }
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ }
+
+ class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ // The activity manager only throws security exceptions, so let's
+ // log all others.
+ if (!(e instanceof SecurityException)) {
+ Slog.wtf(TAG, "SoundTriggerService Crash", e);
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public void startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+ enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+ if (DEBUG) {
+ Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
+ }
+ }
+
+ @Override
+ public void stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+ enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+ if (DEBUG) {
+ Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
+ }
+ }
+
+ @Override
+ public SoundTrigger.SoundTriggerModel getSoundModel(ParcelUuid soundModelId) {
+ enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+ if (DEBUG) {
+ Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
+ }
+ SoundTrigger.SoundTriggerModel model = mDbHelper.getSoundTriggerModel(soundModelId.getUuid());
+ if (model == null) {
+ Slog.e(TAG, "Null model in database.");
+ }
+ return model;
+ }
+
+ @Override
+ public void updateSoundModel(SoundTrigger.SoundTriggerModel soundModel) {
+ enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+ if (DEBUG) {
+ Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
+ }
+ mDbHelper.updateSoundTriggerModel(soundModel);
+ }
+
+ @Override
+ public void deleteSoundModel(ParcelUuid soundModelId) {
+ enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+ if (DEBUG) {
+ Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
+ }
+ mDbHelper.deleteSoundTriggerModel(soundModelId.getUuid());
+ }
+ }
+
+ public final class LocalSoundTriggerService extends SoundTriggerInternal {
+ private final Context mContext;
+ private SoundTriggerHelper mSoundTriggerHelper;
+
+ LocalSoundTriggerService(Context context) {
+ mContext = context;
+ }
+
+ void initSoundTriggerHelper() {
+ if (mSoundTriggerHelper == null) {
+ mSoundTriggerHelper = new SoundTriggerHelper(mContext);
+ }
+ }
+
+ @Override
+ public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
+ IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
+ return mSoundTriggerHelper.startRecognition(keyphraseId, soundModel, listener,
+ recognitionConfig);
+ }
+
+ @Override
+ public int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
+ return mSoundTriggerHelper.stopRecognition(keyphraseId, listener);
+ }
+
+ @Override
+ public void stopAllRecognitions() {
+ mSoundTriggerHelper.stopAllRecognitions();
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mSoundTriggerHelper.dump(fd, pw, args);
+ }
+ }
+
+ private void enforceCallingPermission(String permission) {
+ if (mContext.checkCallingOrSelfPermission(permission)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold the permission " + permission);
+ }
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 2aef109..4a54643 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -62,6 +62,7 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
+import com.android.server.soundtrigger.SoundTriggerInternal;
import com.android.server.SystemService;
import com.android.server.UiThread;
@@ -79,15 +80,14 @@
final Context mContext;
final ContentResolver mResolver;
final DatabaseHelper mDbHelper;
- final SoundTriggerHelper mSoundTriggerHelper;
final ActivityManagerInternal mAmInternal;
+ SoundTriggerInternal mSoundTriggerInternal;
public VoiceInteractionManagerService(Context context) {
super(context);
mContext = context;
mResolver = context.getContentResolver();
mDbHelper = new DatabaseHelper(context);
- mSoundTriggerHelper = new SoundTriggerHelper(context);
mServiceStub = new VoiceInteractionManagerServiceStub();
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -115,7 +115,9 @@
@Override
public void onBootPhase(int phase) {
- if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ if (PHASE_SYSTEM_SERVICES_READY == phase) {
+ mSoundTriggerInternal = LocalServices.getService(SoundTriggerInternal.class);
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mServiceStub.systemRunning(isSafeMode());
}
}
@@ -380,7 +382,7 @@
if (force || mImpl == null || mImpl.mUser != mCurUser
|| !mImpl.mComponent.equals(serviceComponent)) {
- mSoundTriggerHelper.stopAllRecognitions();
+ mSoundTriggerInternal.stopAllRecognitions();
if (mImpl != null) {
mImpl.shutdownLocked();
}
@@ -736,9 +738,9 @@
mImpl.notifySoundModelsChangedLocked();
}
}
- return SoundTriggerHelper.STATUS_OK;
+ return SoundTriggerInternal.STATUS_OK;
} else {
- return SoundTriggerHelper.STATUS_ERROR;
+ return SoundTriggerInternal.STATUS_ERROR;
}
} finally {
Binder.restoreCallingIdentity(caller);
@@ -759,7 +761,7 @@
boolean deleted = false;
try {
deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
- return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
+ return deleted ? SoundTriggerInternal.STATUS_OK : SoundTriggerInternal.STATUS_ERROR;
} finally {
if (deleted) {
synchronized (this) {
@@ -812,7 +814,7 @@
final long caller = Binder.clearCallingIdentity();
try {
- return mSoundTriggerHelper.moduleProperties;
+ return mSoundTriggerInternal.getModuleProperties();
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -845,9 +847,9 @@
|| soundModel.uuid == null
|| soundModel.keyphrases == null) {
Slog.w(TAG, "No matching sound model found in startRecognition");
- return SoundTriggerHelper.STATUS_ERROR;
+ return SoundTriggerInternal.STATUS_ERROR;
} else {
- return mSoundTriggerHelper.startRecognition(
+ return mSoundTriggerInternal.startRecognition(
keyphraseId, soundModel, callback, recognitionConfig);
}
} finally {
@@ -869,7 +871,7 @@
final long caller = Binder.clearCallingIdentity();
try {
- return mSoundTriggerHelper.stopRecognition(keyphraseId, callback);
+ return mSoundTriggerInternal.stopRecognition(keyphraseId, callback);
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -1011,7 +1013,7 @@
}
mImpl.dumpLocked(fd, pw, args);
}
- mSoundTriggerHelper.dump(fd, pw, args);
+ mSoundTriggerInternal.dump(fd, pw, args);
}
private void enforceCallingPermission(String permission) {
@@ -1060,7 +1062,7 @@
// The user is force stopping our current interactor/recognizer.
// Clear the current settings and restore default state.
synchronized (VoiceInteractionManagerService.this) {
- mSoundTriggerHelper.stopAllRecognitions();
+ mSoundTriggerInternal.stopAllRecognitions();
if (mImpl != null) {
mImpl.shutdownLocked();
mImpl = null;
diff --git a/tests/SoundTriggerTestApp/Android.mk b/tests/SoundTriggerTestApp/Android.mk
new file mode 100644
index 0000000..7bcab5e
--- /dev/null
+++ b/tests/SoundTriggerTestApp/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SoundTriggerTestApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..40619da
--- /dev/null
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.soundtrigger">
+
+ <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
+ <application
+ android:permission="android.permission.MANAGE_SOUND_TRIGGER">
+ <activity
+ android:name="TestSoundTriggerActivity"
+ android:label="SoundTrigger Test Application"
+ android:theme="@android:style/Theme.Material.Light.Voice">
+ <intent-filter>
+ <action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
new file mode 100644
index 0000000..9d2b9d9
--- /dev/null
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 Google Inc.
+
+ 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/enroll"
+ android:onClick="onEnrollButtonClicked"
+ android:padding="20dp" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/reenroll"
+ android:onClick="onReEnrollButtonClicked"
+ android:padding="20dp" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/unenroll"
+ android:onClick="onUnEnrollButtonClicked"
+ android:padding="20dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
new file mode 100644
index 0000000..07bac2a
--- /dev/null
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 Google Inc.
+
+ 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="enroll">Enroll</string>
+ <string name="reenroll">Re-enroll</string>
+ <string name="unenroll">Un-enroll</string>
+</resources>
\ No newline at end of file
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
new file mode 100644
index 0000000..98713bd2
--- /dev/null
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.android.test.soundtrigger;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.SoundTriggerModel;
+import android.media.soundtrigger.SoundTriggerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.UUID;
+
+/**
+ * Utility class for the managing sound trigger sound models.
+ */
+public class SoundTriggerUtil {
+ private static final String TAG = "TestSoundTriggerUtil:Hotsound";
+
+ private final ISoundTriggerService mSoundTriggerService;
+ private final SoundTriggerManager mSoundTriggerManager;
+ private final Context mContext;
+
+ public SoundTriggerUtil(Context context) {
+ mSoundTriggerService = ISoundTriggerService.Stub.asInterface(
+ ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+ mSoundTriggerManager = (SoundTriggerManager) context.getSystemService(
+ Context.SOUND_TRIGGER_SERVICE);
+ mContext = context;
+ }
+
+ /**
+ * Adds/Updates a sound model.
+ * The sound model must contain a valid UUID.
+ *
+ * @param soundModel The sound model to add/update.
+ */
+ public boolean addOrUpdateSoundModel(SoundTriggerModel soundModel) {
+ try {
+ mSoundTriggerService.updateSoundModel(soundModel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in updateSoundModel", e);
+ }
+ return true;
+ }
+
+ public void addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) {
+ mSoundTriggerManager.updateModel(soundModel);
+ }
+
+ /**
+ * Gets the sound model for the given keyphrase, null if none exists.
+ * If a sound model for a given keyphrase exists, and it needs to be updated,
+ * it should be obtained using this method, updated and then passed in to
+ * {@link #addOrUpdateSoundModel(SoundTriggerModel)} without changing the IDs.
+ *
+ * @param modelId The model ID to look-up the sound model for.
+ * @return The sound model if one was found, null otherwise.
+ */
+ @Nullable
+ public SoundTriggerModel getSoundModel(UUID modelId) {
+ SoundTriggerModel model = null;
+ try {
+ model = mSoundTriggerService.getSoundModel(new ParcelUuid(modelId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in updateKeyphraseSoundModel");
+ }
+
+ if (model == null) {
+ Log.w(TAG, "No models present for the gien keyphrase ID");
+ return null;
+ } else {
+ return model;
+ }
+ }
+
+ /**
+ * Deletes the sound model for the given keyphrase id.
+ *
+ * @param modelId The model ID to look-up the sound model for.
+ * @return {@code true} if the call succeeds, {@code false} otherwise.
+ */
+ @Nullable
+ public boolean deleteSoundModel(UUID modelId) {
+ try {
+ mSoundTriggerService.deleteSoundModel(new ParcelUuid(modelId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in updateSoundModel");
+ }
+ return true;
+ }
+
+ public void deleteSoundModelUsingManager(UUID modelId) {
+ mSoundTriggerManager.deleteModel(modelId);
+ }
+}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
new file mode 100644
index 0000000..82890c1
--- /dev/null
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
@@ -0,0 +1,121 @@
+/*
+ * 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 com.android.test.soundtrigger;
+
+import java.util.Random;
+import java.util.UUID;
+
+import android.app.Activity;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.SoundTriggerModel;
+import android.media.soundtrigger.SoundTriggerManager;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+public class TestSoundTriggerActivity extends Activity {
+ private static final String TAG = "TestSoundTriggerActivity";
+ private static final boolean DBG = true;
+
+ private SoundTriggerUtil mSoundTriggerUtil;
+ private Random mRandom;
+ private UUID mModelUuid = UUID.randomUUID();
+ private UUID mModelUuid2 = UUID.randomUUID();
+ private UUID mVendorUuid = UUID.randomUUID();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ if (DBG) Log.d(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ mSoundTriggerUtil = new SoundTriggerUtil(this);
+ mRandom = new Random();
+ }
+
+ /**
+ * Called when the user clicks the enroll button.
+ * Performs a fresh enrollment.
+ */
+ public void onEnrollButtonClicked(View v) {
+ // Generate a fake model to push.
+ byte[] data = new byte[1024];
+ mRandom.nextBytes(data);
+ SoundTriggerModel model = new SoundTriggerModel(mModelUuid, mVendorUuid, data);
+
+ boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
+ if (status) {
+ Toast.makeText(
+ this, "Successfully created sound trigger model UUID=" + mModelUuid, Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(this, "Failed to enroll!!!" + mModelUuid, Toast.LENGTH_SHORT).show();
+ }
+
+ // Test the SoundManager API.
+ SoundTriggerManager.Model tmpModel = SoundTriggerManager.Model.create(mModelUuid2,
+ mVendorUuid, data);
+ mSoundTriggerUtil.addOrUpdateSoundModel(tmpModel);
+ }
+
+ /**
+ * Called when the user clicks the un-enroll button.
+ * Clears the enrollment information for the user.
+ */
+ public void onUnEnrollButtonClicked(View v) {
+ SoundTriggerModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+ if (soundModel == null) {
+ Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid);
+ if (status) {
+ Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
+ Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(this, "Failed to delete sound model!!!", Toast.LENGTH_SHORT).show();
+ }
+ mSoundTriggerUtil.deleteSoundModelUsingManager(mModelUuid2);
+ }
+
+ /**
+ * Called when the user clicks the re-enroll button.
+ * Uses the previously enrolled sound model and makes changes to it before pushing it back.
+ */
+ public void onReEnrollButtonClicked(View v) {
+ SoundTriggerModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+ if (soundModel == null) {
+ Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ // Generate a fake model to push.
+ byte[] data = new byte[2048];
+ mRandom.nextBytes(data);
+ SoundTriggerModel updated = new SoundTriggerModel(soundModel.uuid,
+ soundModel.vendorUuid, data);
+ boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated);
+ if (status) {
+ Toast.makeText(this, "Successfully re-enrolled, model UUID=" + updated.uuid,
+ Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(this, "Failed to re-enroll!!!", Toast.LENGTH_SHORT).show();
+ }
+ }
+}