Merge "Add CameraToo, a sample point-and-shoot camera app"
diff --git a/api/current.txt b/api/current.txt
index 80f83cd..cee6604 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1000,6 +1000,9 @@
field public static final int scrollbars = 16842974; // 0x10100de
field public static final int scrollingCache = 16843006; // 0x10100fe
field public static final deprecated int searchButtonText = 16843269; // 0x1010205
+ field public static final int searchKeyphrase = 16843874; // 0x1010462
+ field public static final int searchKeyphraseId = 16843873; // 0x1010461
+ field public static final int searchKeyphraseSupportedLocales = 16843875; // 0x1010463
field public static final int searchMode = 16843221; // 0x10101d5
field public static final int searchSettingsDescription = 16843402; // 0x101028a
field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
@@ -13681,6 +13684,7 @@
method public void adjustStreamVolume(int, int, int);
method public void adjustSuggestedStreamVolume(int, int, int);
method public void adjustVolume(int, int);
+ method public int allocateAudioSessionId();
method public void dispatchMediaKeyEvent(android.view.KeyEvent);
method public int getMode();
method public java.lang.String getParameters(java.lang.String);
@@ -13741,6 +13745,7 @@
field public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3; // 0xfffffffd
field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
+ field public static final int ERROR = -1; // 0xffffffff
field public static final java.lang.String EXTRA_RINGER_MODE = "android.media.EXTRA_RINGER_MODE";
field public static final java.lang.String EXTRA_SCO_AUDIO_PREVIOUS_STATE = "android.media.extra.SCO_AUDIO_PREVIOUS_STATE";
field public static final java.lang.String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE";
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index af27e1d..8b42bcd 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -361,7 +361,7 @@
RouteInfo target = (RouteInfo) obj;
- return Objects.equals(mDestination, target.getDestination()) &&
+ return Objects.equals(mDestination, target.getDestinationLinkAddress()) &&
Objects.equals(mGateway, target.getGateway()) &&
Objects.equals(mInterface, target.getInterface());
}
diff --git a/core/java/android/service/voice/DspInfo.java b/core/java/android/service/voice/DspInfo.java
new file mode 100644
index 0000000..0862309
--- /dev/null
+++ b/core/java/android/service/voice/DspInfo.java
@@ -0,0 +1,56 @@
+/*
+ * 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 java.util.UUID;
+
+/**
+ * Properties of the DSP hardware on the device.
+ * @hide
+ */
+public class DspInfo {
+ /**
+ * Unique voice engine Id (changes with each version).
+ */
+ public final UUID voiceEngineId;
+
+ /**
+ * Human readable voice detection engine implementor.
+ */
+ public final String voiceEngineImplementor;
+ /**
+ * Human readable voice detection engine description.
+ */
+ public final String voiceEngineDescription;
+ /**
+ * Human readable voice detection engine version
+ */
+ public final int voiceEngineVersion;
+ /**
+ * Rated power consumption when detection is active.
+ */
+ public final int powerConsumptionMw;
+
+ public DspInfo(UUID voiceEngineId, String voiceEngineImplementor,
+ String voiceEngineDescription, int version, int powerConsumptionMw) {
+ this.voiceEngineId = voiceEngineId;
+ this.voiceEngineImplementor = voiceEngineImplementor;
+ this.voiceEngineDescription = voiceEngineDescription;
+ this.voiceEngineVersion = version;
+ this.powerConsumptionMw = powerConsumptionMw;
+ }
+}
diff --git a/core/java/android/service/voice/KeyphraseEnrollmentInfo.java b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java
new file mode 100644
index 0000000..ebe41ce
--- /dev/null
+++ b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java
@@ -0,0 +1,246 @@
+/*
+ * 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.Slog;
+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><{@link
+ * android.R.styleable#VoiceEnrollmentApplication
+ * voice-enrollment-application}></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";
+ /**
+ * Intent extra: The intent extra for un-enrolling a user for a particular keyphrase.
+ */
+ public static final String EXTRA_VOICE_KEYPHRASE_UNENROLL =
+ "com.android.intent.extra.VOICE_KEYPHRASE_UNENROLL";
+ /**
+ * Intent extra: The hint text to be shown on the voice keyphrase management UI.
+ */
+ public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT =
+ "com.android.intent.extra.VOICE_KEYPHRASE_HINT_TEXT";
+ /**
+ * Intent extra: The voice locale to use while managing the keyphrase.
+ */
+ public static final String EXTRA_VOICE_KEYPHRASE_LOCALE =
+ "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE";
+
+ private KeyphraseInfo[] mKeyphrases;
+ private String mEnrollmentPackage;
+ 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.
+ Slog.w(TAG, ai.packageName + "is not 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.
+ Slog.w(TAG, ai.packageName + " does not require MANAGE_VOICE_KEYPHRASES");
+ continue;
+ }
+ mEnrollmentPackage = ai.packageName;
+ found = true;
+ break;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "error parsing voice enrollment meta-data", e);
+ }
+ }
+
+ if (!found) {
+ mKeyphrases = null;
+ 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;
+ Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+ return;
+ } catch (IOException e) {
+ mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
+ Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+ return;
+ } catch (PackageManager.NameNotFoundException e) {
+ mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
+ Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+ return;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ public String getParseError() {
+ return mParseError;
+ }
+
+ /**
+ * @return An array of available keyphrases that can be enrolled on the system.
+ * It may be null if no keyphrases can be enrolled.
+ */
+ public KeyphraseInfo[] getKeyphrases() {
+ return mKeyphrases;
+ }
+
+ /**
+ * Returns an intent to launch an activity that manages the given keyphrase
+ * for the locale.
+ *
+ * @param enroll Indicates if the intent should enroll the user or un-enroll them.
+ * @param keyphrase The keyphrase that the user needs to be enrolled to.
+ * @param locale The locale for which the enrollment needs to be performed.
+ * @return An {@link Intent} to manage the keyphrase. This can be null if managing the
+ * given keyphrase/locale combination isn't possible.
+ */
+ public Intent getManageKeyphraseIntent(boolean enroll, String keyphrase, String locale) {
+ if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) {
+ Slog.w(TAG, "No enrollment application exists");
+ return null;
+ }
+
+ if (isKeyphraseEnrollmentSupported(keyphrase, locale)) {
+ Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES)
+ .setPackage(mEnrollmentPackage)
+ .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase)
+ .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale);
+ if (!enroll) intent.putExtra(EXTRA_VOICE_KEYPHRASE_UNENROLL, true);
+ return intent;
+ }
+ return null;
+ }
+
+ /**
+ * Indicates if enrollment is supported for the given keyphrase & locale.
+ *
+ * @param keyphrase The keyphrase that the user needs to be enrolled to.
+ * @param locale The locale for which the enrollment needs to be performed.
+ * @return true, if an enrollment client supports the given keyphrase and the given locale.
+ */
+ public boolean isKeyphraseEnrollmentSupported(String keyphrase, String locale) {
+ if (mKeyphrases == null || mKeyphrases.length == 0) {
+ Slog.w(TAG, "Enrollment application doesn't support keyphrases");
+ return false;
+ }
+ for (KeyphraseInfo keyphraseInfo : mKeyphrases) {
+ // Check if the given keyphrase is supported in the locale provided by
+ // the enrollment application.
+ String supportedKeyphrase = keyphraseInfo.keyphrase;
+ if (supportedKeyphrase.equalsIgnoreCase(keyphrase)
+ && keyphraseInfo.supportedLocales.contains(locale)) {
+ return true;
+ }
+ }
+ Slog.w(TAG, "Enrollment application doesn't support the given keyphrase");
+ return false;
+ }
+}
diff --git a/core/java/android/service/voice/KeyphraseInfo.java b/core/java/android/service/voice/KeyphraseInfo.java
new file mode 100644
index 0000000..d266e1a
--- /dev/null
+++ b/core/java/android/service/voice/KeyphraseInfo.java
@@ -0,0 +1,27 @@
+package android.service.voice;
+
+import android.util.ArraySet;
+
+/**
+ * A Voice Keyphrase.
+ * @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);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales;
+ }
+}
diff --git a/core/java/android/service/voice/SoundTriggerManager.java b/core/java/android/service/voice/SoundTriggerManager.java
new file mode 100644
index 0000000..2d049b9
--- /dev/null
+++ b/core/java/android/service/voice/SoundTriggerManager.java
@@ -0,0 +1,73 @@
+/*
+ * 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.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
+
+import java.util.ArrayList;
+
+/**
+ * Manager for {@link SoundTrigger} APIs.
+ * Currently this just acts as an abstraction over all SoundTrigger API calls.
+ * @hide
+ */
+public class SoundTriggerManager {
+ /** The {@link DspInfo} for the system, or null if none exists. */
+ public DspInfo dspInfo;
+
+ public SoundTriggerManager() {
+ ArrayList <ModuleProperties> modules = new ArrayList<>();
+ int status = SoundTrigger.listModules(modules);
+ if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
+ // TODO(sansid, elaurent): Figure out how to handle errors in listing the modules here.
+ dspInfo = null;
+ } else {
+ // TODO(sansid, elaurent): Figure out how to determine which module corresponds to the
+ // DSP hardware.
+ ModuleProperties properties = modules.get(0);
+ dspInfo = new DspInfo(properties.uuid, properties.implementor, properties.description,
+ properties.version, properties.powerConsumptionMw);
+ }
+ }
+
+ /**
+ * @return True, if the keyphrase is supported on DSP for the given locale.
+ */
+ public boolean isKeyphraseSupported(String keyphrase, String locale) {
+ // TODO(sansid): We also need to look into a SoundTrigger API that let's us
+ // query this. For now just return supported if there's a DSP available.
+ return dspInfo != null;
+ }
+
+ /**
+ * @return True, if the keyphrase is has been enrolled for the given locale.
+ */
+ public boolean isKeyphraseEnrolled(String keyphrase, String locale) {
+ // TODO(sansid, elaurent): Query SoundTrigger to list currently loaded sound models.
+ // They have been enrolled.
+ return false;
+ }
+
+ /**
+ * @return True, if a recognition for the keyphrase is active for the given locale.
+ */
+ public boolean isKeyphraseActive(String keyphrase, String locale) {
+ // TODO(sansid, elaurent): Check if the recognition for the keyphrase is currently active.
+ return false;
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index e15489b..e0329f8 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -17,7 +17,6 @@
package android.service.voice;
import android.annotation.SdkConstant;
-import android.app.Instrumentation;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -25,8 +24,11 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractionManagerService;
+
/**
* Top-level service of the current global voice interactor, which is providing
* support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
@@ -51,6 +53,16 @@
public static final String SERVICE_INTERFACE =
"android.service.voice.VoiceInteractionService";
+ // TODO(sansid): Unhide these.
+ /** @hide */
+ public static final int KEYPHRASE_UNAVAILABLE = 0;
+ /** @hide */
+ public static final int KEYPHRASE_UNENROLLED = 1;
+ /** @hide */
+ public static final int KEYPHRASE_ENROLLED = 2;
+ /** @hide */
+ public static final int KEYPHRASE_ACTIVE = 3;
+
/**
* Name under which a VoiceInteractionService component publishes information about itself.
* This meta-data should reference an XML resource containing a
@@ -64,6 +76,9 @@
IVoiceInteractionManagerService mSystemService;
+ private SoundTriggerManager mSoundTriggerManager;
+ private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
+
public void startSession(Bundle args) {
try {
mSystemService.startSession(mInterface, args);
@@ -76,6 +91,8 @@
super.onCreate();
mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
+ mSoundTriggerManager = new SoundTriggerManager();
}
@Override
@@ -85,4 +102,44 @@
}
return null;
}
+
+ /**
+ * Gets the state of always-on hotword detection for the given keyphrase and locale
+ * on this system.
+ * Availability implies that the hardware on this system is capable of listening for
+ * the given keyphrase or not.
+ * The return code is one of {@link #KEYPHRASE_UNAVAILABLE}, {@link #KEYPHRASE_UNENROLLED}
+ * {@link #KEYPHRASE_ENROLLED} or {@link #KEYPHRASE_ACTIVE}.
+ *
+ * @param keyphrase The keyphrase whose availability is being checked.
+ * @param locale The locale for which the availability is being checked.
+ * @return Indicates if always-on hotword detection is available for the given keyphrase.
+ * TODO(sansid): Unhide this.
+ * @hide
+ */
+ public final int getAlwaysOnKeyphraseAvailability(String keyphrase, String locale) {
+ // The available keyphrases is a combination of DSP availability and
+ // the keyphrases that have an enrollment application for them.
+ if (!mSoundTriggerManager.isKeyphraseSupported(keyphrase, locale)
+ || !mKeyphraseEnrollmentInfo.isKeyphraseEnrollmentSupported(keyphrase, locale)) {
+ return KEYPHRASE_UNAVAILABLE;
+ }
+ if (!mSoundTriggerManager.isKeyphraseEnrolled(keyphrase, locale)) {
+ return KEYPHRASE_UNENROLLED;
+ }
+ if (!mSoundTriggerManager.isKeyphraseActive(keyphrase, locale)) {
+ return KEYPHRASE_ENROLLED;
+ } else {
+ return KEYPHRASE_ACTIVE;
+ }
+ }
+
+ /**
+ * @return Details of keyphrases available for enrollment.
+ * @hide
+ */
+ @VisibleForTesting
+ protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
+ return mKeyphraseEnrollmentInfo;
+ }
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index de46804..15dfed1 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -89,6 +89,7 @@
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
+ android/graphics/AndroidPicture.cpp \
android/graphics/AutoDecodeCancel.cpp \
android/graphics/Bitmap.cpp \
android/graphics/BitmapFactory.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 6c19b60..f2b9bac 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -244,9 +244,6 @@
mArgBlockLength(argBlockLength)
{
SkGraphics::Init();
- // this sets our preference for 16bit images during decode
- // in case the src is opaque and 24bit
- SkImageDecoder::SetDeviceConfig(SkBitmap::kRGB_565_Config);
// There is also a global font cache, but its budget is specified in code
// see SkFontHost_android.cpp
diff --git a/core/jni/android/graphics/AndroidPicture.cpp b/core/jni/android/graphics/AndroidPicture.cpp
new file mode 100644
index 0000000..5977ab2
--- /dev/null
+++ b/core/jni/android/graphics/AndroidPicture.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#include "AndroidPicture.h"
+#include "SkCanvas.h"
+#include "SkStream.h"
+
+AndroidPicture::AndroidPicture(const AndroidPicture* src) {
+ if (NULL != src) {
+ mWidth = src->width();
+ mHeight = src->height();
+ if (NULL != src->mPicture.get()) {
+ mPicture.reset(SkRef(src->mPicture.get()));
+ } if (NULL != src->mRecorder.get()) {
+ mPicture.reset(src->makePartialCopy());
+ }
+ } else {
+ mWidth = 0;
+ mHeight = 0;
+ }
+}
+
+SkCanvas* AndroidPicture::beginRecording(int width, int height) {
+ mPicture.reset(NULL);
+ mRecorder.reset(new SkPictureRecorder);
+ mWidth = width;
+ mHeight = height;
+ return mRecorder->beginRecording(width, height, NULL, 0);
+}
+
+void AndroidPicture::endRecording() {
+ if (NULL != mRecorder.get()) {
+ mPicture.reset(mRecorder->endRecording());
+ mRecorder.reset(NULL);
+ }
+}
+
+int AndroidPicture::width() const {
+ if (NULL != mPicture.get()) {
+ SkASSERT(mPicture->width() == mWidth);
+ SkASSERT(mPicture->height() == mHeight);
+ }
+
+ return mWidth;
+}
+
+int AndroidPicture::height() const {
+ if (NULL != mPicture.get()) {
+ SkASSERT(mPicture->width() == mWidth);
+ SkASSERT(mPicture->height() == mHeight);
+ }
+
+ return mHeight;
+}
+
+AndroidPicture* AndroidPicture::CreateFromStream(SkStream* stream) {
+ AndroidPicture* newPict = new AndroidPicture;
+
+ newPict->mPicture.reset(SkPicture::CreateFromStream(stream));
+ if (NULL != newPict->mPicture.get()) {
+ newPict->mWidth = newPict->mPicture->width();
+ newPict->mHeight = newPict->mPicture->height();
+ }
+
+ return newPict;
+}
+
+void AndroidPicture::serialize(SkWStream* stream) const {
+ if (NULL != mRecorder.get()) {
+ SkAutoTDelete<SkPicture> tempPict(this->makePartialCopy());
+ tempPict->serialize(stream);
+ } else if (NULL != mPicture.get()) {
+ mPicture->serialize(stream);
+ } else {
+ SkPicture empty;
+ empty.serialize(stream);
+ }
+}
+
+void AndroidPicture::draw(SkCanvas* canvas) {
+ if (NULL != mRecorder.get()) {
+ this->endRecording();
+ SkASSERT(NULL != mPicture.get());
+ }
+ if (NULL != mPicture.get()) {
+ // TODO: remove this const_cast once pictures are immutable
+ const_cast<SkPicture*>(mPicture.get())->draw(canvas);
+ }
+}
+
+SkPicture* AndroidPicture::makePartialCopy() const {
+ SkASSERT(NULL != mRecorder.get());
+
+ SkPictureRecorder reRecorder;
+
+ SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0);
+ mRecorder->partialReplay(canvas);
+ return reRecorder.endRecording();
+}
diff --git a/core/jni/android/graphics/AndroidPicture.h b/core/jni/android/graphics/AndroidPicture.h
new file mode 100644
index 0000000..f434941
--- /dev/null
+++ b/core/jni/android/graphics/AndroidPicture.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_PICTURE_H
+#define ANDROID_PICTURE_H
+
+#include "SkPicture.h"
+#include "SkPictureRecorder.h"
+#include "SkRefCnt.h"
+#include "SkTemplates.h"
+
+class SkCanvas;
+class SkPicture;
+class SkPictureRecorder;
+class SkStream;
+class SkWStream;
+
+// Skia's SkPicture class has been split into an SkPictureRecorder
+// and an SkPicture. AndroidPicture recreates the functionality
+// of the old SkPicture interface by flip-flopping between the two
+// new classes.
+class AndroidPicture {
+public:
+ explicit AndroidPicture(const AndroidPicture* src = NULL);
+
+ SkCanvas* beginRecording(int width, int height);
+
+ void endRecording();
+
+ int width() const;
+
+ int height() const;
+
+ static AndroidPicture* CreateFromStream(SkStream* stream);
+
+ void serialize(SkWStream* stream) const;
+
+ void draw(SkCanvas* canvas);
+
+private:
+ int mWidth;
+ int mHeight;
+ SkAutoTUnref<const SkPicture> mPicture;
+ SkAutoTDelete<SkPictureRecorder> mRecorder;
+
+ // Make a copy of a picture that is in the midst of being recorded. The
+ // resulting picture will have balanced saves and restores.
+ SkPicture* makePartialCopy() const;
+};
+#endif // ANDROID_PICTURE_H
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 0328517..9998995 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -361,24 +361,50 @@
}
static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle,
- jint width, jint height, jint configHandle, jint allocSize) {
+ jint width, jint height, jint configHandle, jint allocSize,
+ jboolean requestPremul) {
SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
SkBitmap::Config config = static_cast<SkBitmap::Config>(configHandle);
- if (width * height * SkBitmap::ComputeBytesPerPixel(config) > allocSize) {
+ SkColorType colorType = SkBitmapConfigToColorType(config);
+
+ // ARGB_4444 is a deprecated format, convert automatically to 8888
+ if (colorType == kARGB_4444_SkColorType) {
+ colorType = kN32_SkColorType;
+ }
+
+ if (width * height * SkColorTypeBytesPerPixel(colorType) > allocSize) {
// done in native as there's no way to get BytesPerPixel in Java
doThrowIAE(env, "Bitmap not large enough to support new configuration");
return;
}
SkPixelRef* ref = bitmap->pixelRef();
- SkSafeRef(ref);
- bitmap->setConfig(config, width, height);
+ ref->ref();
+ SkAlphaType alphaType;
+ if (bitmap->colorType() != kRGB_565_SkColorType
+ && bitmap->alphaType() == kOpaque_SkAlphaType) {
+ // If the original bitmap was set to opaque, keep that setting, unless it
+ // was 565, which is required to be opaque.
+ alphaType = kOpaque_SkAlphaType;
+ } else {
+ // Otherwise respect the premultiplied request.
+ alphaType = requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
+ }
+ bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType));
+ // FIXME: Skia thinks of an SkPixelRef as having a constant SkImageInfo (except for
+ // its alphatype), so it would make more sense from Skia's perspective to create a
+ // new SkPixelRef. That said, libhwui uses the pointer to the SkPixelRef as a key
+ // for its cache, so it won't realize this is the same Java Bitmap.
+ SkImageInfo& info = const_cast<SkImageInfo&>(ref->info());
+ // Use the updated from the SkBitmap, which may have corrected an invalid alphatype.
+ // (e.g. 565 non-opaque)
+ info = bitmap->info();
bitmap->setPixelRef(ref);
// notifyPixelsChanged will increment the generation ID even though the actual pixel data
// hasn't been touched. This signals the renderer that the bitmap (including width, height,
- // and config) has changed.
+ // colortype and alphatype) has changed.
ref->notifyPixelsChanged();
- SkSafeUnref(ref);
+ ref->unref();
}
// These must match the int values in Bitmap.java
@@ -799,7 +825,7 @@
(void*)Bitmap_copy },
{ "nativeDestructor", "(J)V", (void*)Bitmap_destructor },
{ "nativeRecycle", "(J)Z", (void*)Bitmap_recycle },
- { "nativeReconfigure", "(JIIII)V", (void*)Bitmap_reconfigure },
+ { "nativeReconfigure", "(JIIIIZ)V", (void*)Bitmap_reconfigure },
{ "nativeCompress", "(JIILjava/io/OutputStream;[B)Z",
(void*)Bitmap_compress },
{ "nativeErase", "(JI)V", (void*)Bitmap_erase },
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 64ad223..a4337ccc 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -3,6 +3,7 @@
#include "jni.h"
#include "JNIHelp.h"
#include "GraphicsJNI.h"
+#include "AndroidPicture.h"
#include "SkCanvas.h"
#include "SkDevice.h"
@@ -345,13 +346,13 @@
return p;
}
-SkPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture)
+AndroidPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture)
{
SkASSERT(env);
SkASSERT(picture);
SkASSERT(env->IsInstanceOf(picture, gPicture_class));
jlong pictureHandle = env->GetLongField(picture, gPicture_nativeInstanceID);
- SkPicture* p = reinterpret_cast<SkPicture*>(pictureHandle);
+ AndroidPicture* p = reinterpret_cast<AndroidPicture*>(pictureHandle);
SkASSERT(p);
return p;
}
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 73dd11b..2e2f920 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -14,7 +14,7 @@
class SkBitmapRegionDecoder;
class SkCanvas;
class SkPaint;
-class SkPicture;
+class AndroidPicture;
class GraphicsJNI {
public:
@@ -50,7 +50,7 @@
static SkPaint* getNativePaint(JNIEnv*, jobject paint);
static android::TypefaceImpl* getNativeTypeface(JNIEnv*, jobject paint);
static SkBitmap* getNativeBitmap(JNIEnv*, jobject bitmap);
- static SkPicture* getNativePicture(JNIEnv*, jobject picture);
+ static AndroidPicture* getNativePicture(JNIEnv*, jobject picture);
static SkRegion* getNativeRegion(JNIEnv*, jobject region);
// Given the 'native' long held by the Rasterizer.java object, return a
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index a8a3dae..0683f73 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -17,9 +17,9 @@
#include "jni.h"
#include "GraphicsJNI.h"
#include <android_runtime/AndroidRuntime.h>
+#include "AndroidPicture.h"
#include "SkCanvas.h"
-#include "SkPicture.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "CreateJavaOutputStreamAdaptor.h"
@@ -29,45 +29,41 @@
class SkPictureGlue {
public:
static jlong newPicture(JNIEnv* env, jobject, jlong srcHandle) {
- const SkPicture* src = reinterpret_cast<SkPicture*>(srcHandle);
- if (src) {
- return reinterpret_cast<jlong>(new SkPicture(*src));
- } else {
- return reinterpret_cast<jlong>(new SkPicture);
- }
+ const AndroidPicture* src = reinterpret_cast<AndroidPicture*>(srcHandle);
+ return reinterpret_cast<jlong>(new AndroidPicture(src));
}
static jlong deserialize(JNIEnv* env, jobject, jobject jstream,
jbyteArray jstorage) {
- SkPicture* picture = NULL;
+ AndroidPicture* picture = NULL;
SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage);
if (strm) {
- picture = SkPicture::CreateFromStream(strm);
+ picture = AndroidPicture::CreateFromStream(strm);
delete strm;
}
return reinterpret_cast<jlong>(picture);
}
static void killPicture(JNIEnv* env, jobject, jlong pictureHandle) {
- SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle);
+ AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle);
SkASSERT(picture);
- picture->unref();
+ delete picture;
}
static void draw(JNIEnv* env, jobject, jlong canvasHandle,
jlong pictureHandle) {
SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
- SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle);
+ AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle);
SkASSERT(canvas);
SkASSERT(picture);
picture->draw(canvas);
}
static jboolean serialize(JNIEnv* env, jobject, jlong pictureHandle,
- jobject jstream, jbyteArray jstorage) {
- SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle);
+ jobject jstream, jbyteArray jstorage) {
+ AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle);
SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
-
+
if (NULL != strm) {
picture->serialize(strm);
delete strm;
@@ -78,19 +74,21 @@
static jint getWidth(JNIEnv* env, jobject jpic) {
NPE_CHECK_RETURN_ZERO(env, jpic);
- int width = GraphicsJNI::getNativePicture(env, jpic)->width();
+ AndroidPicture* pict = GraphicsJNI::getNativePicture(env, jpic);
+ int width = pict->width();
return static_cast<jint>(width);
}
static jint getHeight(JNIEnv* env, jobject jpic) {
NPE_CHECK_RETURN_ZERO(env, jpic);
- int height = GraphicsJNI::getNativePicture(env, jpic)->height();
+ AndroidPicture* pict = GraphicsJNI::getNativePicture(env, jpic);
+ int height = pict->height();
return static_cast<jint>(height);
}
static jlong beginRecording(JNIEnv* env, jobject, jlong pictHandle,
- jint w, jint h) {
- SkPicture* pict = reinterpret_cast<SkPicture*>(pictHandle);
+ jint w, jint h) {
+ AndroidPicture* pict = reinterpret_cast<AndroidPicture*>(pictHandle);
// beginRecording does not ref its return value, it just returns it.
SkCanvas* canvas = pict->beginRecording(w, h);
// the java side will wrap this guy in a Canvas.java, which will call
@@ -101,7 +99,7 @@
}
static void endRecording(JNIEnv* env, jobject, jlong pictHandle) {
- SkPicture* pict = reinterpret_cast<SkPicture*>(pictHandle);
+ AndroidPicture* pict = reinterpret_cast<AndroidPicture*>(pictHandle);
pict->endRecording();
}
};
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index d54aaa8..3812c27 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -24,6 +24,7 @@
#include "SkCanvas.h"
#include "SkDocument.h"
#include "SkPicture.h"
+#include "SkPictureRecorder.h"
#include "SkStream.h"
#include "SkRect.h"
@@ -32,15 +33,22 @@
struct PageRecord {
PageRecord(int width, int height, const SkRect& contentRect)
- : mPicture(new SkPicture()), mWidth(width), mHeight(height) {
+ : mPictureRecorder(new SkPictureRecorder())
+ , mPicture(NULL)
+ , mWidth(width)
+ , mHeight(height) {
mContentRect = contentRect;
}
~PageRecord() {
- mPicture->unref();
+ delete mPictureRecorder;
+ if (NULL != mPicture) {
+ mPicture->unref();
+ }
}
- SkPicture* const mPicture;
+ SkPictureRecorder* mPictureRecorder;
+ SkPicture* mPicture;
const int mWidth;
const int mHeight;
SkRect mContentRect;
@@ -62,8 +70,8 @@
mPages.push_back(page);
mCurrentPage = page;
- SkCanvas* canvas = page->mPicture->beginRecording(
- contentRect.width(), contentRect.height(), 0);
+ SkCanvas* canvas = page->mPictureRecorder->beginRecording(
+ contentRect.width(), contentRect.height(), NULL, 0);
// We pass this canvas to Java where it is used to construct
// a Java Canvas object which dereferences the pointer when it
@@ -75,7 +83,11 @@
void finishPage() {
assert(mCurrentPage != NULL);
- mCurrentPage->mPicture->endRecording();
+ assert(mCurrentPage->mPictureRecorder != NULL);
+ assert(mCurrentPage->mPicture == NULL);
+ mCurrentPage->mPicture = mCurrentPage->mPictureRecorder->endRecording();
+ delete mCurrentPage->mPictureRecorder;
+ mCurrentPage->mPictureRecorder = NULL;
mCurrentPage = NULL;
}
@@ -89,7 +101,7 @@
canvas->clipRect(page->mContentRect);
canvas->translate(page->mContentRect.left(), page->mContentRect.top());
- canvas->drawPicture(*page->mPicture);
+ canvas->drawPicture(page->mPicture);
document->endPage();
}
@@ -97,11 +109,10 @@
}
void close() {
+ assert(NULL == mCurrentPage);
for (unsigned i = 0; i < mPages.size(); i++) {
delete mPages[i];
}
- delete mCurrentPage;
- mCurrentPage = NULL;
}
private:
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index bf47dd3..ee4c619 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -244,6 +244,12 @@
}
static jint
+android_media_AudioSystem_newAudioSessionId(JNIEnv *env, jobject thiz)
+{
+ return AudioSystem::newAudioSessionId();
+}
+
+static jint
android_media_AudioSystem_setParameters(JNIEnv *env, jobject thiz, jstring keyValuePairs)
{
const jchar* c_keyValuePairs = env->GetStringCritical(keyValuePairs, 0);
@@ -1295,6 +1301,7 @@
{"isStreamActive", "(II)Z", (void *)android_media_AudioSystem_isStreamActive},
{"isStreamActiveRemotely","(II)Z", (void *)android_media_AudioSystem_isStreamActiveRemotely},
{"isSourceActive", "(I)Z", (void *)android_media_AudioSystem_isSourceActive},
+ {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId},
{"setDeviceConnectionState", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
{"getDeviceConnectionState", "(ILjava/lang/String;)I", (void *)android_media_AudioSystem_getDeviceConnectionState},
{"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f3b5ccf..75e226d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2071,6 +2071,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 -->
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index bdf27c8..0564a8f 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -145,7 +145,7 @@
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:visibility="gone"
- style="@style/Widget.Material.Light.ProgressBar.Horizontal"
+ style="@style/Widget.StatusBar.Material.ProgressBar"
/>
</LinearLayout>
<ImageView
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index c89b9f9..f8e1986 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -38,7 +38,6 @@
android:minHeight="@dimen/notification_large_icon_height"
android:paddingTop="2dp"
android:orientation="vertical"
- android:background="@color/notification_media_info_bg"
>
<LinearLayout
android:id="@+id/line1"
@@ -147,7 +146,7 @@
android:layout_height="6dp"
android:layout_gravity="top"
android:visibility="gone"
- style="@style/Widget.StatusBar.Material.ProgressBar"
+ style="@style/Widget.StatusBar.Material.ProgressBar.Media"
/>
</FrameLayout>
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 6f8c3a9..1de5add 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -99,7 +99,7 @@
android:layout_marginEnd="8dp"
android:visibility="gone"
android:layout_weight="0"
- style="@style/Widget.Material.Light.ProgressBar.Horizontal"
+ style="@style/Widget.StatusBar.Material.ProgressBar"
/>
<TextView android:id="@+id/big_text"
android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5b362fc..2fea91e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6390,6 +6390,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. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 9bf2ce8..9f6c7ad 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -135,7 +135,6 @@
<color name="notification_action_legacy_color_filter">#ff555555</color>
<color name="notification_media_action_bg">#00000000</color>
- <color name="notification_media_info_bg">#40FFFFFF</color>
<color name="notification_media_progress">#FFFFFFFF</color>
<!-- Keyguard colors -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 8f3ee60..2792c93 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2185,6 +2185,9 @@
<public type="attr" name="translateY" />
<public type="attr" name="selectableItemBackgroundBorderless" />
<public type="attr" name="elegantTextHeight" />
+ <public type="attr" name="searchKeyphraseId" />
+ <public type="attr" name="searchKeyphrase" />
+ <public type="attr" name="searchKeyphraseSupportedLocales" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2903ac2..f1d9dc3 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1122,6 +1122,12 @@
interface of a voice interaction service. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_manageVoiceKeyphrases">manage voice keyphrases</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_manageVoiceKeyphrases">Allows the holder to manage the keyphrases for voice hotword detection.
+ Should never be needed for normal apps.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_bindRemoteDisplay">bind to a remote display</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_bindRemoteDisplay">Allows the holder to bind to the top-level
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 92cce25..a40835c 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -345,6 +345,9 @@
</style>
<style name="Widget.StatusBar.Material.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal">
+ </style>
+
+ <style name="Widget.StatusBar.Material.ProgressBar.Media">
<item name="android:progressDrawable">@drawable/notification_material_media_progress</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0887170..8aca35a8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1679,7 +1679,6 @@
<java-symbol type="drawable" name="notification_material_bg" />
<java-symbol type="drawable" name="notification_material_media_progress" />
<java-symbol type="color" name="notification_media_action_bg" />
- <java-symbol type="color" name="notification_media_info_bg" />
<java-symbol type="color" name="notification_media_progress" />
<java-symbol type="id" name="media_action_area" />
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 06cf253..ef0a411 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -194,6 +194,11 @@
* while {@link #getAllocationByteCount()} will reflect that of the initial
* configuration.</p>
*
+ * <p>Note: This may change this result of hasAlpha(). When converting to 565,
+ * the new bitmap will always be considered opaque. When converting from 565,
+ * the new bitmap will be considered non-opaque, and will respect the value
+ * set by setPremultiplied().</p>
+ *
* <p>WARNING: This method should NOT be called on a bitmap currently used
* by the view system. It does not make guarantees about how the underlying
* pixel buffer is remapped to the new config, just that the allocation is
@@ -217,7 +222,8 @@
throw new IllegalStateException("native-backed bitmaps may not be reconfigured");
}
- nativeReconfigure(mNativeBitmap, width, height, config.nativeInt, mBuffer.length);
+ nativeReconfigure(mNativeBitmap, width, height, config.nativeInt, mBuffer.length,
+ mIsPremultiplied);
mWidth = width;
mHeight = height;
}
@@ -1586,7 +1592,8 @@
private static native void nativeDestructor(long nativeBitmap);
private static native boolean nativeRecycle(long nativeBitmap);
private static native void nativeReconfigure(long nativeBitmap, int width, int height,
- int config, int allocSize);
+ int config, int allocSize,
+ boolean isPremultiplied);
private static native boolean nativeCompress(long nativeBitmap, int format,
int quality, OutputStream stream,
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c3d5d94..ba6b214 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1653,6 +1653,25 @@
}
}
+
+ /**
+ * Return a new audio session identifier not associated with any player or effect.
+ * It can for instance be used to create one of the {@link android.media.audiofx.AudioEffect}
+ * objects.
+ * @return a new unclaimed and unused audio session identifier, or {@link #ERROR} when the
+ * system failed to allocate a new session.
+ */
+ public int allocateAudioSessionId() {
+ int session = AudioSystem.newAudioSessionId();
+ if (session > 0) {
+ return session;
+ } else {
+ Log.e(TAG, "Failure to allocate a new audio session ID");
+ return ERROR;
+ }
+ }
+
+
/*
* Sets a generic audio configuration parameter. The use of these parameters
* are platform dependant, see libaudio
@@ -2998,7 +3017,8 @@
/** @hide
*/
public static final int SUCCESS = AudioSystem.SUCCESS;
- /** @hide
+ /**
+ * A default error code.
*/
public static final int ERROR = AudioSystem.ERROR;
/** @hide
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index c8d64ce..9fbcd18 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -130,6 +130,11 @@
public static native boolean isSourceActive(int source);
/*
+ * Returns a new unused audio session ID
+ */
+ public static native int newAudioSessionId();
+
+ /*
* Sets a group generic audio configuration parameters. The use of these parameters
* are platform dependent, see libaudio
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index b91e129..8d19f50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -158,7 +158,7 @@
private final Runnable mTapTimeoutRunnable = new Runnable() {
@Override
public void run() {
- makeInactive();
+ makeInactive(true /* animate */);
}
};
@@ -183,7 +183,7 @@
break;
case MotionEvent.ACTION_MOVE:
if (!isWithinTouchSlop(event)) {
- makeInactive();
+ makeInactive(true /* animate */);
return false;
}
break;
@@ -193,14 +193,17 @@
makeActive();
postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
} else {
- performClick();
+ boolean performed = performClick();
+ if (performed) {
+ removeCallbacks(mTapTimeoutRunnable);
+ }
}
} else {
- makeInactive();
+ makeInactive(true /* animate */);
}
break;
case MotionEvent.ACTION_CANCEL:
- makeInactive();
+ makeInactive(true /* animate */);
break;
default:
break;
@@ -257,10 +260,14 @@
/**
* Cancels the hotspot and makes the notification inactive.
*/
- private void makeInactive() {
+ public void makeInactive(boolean animate) {
if (mActivated) {
if (mDimmed) {
- startActivateAnimation(true /* reverse */);
+ if (animate) {
+ startActivateAnimation(true /* reverse */);
+ } else {
+ mBackgroundNormal.setVisibility(View.INVISIBLE);
+ }
}
mActivated = false;
}
@@ -351,6 +358,7 @@
mBackgroundDimmed.setVisibility(View.INVISIBLE);
mBackgroundNormal.setVisibility(View.VISIBLE);
mBackgroundNormal.setAlpha(1f);
+ removeCallbacks(mTapTimeoutRunnable);
}
}
@@ -581,7 +589,7 @@
}
public interface OnActivatedListener {
- void onActivated(View view);
- void onActivationReset(View view);
+ void onActivated(ActivatableNotificationView view);
+ void onActivationReset(ActivatableNotificationView view);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 508c34e..28b2780 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -80,11 +80,10 @@
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
+import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.Locale;
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
@@ -106,6 +105,7 @@
protected static final int MSG_SHOW_HEADS_UP = 1026;
protected static final int MSG_HIDE_HEADS_UP = 1027;
protected static final int MSG_ESCALATE_HEADS_UP = 1028;
+ protected static final int MSG_DECAY_HEADS_UP = 1029;
protected static final boolean ENABLE_HEADS_UP = true;
// scores above this threshold should be displayed in heads up mode.
@@ -129,7 +129,9 @@
protected NotificationData mNotificationData = new NotificationData();
protected NotificationStackScrollLayout mStackScroller;
- protected NotificationData.Entry mInterruptingNotificationEntry;
+ // for heads up notifications
+ protected HeadsUpNotificationView mHeadsUpNotificationView;
+ protected int mHeadsUpNotificationDecay;
protected long mInterruptingNotificationTime;
// used to notify status bar for suppressing notification LED
@@ -505,8 +507,8 @@
protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
View vetoButton = row.findViewById(R.id.veto);
- if (n.isClearable() || (mInterruptingNotificationEntry != null
- && mInterruptingNotificationEntry.row == row)) {
+ if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null
+ && mHeadsUpNotificationView.getEntry().row == row)) {
final String _pkg = n.getPackageName();
final String _tag = n.getTag();
final int _id = n.getId();
@@ -765,6 +767,12 @@
public abstract void resetHeadsUpDecayTimer();
+ public abstract void scheduleHeadsUpOpen();
+
+ public abstract void scheduleHeadsUpClose();
+
+ public abstract void scheduleHeadsUpEscalation();
+
/**
* Save the current "public" (locked and secure) state of the lockscreen.
*/
@@ -796,6 +804,18 @@
return mUsersAllowingPrivateNotifications.get(userHandle);
}
+ public void onNotificationClear(StatusBarNotification notification) {
+ try {
+ mBarService.onNotificationClear(
+ notification.getPackageName(),
+ notification.getTag(),
+ notification.getId(),
+ notification.getUserId());
+ } catch (android.os.RemoteException ex) {
+ // oh well
+ }
+ }
+
protected class H extends Handler {
public void handleMessage(Message m) {
Intent intent;
@@ -1101,7 +1121,7 @@
try {
if (mIsHeadsUp) {
- mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
+ mHeadsUpNotificationView.clear();
}
mBarService.onNotificationClick(mNotificationKey);
} catch (RemoteException ex) {
@@ -1394,15 +1414,15 @@
try {
updateNotificationViews(oldEntry, notification);
- if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
- && oldNotification == mInterruptingNotificationEntry.notification) {
+ if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null
+ && oldNotification == mHeadsUpNotificationView.getEntry().notification) {
if (!shouldInterrupt(notification)) {
if (DEBUG) Log.d(TAG, "no longer interrupts!");
- mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
+ scheduleHeadsUpClose();
} else {
if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
- mInterruptingNotificationEntry.notification = notification;
- updateHeadsUpViews(mInterruptingNotificationEntry, notification);
+ mHeadsUpNotificationView.getEntry().notification = notification;
+ updateHeadsUpViews(mHeadsUpNotificationView.getEntry(), notification);
}
}
@@ -1511,8 +1531,8 @@
}
protected void notifyHeadsUpScreenOn(boolean screenOn) {
- if (!screenOn && mInterruptingNotificationEntry != null) {
- mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
+ if (!screenOn) {
+ scheduleHeadsUpEscalation();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java
index 086a266..e312d58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java
@@ -423,6 +423,7 @@
return;
}
if (!animate) {
+ view.animate().cancel();
view.setAlpha(alpha);
view.setScaleX(scale);
view.setScaleY(scale);
@@ -465,6 +466,13 @@
}
public void reset() {
+ if (mSwipeAnimator != null) {
+ mSwipeAnimator.cancel();
+ }
+ ArrayList<View> targetViews = mCallback.getTranslationViews();
+ for (View view : targetViews) {
+ view.animate().cancel();
+ }
setTranslation(0.0f, true);
mSwipingInProgress = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 772d0e7..1f3098d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -27,6 +27,7 @@
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -67,6 +68,11 @@
private VelocityTrackerInterface mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
+ /**
+ * Whether an instant expand request is currently pending and we are just waiting for layout.
+ */
+ private boolean mInstantExpanding;
+
PanelBar mBar;
protected int mMaxPanelHeight = -1;
@@ -128,6 +134,9 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
+ if (mInstantExpanding) {
+ return false;
+ }
/*
* We capture touch events here and update the expand height here in case according to
@@ -263,6 +272,9 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (mInstantExpanding) {
+ return false;
+ }
/*
* If the user drags anywhere inside the panel we intercept it if he moves his finger
@@ -556,6 +568,41 @@
}
}
+ public void instantExpand() {
+ mInstantExpanding = true;
+ abortAnimations();
+ if (mTracking) {
+ onTrackingStopped(true /* expands */); // The panel is expanded after this call.
+ onExpandingFinished();
+ }
+ setVisibility(VISIBLE);
+
+ // Wait for window manager to pickup the change, so we know the maximum height of the panel
+ // then.
+ getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (mStatusBar.getStatusBarWindow().getHeight()
+ != mStatusBar.getStatusBarHeight()) {
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ setExpandedFraction(1f);
+ mInstantExpanding = false;
+ }
+ }
+ });
+
+ // Make sure a layout really happens.
+ requestLayout();
+ }
+
+ private void abortAnimations() {
+ cancelPeek();
+ if (mHeightAnimator != null) {
+ mHeightAnimator.cancel();
+ }
+ }
+
protected void startUnlockHintAnimation() {
// We don't need to hint the user if an animation is already running or the user is changing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index a8f61f3..689fd29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -103,6 +103,7 @@
import com.android.systemui.qs.CircularClipper;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DragDownHelper;
@@ -277,10 +278,6 @@
// the date view
DateView mDateView;
- // for heads up notifications
- private HeadsUpNotificationView mHeadsUpNotificationView;
- private int mHeadsUpNotificationDecay;
-
// on-screen navigation buttons
private NavigationBarView mNavigationBarView = null;
private int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -366,7 +363,7 @@
if (!mUseHeadsUp) {
Log.d(TAG, "dismissing any existing heads up notification on disable event");
setHeadsUpVisibility(false);
- mHeadsUpNotificationView.setNotification(null);
+ mHeadsUpNotificationView.release();
removeHeadsUpView();
} else {
addHeadsUpView();
@@ -818,6 +815,10 @@
return mStatusBarView;
}
+ public StatusBarWindowView getStatusBarWindow() {
+ return mStatusBarWindow;
+ }
+
@Override
protected WindowManager.LayoutParams getSearchLayoutParams(LayoutParams layoutParams) {
boolean opaque = false;
@@ -1063,29 +1064,27 @@
public void displayNotification(StatusBarNotification notification,
Ranking ranking) {
- Entry shadeEntry = createNotificationViews(notification);
- if (shadeEntry == null) {
- return;
- }
if (mUseHeadsUp && shouldInterrupt(notification)) {
if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
Entry interruptionCandidate = new Entry(notification, null);
ViewGroup holder = mHeadsUpNotificationView.getHolder();
if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
mInterruptingNotificationTime = System.currentTimeMillis();
- mInterruptingNotificationEntry = interruptionCandidate;
- shadeEntry.setInterruption();
// 1. Populate mHeadsUpNotificationView
- mHeadsUpNotificationView.setNotification(mInterruptingNotificationEntry);
+ mHeadsUpNotificationView.showNotification(interruptionCandidate);
- // 2. Animate mHeadsUpNotificationView in
- mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
-
- // 3. Set alarm to age the notification off
- resetHeadsUpDecayTimer();
+ // do not show the notification in the shade, yet.
+ return;
}
- } else if (notification.getNotification().fullScreenIntent != null) {
+ }
+
+ Entry shadeEntry = createNotificationViews(notification);
+ if (shadeEntry == null) {
+ return;
+ }
+
+ if (notification.getNotification().fullScreenIntent != null) {
// Stop screensaver if the notification has a full-screen intent.
// (like an incoming phone call)
awakenDreams();
@@ -1100,7 +1099,7 @@
// usual case: status bar visible & not immersive
// show the ticker if there isn't already a heads up
- if (mInterruptingNotificationEntry == null) {
+ if (mHeadsUpNotificationView.getEntry() == null) {
tick(notification, true);
}
}
@@ -1110,16 +1109,44 @@
updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
}
+ public void displayNotificationFromHeadsUp(StatusBarNotification notification) {
+ NotificationData.Entry shadeEntry = createNotificationViews(notification);
+ if (shadeEntry == null) {
+ return;
+ }
+ shadeEntry.setInterruption();
+
+ addNotificationViews(shadeEntry, null);
+ // Recalculate the position of the sliding windows and the titles.
+ setAreThereNotifications();
+ updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+ }
+
@Override
public void resetHeadsUpDecayTimer() {
- mHandler.removeMessages(MSG_HIDE_HEADS_UP);
+ mHandler.removeMessages(MSG_DECAY_HEADS_UP);
if (mUseHeadsUp && mHeadsUpNotificationDecay > 0
&& mHeadsUpNotificationView.isClearable()) {
- mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, mHeadsUpNotificationDecay);
+ mHandler.sendEmptyMessageDelayed(MSG_DECAY_HEADS_UP, mHeadsUpNotificationDecay);
}
}
@Override
+ public void scheduleHeadsUpOpen() {
+ mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
+ }
+
+ @Override
+ public void scheduleHeadsUpClose() {
+ mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
+ }
+
+ @Override
+ public void scheduleHeadsUpEscalation() {
+ mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
+ }
+
+ @Override
public void updateNotificationInternal(StatusBarNotification notification, Ranking ranking) {
super.updateNotificationInternal(notification, ranking);
// if we're here, then the notification is already in the shade
@@ -1135,6 +1162,11 @@
@Override
public void removeNotificationInternal(String key, Ranking ranking) {
+ if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null
+ && key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) {
+ mHeadsUpNotificationView.clear();
+ }
+
StatusBarNotification old = removeNotificationViews(key, ranking);
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
@@ -1147,11 +1179,6 @@
// Recalculate the position of the sliding windows and the titles.
updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
- if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
- && old == mInterruptingNotificationEntry.notification) {
- mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
- }
-
if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0
&& !mNotificationPanel.isTracking() && mState != StatusBarState.KEYGUARD) {
animateCollapsePanels();
@@ -1574,7 +1601,12 @@
case MSG_SHOW_HEADS_UP:
setHeadsUpVisibility(true);
break;
+ case MSG_DECAY_HEADS_UP:
+ mHeadsUpNotificationView.release();
+ setHeadsUpVisibility(false);
+ break;
case MSG_HIDE_HEADS_UP:
+ mHeadsUpNotificationView.release();
setHeadsUpVisibility(false);
break;
case MSG_ESCALATE_HEADS_UP:
@@ -1587,8 +1619,9 @@
/** if the interrupting notification had a fullscreen intent, fire it now. */
private void escalateHeadsUp() {
- if (mInterruptingNotificationEntry != null) {
- final StatusBarNotification sbn = mInterruptingNotificationEntry.notification;
+ if (mHeadsUpNotificationView.getEntry() != null) {
+ final StatusBarNotification sbn = mHeadsUpNotificationView.getEntry().notification;
+ mHeadsUpNotificationView.release();
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
if (DEBUG)
@@ -2243,7 +2276,7 @@
pw.print(" mUseHeadsUp=");
pw.println(mUseHeadsUp);
pw.print(" interrupting package: ");
- pw.println(hunStateToString(mInterruptingNotificationEntry));
+ pw.println(hunStateToString(mHeadsUpNotificationView.getEntry()));
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
if (mNavigationBarView != null) {
pw.print(" mNavigationBarWindowState=");
@@ -2517,26 +2550,10 @@
if (!ENABLE_HEADS_UP) return;
if (DEBUG) Log.v(TAG, (vis ? "showing" : "hiding") + " heads up window");
mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE);
- if (!vis) {
- if (DEBUG) Log.d(TAG, "setting heads up entry to null");
- mInterruptingNotificationEntry = null;
- }
}
public void onHeadsUpDismissed() {
- if (mInterruptingNotificationEntry == null) return;
- mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
- if (mHeadsUpNotificationView.isClearable()) {
- try {
- mBarService.onNotificationClear(
- mInterruptingNotificationEntry.notification.getPackageName(),
- mInterruptingNotificationEntry.notification.getTag(),
- mInterruptingNotificationEntry.notification.getId(),
- mInterruptingNotificationEntry.notification.getUserId());
- } catch (android.os.RemoteException ex) {
- // oh well
- }
- }
+ mHeadsUpNotificationView.dismiss();
}
/**
@@ -2905,7 +2922,6 @@
mNotificationPanel.setKeyguardShowing(false);
mScrimController.setKeyguardShowing(false);
}
-
updateStackScrollerState();
updatePublicMode();
updateNotifications();
@@ -2921,6 +2937,11 @@
? View.INVISIBLE : View.VISIBLE);
mStackScroller.setScrollingEnabled(!onKeyguard);
mStackScroller.setExpandingEnabled(!onKeyguard);
+ ActivatableNotificationView activatedChild = mStackScroller.getActivatedChild();
+ mStackScroller.setActivatedChild(null);
+ if (activatedChild != null) {
+ activatedChild.makeInactive(false /* animate */);
+ }
}
public void userActivity() {
@@ -2954,22 +2975,9 @@
private void instantExpandNotificationsPanel() {
- // Make our window larger and the panel visible.
+ // Make our window larger and the panel expanded.
makeExpandedVisible(true);
- mNotificationPanel.setVisibility(View.VISIBLE);
-
- // Wait for window manager to pickup the change, so we know the maximum height of the panel
- // then.
- mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (mStatusBarWindow.getHeight() != getStatusBarHeight()) {
- mNotificationPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- mNotificationPanel.setExpandedFraction(1);
- }
- }
- });
+ mNotificationPanel.instantExpand();
}
private void instantCollapseNotificationPanel() {
@@ -2977,9 +2985,13 @@
}
@Override
- public void onActivated(View view) {
+ public void onActivated(ActivatableNotificationView view) {
userActivity();
mKeyguardIndicationController.showTransientIndication(R.string.notification_tap_again);
+ ActivatableNotificationView previousView = mStackScroller.getActivatedChild();
+ if (previousView != null) {
+ previousView.makeInactive(true /* animate */);
+ }
mStackScroller.setActivatedChild(view);
}
@@ -2992,7 +3004,7 @@
}
@Override
- public void onActivationReset(View view) {
+ public void onActivationReset(ActivatableNotificationView view) {
if (view == mStackScroller.getActivatedChild()) {
mKeyguardIndicationController.hideTransientIndication();
mStackScroller.setActivatedChild(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index df01c12..d778ccb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -33,9 +33,9 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
-import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback,
ViewTreeObserver.OnComputeInternalInsetsListener {
@@ -51,7 +51,7 @@
private SwipeHelper mSwipeHelper;
private EdgeSwipeHelper mEdgeSwipeHelper;
- private BaseStatusBar mBar;
+ private PhoneStatusBar mBar;
private ExpandHelper mExpandHelper;
private long mStartTouchTime;
@@ -69,7 +69,7 @@
if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay);
}
- public void setBar(BaseStatusBar bar) {
+ public void setBar(PhoneStatusBar bar) {
mBar = bar;
}
@@ -77,7 +77,10 @@
return mContentHolder;
}
- public boolean setNotification(NotificationData.Entry headsUp) {
+ public boolean showNotification(NotificationData.Entry headsUp) {
+ // bump any previous heads up back to the shade
+ release();
+
mHeadsUp = headsUp;
if (mContentHolder != null) {
mContentHolder.removeAllViews();
@@ -97,10 +100,46 @@
mSwipeHelper.snapChild(mContentHolder, 1f);
mStartTouchTime = System.currentTimeMillis() + mTouchSensitivityDelay;
+
+ // 2. Animate mHeadsUpNotificationView in
+ mBar.scheduleHeadsUpOpen();
+
+ // 3. Set alarm to age the notification off
+ mBar.resetHeadsUpDecayTimer();
}
return true;
}
+ /** Discard the Heads Up notification. */
+ public void clear() {
+ mHeadsUp = null;
+ mBar.scheduleHeadsUpClose();
+ }
+
+ /** Respond to dismissal of the Heads Up window. */
+ public void dismiss() {
+ if (mHeadsUp == null) return;
+ if (mHeadsUp.notification.isClearable()) {
+ mBar.onNotificationClear(mHeadsUp.notification);
+ } else {
+ release();
+ }
+ mHeadsUp = null;
+ mBar.scheduleHeadsUpClose();
+ }
+
+ /** Push any current Heads Up notification down into the shade. */
+ public void release() {
+ if (mHeadsUp != null) {
+ mBar.displayNotificationFromHeadsUp(mHeadsUp.notification);
+ }
+ mHeadsUp = null;
+ }
+
+ public NotificationData.Entry getEntry() {
+ return mHeadsUp;
+ }
+
public boolean isClearable() {
return mHeadsUp == null || mHeadsUp.notification.isClearable();
}
@@ -125,7 +164,7 @@
if (mHeadsUp != null) {
// whoops, we're on already!
- setNotification(mHeadsUp);
+ showNotification(mHeadsUp);
}
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
@@ -282,6 +321,10 @@
mTmpTwoArray[1] + mContentHolder.getHeight());
}
+ public void escalate() {
+ mBar.scheduleHeadsUpEscalation();
+ }
+
private class EdgeSwipeHelper implements Gefingerpoken {
private static final boolean DEBUG_EDGE_SWIPE = false;
private final float mTouchSlop;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index b21e12c..6d92b05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.stack;
import android.view.View;
+import com.android.systemui.statusbar.ActivatableNotificationView;
import java.util.ArrayList;
@@ -27,7 +28,7 @@
private ArrayList<View> mDraggedViews = new ArrayList<View>();
private int mScrollY;
private boolean mDimmed;
- private View mActivatedChild;
+ private ActivatableNotificationView mActivatedChild;
private float mOverScrollTopAmount;
private float mOverScrollBottomAmount;
private int mSpeedBumpIndex = -1;
@@ -64,7 +65,7 @@
* In dimmed mode, a child can be activated, which happens on the first tap of the double-tap
* interaction. This child is then scaled normally and its background is fully opaque.
*/
- public void setActivatedChild(View activatedChild) {
+ public void setActivatedChild(ActivatableNotificationView activatedChild) {
mActivatedChild = activatedChild;
}
@@ -72,7 +73,7 @@
return mDimmed;
}
- public View getActivatedChild() {
+ public ActivatableNotificationView getActivatedChild() {
return mActivatedChild;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 5ace89f..e47f475 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -33,6 +33,7 @@
import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
+import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.SpeedBumpView;
@@ -1760,7 +1761,7 @@
/**
* See {@link AmbientState#setActivatedChild}.
*/
- public void setActivatedChild(View activatedChild) {
+ public void setActivatedChild(ActivatableNotificationView activatedChild) {
mAmbientState.setActivatedChild(activatedChild);
if (mAnimationsEnabled) {
mActivateNeedsAnimation = true;
@@ -1769,7 +1770,7 @@
requestChildrenUpdate();
}
- public View getActivatedChild() {
+ public ActivatableNotificationView getActivatedChild() {
return mAmbientState.getActivatedChild();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index faea8de..ac8d1a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.tv;
import android.os.IBinder;
-import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.StatusBarNotification;
import android.view.View;
@@ -25,6 +24,7 @@
import android.view.WindowManager;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BaseStatusBar;
/*
@@ -147,6 +147,18 @@
}
@Override
+ public void scheduleHeadsUpOpen() {
+ }
+
+ @Override
+ public void scheduleHeadsUpEscalation() {
+ }
+
+ @Override
+ public void scheduleHeadsUpClose() {
+ }
+
+ @Override
protected int getMaxKeyguardNotifications() {
return 0;
}
@@ -164,10 +176,10 @@
}
@Override
- public void onActivated(View view) {
+ public void onActivated(ActivatableNotificationView view) {
}
@Override
- public void onActivationReset(View view) {
+ public void onActivationReset(ActivatableNotificationView view) {
}
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 32546df..b9ef492 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -186,12 +186,16 @@
if (resolveInfo.serviceInfo == null) continue;
String packageName = resolveInfo.serviceInfo.packageName;
+ // STOPSHIP Reenable this check once the GMS Core prebuild library has the
+ // permission.
+ /*
if (pm.checkPermission(PERMISSION_PROVIDE_AGENT, packageName)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "Skipping agent because package " + packageName
+ " does not have permission " + PERMISSION_PROVIDE_AGENT + ".");
continue;
}
+ */
ComponentName name = getComponentName(resolveInfo);
if (!enabledAgents.contains(name)) continue;
diff --git a/tests/VoiceEnrollment/Android.mk b/tests/VoiceEnrollment/Android.mk
new file mode 100644
index 0000000..2ab3d02
--- /dev/null
+++ b/tests/VoiceEnrollment/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 := VoiceEnrollment
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
diff --git a/tests/VoiceEnrollment/AndroidManifest.xml b/tests/VoiceEnrollment/AndroidManifest.xml
new file mode 100644
index 0000000..6321222
--- /dev/null
+++ b/tests/VoiceEnrollment/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.voiceenrollment">
+
+ <application
+ android:permission="android.permission.MANAGE_VOICE_KEYPHRASES">
+ <activity android:name="TestEnrollmentActivity" android:label="Voice Enrollment Application"
+ android:theme="@android:style/Theme.Material.Light.Voice">
+ <intent-filter>
+ <action android:name="com.android.intent.action.MANAGE_VOICE_KEYPHRASES" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <meta-data android:name="android.voice_enrollment"
+ android:resource="@xml/enrollment_application"/>
+ </application>
+</manifest>
diff --git a/tests/VoiceEnrollment/res/xml/enrollment_application.xml b/tests/VoiceEnrollment/res/xml/enrollment_application.xml
new file mode 100644
index 0000000..710a0ac
--- /dev/null
+++ b/tests/VoiceEnrollment/res/xml/enrollment_application.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+
+<voice-enrollment-application xmlns:android="http://schemas.android.com/apk/res/android"
+ android:searchKeyphraseId="101"
+ android:searchKeyphrase="Hello There"
+ android:searchKeyphraseSupportedLocales="en-US,en-GB,fr-FR,de-DE" />
diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
new file mode 100644
index 0000000..7fbd965
--- /dev/null
+++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.voiceenrollment;
+
+import android.app.Activity;
+
+public class TestEnrollmentActivity extends Activity {
+ // TODO(sansid): Add a test enrollment flow here.
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index d40b05f..00c2c64 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -21,6 +21,8 @@
import android.service.voice.VoiceInteractionService;
import android.util.Log;
+import java.util.Arrays;
+
public class MainInteractionService extends VoiceInteractionService {
static final String TAG = "MainInteractionService";
@@ -28,6 +30,9 @@
public void onCreate() {
super.onCreate();
Log.i(TAG, "Creating " + this);
+ Log.i(TAG, "Keyphrase enrollment error? " + getKeyphraseEnrollmentInfo().getParseError());
+ Log.i(TAG, "Keyphrase enrollment meta-data: "
+ + Arrays.toString(getKeyphraseEnrollmentInfo().getKeyphrases()));
}
@Override