Merge "Delay settings panel visibility until touch slop exceeded."
diff --git a/Android.mk b/Android.mk
index be7e055..df8fbd9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -180,6 +180,7 @@
core/java/android/os/IUserManager.aidl \
core/java/android/os/IVibratorService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
+ core/java/android/service/notification/IConditionProvider.aidl \
core/java/android/print/ILayoutResultCallback.aidl \
core/java/android/print/IPrinterDiscoveryObserver.aidl \
core/java/android/print/IPrintDocumentAdapter.aidl \
@@ -197,6 +198,8 @@
core/java/android/service/dreams/IDreamService.aidl \
core/java/android/service/trust/ITrustAgentService.aidl \
core/java/android/service/trust/ITrustAgentServiceCallback.aidl \
+ core/java/android/service/voice/IVoiceInteractionService.aidl \
+ core/java/android/service/voice/IVoiceInteractionSession.aidl \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \
core/java/android/service/wallpaper/IWallpaperEngine.aidl \
core/java/android/service/wallpaper/IWallpaperService.aidl \
@@ -230,6 +233,10 @@
core/java/com/android/internal/app/IBatteryStats.aidl \
core/java/com/android/internal/app/IProcessStats.aidl \
core/java/com/android/internal/app/IUsageStats.aidl \
+ core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
+ core/java/com/android/internal/app/IVoiceInteractor.aidl \
+ core/java/com/android/internal/app/IVoiceInteractorCallback.aidl \
+ core/java/com/android/internal/app/IVoiceInteractorRequest.aidl \
core/java/com/android/internal/app/IMediaContainerService.aidl \
core/java/com/android/internal/appwidget/IAppWidgetService.aidl \
core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
diff --git a/api/current.txt b/api/current.txt
index 37d2345..81362f1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31,6 +31,7 @@
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
field public static final java.lang.String BIND_TRUST_AGENT_SERVICE = "android.permission.BIND_TRUST_AGENT_SERVICE";
field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
+ field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION";
field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER";
field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH";
@@ -3202,6 +3203,7 @@
method public int getTaskId();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
+ method public android.app.VoiceInteractor getVoiceInteractor();
method public final int getVolumeControlStream();
method public android.view.Window getWindow();
method public android.view.WindowManager getWindowManager();
@@ -3213,6 +3215,7 @@
method public boolean isFinishing();
method public boolean isImmersive();
method public boolean isTaskRoot();
+ method public boolean isVoiceInteraction();
method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public boolean moveTaskToBack(boolean);
method public boolean navigateUpTo(android.content.Intent);
@@ -4838,6 +4841,23 @@
field public static final int MODE_NIGHT_YES = 2; // 0x2
}
+ public class VoiceInteractor {
+ method public android.app.VoiceInteractor.Request startCommand(android.app.VoiceInteractor.Callback, java.lang.String, android.os.Bundle);
+ method public android.app.VoiceInteractor.Request startConfirmation(android.app.VoiceInteractor.Callback, java.lang.String, android.os.Bundle);
+ method public boolean[] supportsCommands(java.lang.String[]);
+ }
+
+ public static class VoiceInteractor.Callback {
+ ctor public VoiceInteractor.Callback();
+ method public void onCancel(android.app.VoiceInteractor.Request);
+ method public void onCommandResult(android.app.VoiceInteractor.Request, android.os.Bundle);
+ method public void onConfirmationResult(android.app.VoiceInteractor.Request, boolean, android.os.Bundle);
+ }
+
+ public static class VoiceInteractor.Request {
+ method public void cancel();
+ }
+
public final class WallpaperInfo implements android.os.Parcelable {
ctor public WallpaperInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public int describeContents();
@@ -4947,7 +4967,9 @@
public class DevicePolicyManager {
method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
+ method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
+ method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
method public java.util.List<android.content.ComponentName> getActiveAdmins();
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
method public boolean getCameraDisabled(android.content.ComponentName);
@@ -6997,6 +7019,7 @@
field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB";
field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST";
field public static final java.lang.String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
+ field public static final java.lang.String CATEGORY_VOICE = "android.intent.category.VOICE";
field public static final android.os.Parcelable.Creator CREATOR;
field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
@@ -10023,11 +10046,16 @@
method public void setPaint(android.graphics.Paint);
}
- public class Outline {
+ public final class Outline {
ctor public Outline();
- method public final boolean isValid();
+ ctor public Outline(android.graphics.Outline);
+ method public boolean isValid();
method public void set(android.graphics.Outline);
+ method public void setConvexPath(android.graphics.Path);
+ method public void setRect(int, int, int, int);
+ method public void setRect(android.graphics.Rect);
method public void setRoundRect(int, int, int, int, float);
+ method public void setRoundRect(android.graphics.Rect, float);
}
public class Paint {
@@ -10750,7 +10778,7 @@
method public int getMinimumHeight();
method public int getMinimumWidth();
method public abstract int getOpacity();
- method public android.graphics.Outline getOutline();
+ method public boolean getOutline(android.graphics.Outline);
method public boolean getPadding(android.graphics.Rect);
method public int[] getState();
method public android.graphics.Region getTransparentRegion();
@@ -13377,6 +13405,18 @@
ctor public DeniedByServerException(java.lang.String);
}
+ public final class DngCreator {
+ ctor public DngCreator(android.hardware.camera2.CameraCharacteristics, android.hardware.camera2.CaptureResult);
+ method public android.media.DngCreator setDescription(java.lang.String);
+ method public android.media.DngCreator setLocation(android.location.Location);
+ method public android.media.DngCreator setOrientation(int);
+ method public android.media.DngCreator setThumbnail(android.graphics.Bitmap);
+ method public android.media.DngCreator setThumbnail(android.media.Image);
+ method public void writeByteBuffer(java.io.OutputStream, java.nio.ByteBuffer, int, long) throws java.io.IOException;
+ method public void writeImage(java.io.OutputStream, android.media.Image) throws java.io.IOException;
+ method public void writeInputStream(java.io.OutputStream, java.io.InputStream, int, long) throws java.io.IOException;
+ }
+
public class ExifInterface {
ctor public ExifInterface(java.lang.String) throws java.io.IOException;
method public double getAltitude(double);
@@ -24866,6 +24906,36 @@
}
+package android.service.voice {
+
+ public class VoiceInteractionService extends android.app.Service {
+ ctor public VoiceInteractionService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public void startVoiceActivity(android.content.Intent, android.service.voice.VoiceInteractionSession);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
+ field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction";
+ }
+
+ public abstract class VoiceInteractionSession {
+ ctor public VoiceInteractionSession(android.content.Context);
+ ctor public VoiceInteractionSession(android.content.Context, android.os.Handler);
+ method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request);
+ method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
+ method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
+ method public abstract boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]);
+ }
+
+ public static class VoiceInteractionSession.Caller {
+ }
+
+ public static class VoiceInteractionSession.Request {
+ method public void sendCancelResult();
+ method public void sendCommandResult(android.os.Bundle);
+ method public void sendConfirmResult(boolean, android.os.Bundle);
+ }
+
+}
+
package android.service.wallpaper {
public abstract class WallpaperService extends android.app.Service {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 7df55a5..6b55b7b 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -749,6 +749,11 @@
"Error: Activity not started, you do not "
+ "have permission to access it.");
break;
+ case ActivityManager.START_NOT_VOICE_COMPATIBLE:
+ out.println(
+ "Error: Activity not started, voice control not allowed for: "
+ + intent);
+ break;
default:
out.println(
"Error: Activity not started, unknown error code " + res);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 599a608..197eaf8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -22,6 +22,7 @@
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
import android.widget.Toolbar;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.policy.PolicyManager;
@@ -726,6 +727,8 @@
/*package*/ ActionBar mActionBar = null;
private boolean mEnableDefaultActionBarUp;
+ private VoiceInteractor mVoiceInteractor;
+
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -1134,6 +1137,23 @@
}
/**
+ * Check whether this activity is running as part of a voice interaction with the user.
+ * If true, it should perform its interaction with the user through the
+ * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
+ */
+ public boolean isVoiceInteraction() {
+ return mVoiceInteractor != null;
+ }
+
+ /**
+ * Retrieve the active {@link VoiceInteractor} that the user is going through to
+ * interact with this activity.
+ */
+ public VoiceInteractor getVoiceInteractor() {
+ return mVoiceInteractor;
+ }
+
+ /**
* This is called for activities that set launchMode to "singleTop" in
* their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
* flag when calling {@link #startActivity}. In either case, when the
@@ -5397,7 +5417,7 @@
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config) {
attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
- lastNonConfigurationInstances, config, null);
+ lastNonConfigurationInstances, config, null, null);
}
final void attach(Context context, ActivityThread aThread,
@@ -5405,7 +5425,7 @@
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
- Configuration config, Bundle options) {
+ Configuration config, Bundle options, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5433,6 +5453,8 @@
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
+ mVoiceInteractor = voiceInteractor != null
+ ? new VoiceInteractor(this, voiceInteractor, Looper.myLooper()) : null;
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c027e99..018e949 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -76,6 +76,13 @@
public static final String META_HOME_ALTERNATE = "android.app.home.alternate";
/**
+ * Result for IActivityManager.startActivity: trying to start an activity under voice
+ * control when that activity does not support the VOICE category.
+ * @hide
+ */
+ public static final int START_NOT_VOICE_COMPATIBLE = -7;
+
+ /**
* Result for IActivityManager.startActivity: an error where the
* start had to be canceled.
* @hide
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 10831f2..b1c37de 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -43,9 +43,11 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
+import android.service.voice.IVoiceInteractionSession;
import android.text.TextUtils;
import android.util.Log;
import android.util.Singleton;
+import com.android.internal.app.IVoiceInteractor;
import java.util.ArrayList;
import java.util.List;
@@ -242,6 +244,33 @@
return true;
}
+ case START_VOICE_ACTIVITY_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ String callingPackage = data.readString();
+ int callingPid = data.readInt();
+ int callingUid = data.readInt();
+ Intent intent = Intent.CREATOR.createFromParcel(data);
+ String resolvedType = data.readString();
+ IVoiceInteractionSession session = IVoiceInteractionSession.Stub.asInterface(
+ data.readStrongBinder());
+ IVoiceInteractor interactor = IVoiceInteractor.Stub.asInterface(
+ data.readStrongBinder());
+ int startFlags = data.readInt();
+ String profileFile = data.readString();
+ ParcelFileDescriptor profileFd = data.readInt() != 0
+ ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
+ int userId = data.readInt();
+ int result = startVoiceActivity(callingPackage, callingPid, callingUid,
+ intent, resolvedType, session, interactor, startFlags,
+ profileFile, profileFd, options, userId);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
+
case START_NEXT_MATCHING_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -2323,6 +2352,42 @@
data.recycle();
return result;
}
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(callingPackage);
+ data.writeInt(callingPid);
+ data.writeInt(callingUid);
+ intent.writeToParcel(data, 0);
+ data.writeString(resolvedType);
+ data.writeStrongBinder(session.asBinder());
+ data.writeStrongBinder(interactor.asBinder());
+ data.writeInt(startFlags);
+ data.writeString(profileFile);
+ if (profileFd != null) {
+ data.writeInt(1);
+ profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ data.writeInt(0);
+ }
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ data.writeInt(userId);
+ mRemote.transact(START_VOICE_ACTIVITY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int result = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 88eae7f..7dc21b4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -94,6 +94,7 @@
import android.renderscript.RenderScript;
import android.security.AndroidKeyStoreProvider;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
@@ -265,6 +266,7 @@
IBinder token;
int ident;
Intent intent;
+ IVoiceInteractor voiceInteractor;
Bundle state;
Activity activity;
Window window;
@@ -603,6 +605,7 @@
// activity itself back to the activity manager. (matters more with ipc)
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
+ IVoiceInteractor voiceInteractor,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
@@ -615,6 +618,7 @@
r.token = token;
r.ident = ident;
r.intent = intent;
+ r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
@@ -2197,7 +2201,8 @@
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstances, config, options);
+ r.embeddedID, r.lastNonConfigurationInstances, config, options,
+ r.voiceInteractor);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -3003,7 +3008,9 @@
int h;
if (w < 0) {
Resources res = r.activity.getResources();
- if (SystemProperties.getBoolean("persist.recents.use_alternate", false)) {
+ Configuration config = res.getConfiguration();
+ boolean useAlternateRecents = (config.smallestScreenWidthDp < 600);
+ if (useAlternateRecents) {
int wId = com.android.internal.R.dimen.recents_thumbnail_width;
int hId = com.android.internal.R.dimen.recents_thumbnail_height;
mThumbnailWidth = w = res.getDimensionPixelSize(wId);
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index f1c632e..fcc7f8e 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -33,6 +33,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import com.android.internal.app.IVoiceInteractor;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -136,6 +137,8 @@
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
+ IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
+ data.readStrongBinder());
int procState = data.readInt();
Bundle state = data.readBundle();
List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
@@ -147,7 +150,8 @@
? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
boolean autoStopProfiler = data.readInt() != 0;
Bundle resumeArgs = data.readBundle();
- scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state,
+ scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo,
+ voiceInteractor, procState, state,
ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
resumeArgs);
return true;
@@ -735,6 +739,7 @@
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
+ IVoiceInteractor voiceInteractor,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
@@ -748,6 +753,7 @@
info.writeToParcel(data, 0);
curConfig.writeToParcel(data, 0);
compatInfo.writeToParcel(data, 0);
+ data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
data.writeInt(procState);
data.writeBundle(state);
data.writeTypedList(pendingResults);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 52003f1..6b94c4e 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -47,6 +47,8 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import java.util.List;
@@ -77,6 +79,10 @@
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
int flagsMask, int flagsValues, Bundle options) throws RemoteException;
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int flags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) throws RemoteException;
public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask)
@@ -733,4 +739,5 @@
int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215;
int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216;
int SET_ACTIVITY_LABEL_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
+ int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index ac8ac8f..f290e94 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -31,6 +31,8 @@
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import java.io.FileDescriptor;
import java.util.List;
@@ -55,8 +57,9 @@
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- int procState, Bundle state, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed,
+ boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle resumeArgs)
throws RemoteException;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 8681f5c..045fab1 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -22,6 +22,8 @@
import android.app.Notification;
import android.content.ComponentName;
import android.content.Intent;
+import android.service.notification.Condition;
+import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
import android.service.notification.ZenModeConfig;
@@ -53,4 +55,5 @@
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
+ void notifyCondition(in IConditionProvider provider, in Condition condition);
}
\ No newline at end of file
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 028fa68..e58ccb8 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1428,7 +1428,7 @@
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * Like {@link #execStartActivity},
* but accepts an array of activities to be started. Note that active
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
@@ -1442,7 +1442,7 @@
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * Like {@link #execStartActivity},
* but accepts an array of activities to be started. Note that active
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
@@ -1545,8 +1545,7 @@
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
- * but for starting as a particular user.
+ * Like {@link #execStartActivity}, but for starting as a particular user.
*
* @param who The Context from which the activity is being started.
* @param contextThread The main thread of the Context from which the activity
@@ -1616,7 +1615,8 @@
mUiAutomationConnection = uiAutomationConnection;
}
- /*package*/ static void checkStartActivityResult(int res, Object intent) {
+ /** @hide */
+ public static void checkStartActivityResult(int res, Object intent) {
if (res >= ActivityManager.START_SUCCESS) {
return;
}
@@ -1640,6 +1640,9 @@
case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
+ case ActivityManager.START_NOT_VOICE_COMPATIBLE:
+ throw new SecurityException(
+ "Starting under voice control not allowed for: " + intent);
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
new file mode 100644
index 0000000..6820dfd
--- /dev/null
+++ b/core/java/android/app/VoiceInteractor.java
@@ -0,0 +1,235 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+import java.util.WeakHashMap;
+
+/**
+ * Interface for an {@link Activity} to interact with the user through voice.
+ */
+public class VoiceInteractor {
+ static final String TAG = "VoiceInteractor";
+ static final boolean DEBUG = true;
+
+ final Context mContext;
+ final IVoiceInteractor mInteractor;
+ final HandlerCaller mHandlerCaller;
+ final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
+ @Override
+ public void executeMessage(Message msg) {
+ SomeArgs args = (SomeArgs)msg.obj;
+ switch (msg.what) {
+ case MSG_CONFIRMATION_RESULT:
+ if (DEBUG) Log.d(TAG, "onConfirmResult: req="
+ + ((IVoiceInteractorRequest)args.arg2).asBinder()
+ + " confirmed=" + msg.arg1 + " result=" + args.arg3);
+ ((Callback)args.arg1).onConfirmationResult(
+ findRequest((IVoiceInteractorRequest)args.arg2),
+ msg.arg1 != 0, (Bundle)args.arg3);
+ break;
+ case MSG_COMMAND_RESULT:
+ if (DEBUG) Log.d(TAG, "onCommandResult: req="
+ + ((IVoiceInteractorRequest)args.arg2).asBinder()
+ + " result=" + args.arg2);
+ ((Callback)args.arg1).onCommandResult(
+ findRequest((IVoiceInteractorRequest) args.arg2),
+ (Bundle) args.arg3);
+ break;
+ case MSG_CANCEL_RESULT:
+ if (DEBUG) Log.d(TAG, "onCancelResult: req="
+ + ((IVoiceInteractorRequest)args.arg2).asBinder());
+ ((Callback)args.arg1).onCancel(
+ findRequest((IVoiceInteractorRequest) args.arg2));
+ break;
+ }
+ }
+ };
+
+ final WeakHashMap<IBinder, Request> mActiveRequests = new WeakHashMap<IBinder, Request>();
+
+ static final int MSG_CONFIRMATION_RESULT = 1;
+ static final int MSG_COMMAND_RESULT = 2;
+ static final int MSG_CANCEL_RESULT = 3;
+
+ public static class Request {
+ final IVoiceInteractorRequest mRequestInterface;
+
+ Request(IVoiceInteractorRequest requestInterface) {
+ mRequestInterface = requestInterface;
+ }
+
+ public void cancel() {
+ try {
+ mRequestInterface.cancel();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Voice interactor has died", e);
+ }
+ }
+ }
+
+ public static class Callback {
+ VoiceInteractor mInteractor;
+
+ final IVoiceInteractorCallback.Stub mWrapper = new IVoiceInteractorCallback.Stub() {
+ @Override
+ public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+ Bundle result) {
+ mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageIOOO(
+ MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, Callback.this, request,
+ result));
+ }
+
+ @Override
+ public void deliverCommandResult(IVoiceInteractorRequest request, Bundle result) {
+ mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageOOO(
+ MSG_COMMAND_RESULT, Callback.this, request, result));
+ }
+
+ @Override
+ public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
+ mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageOO(
+ MSG_CANCEL_RESULT, Callback.this, request));
+ }
+ };
+
+ public void onConfirmationResult(Request request, boolean confirmed, Bundle result) {
+ }
+
+ public void onCommandResult(Request request, Bundle result) {
+ }
+
+ public void onCancel(Request request) {
+ }
+ }
+
+ VoiceInteractor(Context context, IVoiceInteractor interactor, Looper looper) {
+ mContext = context;
+ mInteractor = interactor;
+ mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
+ }
+
+ Request storeRequest(IVoiceInteractorRequest request) {
+ synchronized (mActiveRequests) {
+ Request req = new Request(request);
+ mActiveRequests.put(request.asBinder(), req);
+ return req;
+ }
+ }
+
+ Request findRequest(IVoiceInteractorRequest request) {
+ synchronized (mActiveRequests) {
+ Request req = mActiveRequests.get(request.asBinder());
+ if (req == null) {
+ throw new IllegalStateException("Received callback without active request: "
+ + request);
+ }
+ return req;
+ }
+ }
+
+ /**
+ * Asynchronously confirms an operation with the user via the trusted system
+ * VoiceinteractionService. This allows an Activity to complete an unsafe operation that
+ * would require the user to touch the screen when voice interaction mode is not enabled.
+ * The result of the confirmation will be returned by calling the
+ * {@link Callback#onConfirmationResult Callback.onConfirmationResult} method.
+ *
+ * In some cases this may be a simple yes / no confirmation or the confirmation could
+ * include context information about how the action will be completed
+ * (e.g. booking a cab might include details about how long until the cab arrives) so the user
+ * can give informed consent.
+ * @param callback Required callback target for interaction results.
+ * @param prompt Optional confirmation text to read to the user as the action being confirmed.
+ * @param extras Additional optional information.
+ * @return Returns a new {@link Request} object representing this operation.
+ */
+ public Request startConfirmation(Callback callback, String prompt, Bundle extras) {
+ try {
+ callback.mInteractor = this;
+ Request req = storeRequest(mInteractor.startConfirmation(
+ mContext.getOpPackageName(), callback.mWrapper, prompt, extras));
+ if (DEBUG) Log.d(TAG, "startConfirmation: req=" + req.mRequestInterface.asBinder()
+ + " prompt=" + prompt + " extras=" + extras);
+ return req;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Voice interactor has died", e);
+ }
+ }
+
+ /**
+ * Asynchronously executes a command using the trusted system VoiceinteractionService.
+ * This allows an Activity to request additional information from the user needed to
+ * complete an action (e.g. booking a table might have several possible times that the
+ * user could select from or an app might need the user to agree to a terms of service).
+ *
+ * The command is a string that describes the generic operation to be performed.
+ * The command will determine how the properties in extras are interpreted and the set of
+ * available commands is expected to grow over time. An example might be
+ * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of
+ * airline check-in. (This is not an actual working example.)
+ * The result of the command will be returned by calling the
+ * {@link Callback#onCommandResult Callback.onCommandResult} method.
+ *
+ * @param callback Required callback target for interaction results.
+ * @param command
+ * @param extras
+ * @return Returns a new {@link Request} object representing this operation.
+ */
+ public Request startCommand(Callback callback, String command, Bundle extras) {
+ try {
+ callback.mInteractor = this;
+ Request req = storeRequest(mInteractor.startCommand(
+ mContext.getOpPackageName(), callback.mWrapper, command, extras));
+ if (DEBUG) Log.d(TAG, "startCommand: req=" + req.mRequestInterface.asBinder()
+ + " command=" + command + " extras=" + extras);
+ return req;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Voice interactor has died", e);
+ }
+ }
+
+ /**
+ * Queries the supported commands available from the VoiceinteractionService.
+ * The command is a string that describes the generic operation to be performed.
+ * An example might be "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number
+ * of bags as part of airline check-in. (This is not an actual working example.)
+ *
+ * @param commands
+ */
+ public boolean[] supportsCommands(String[] commands) {
+ try {
+ boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands);
+ if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res);
+ return res;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Voice interactor has died", e);
+ }
+ }
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d7170e8..d173f19 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1957,4 +1957,48 @@
}
return null;
}
+
+ /**
+ * Called by a profile or device owner to set a user restriction specified
+ * by the key.
+ * <p>
+ * The calling device admin must be a profile or device owner; if it is not,
+ * a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated
+ * with.
+ * @param key The key of the restriction. See the constants in
+ * {@link android.os.UserManager} for the list of keys.
+ */
+ public void addUserRestriction(ComponentName admin, String key) {
+ if (mService != null) {
+ try {
+ mService.setUserRestriction(admin, key, true);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile or device owner to clear a user restriction specified
+ * by the key.
+ * <p>
+ * The calling device admin must be a profile or device owner; if it is not,
+ * a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated
+ * with.
+ * @param key The key of the restriction. See the constants in
+ * {@link android.os.UserManager} for the list of keys.
+ */
+ public void clearUserRestriction(ComponentName admin, String key) {
+ if (mService != null) {
+ try {
+ mService.setUserRestriction(admin, key, false);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 85ba58b..72b3c20 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -119,4 +119,6 @@
void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+
+ void setUserRestriction(in ComponentName who, in String key, boolean enable);
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cbb6cf5..de223a3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2442,6 +2442,14 @@
public static final String APPWIDGET_SERVICE = "appwidget";
/**
+ * Official published name of the (internal) voice interaction manager service.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
+
+ /**
* 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/content/Intent.java b/core/java/android/content/Intent.java
index c0f04af..ae5437b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2804,6 +2804,14 @@
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
/**
+ * Categories for activities that can participate in voice interaction.
+ * An activity that supports this category must be prepared to run with
+ * no UI shown at all (though in some case it may have a UI shown), and
+ * rely on {@link android.app.VoiceInteractor} to interact with the user.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
+ /**
* Set if the activity should be considered as an alternative action to
* the data the user is currently viewing. See also
* {@link #CATEGORY_SELECTED_ALTERNATIVE} for an alternative action that
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ae0899f..488e25f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -74,6 +74,9 @@
ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
+ boolean activitySupportsIntent(in ComponentName className, in Intent intent,
+ String resolvedType);
+
ActivityInfo getReceiverInfo(in ComponentName className, int flags, int userId);
ServiceInfo getServiceInfo(in ComponentName className, int flags, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d981cc1..484a2a1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1235,7 +1235,6 @@
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
-
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports app widgets.
@@ -1244,6 +1243,17 @@
public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets";
/**
+ * @hide
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports
+ * {@link android.service.voice.VoiceInteractionService} and
+ * {@link android.app.VoiceInteractor}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports a home screen that is replaceable
* by third party applications.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8b6ca83..8f19f01 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -50,6 +50,7 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
@@ -147,8 +148,7 @@
private String[] mSeparateProcesses;
private boolean mOnlyCoreApps;
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
- private static final String SDK_CODENAME = "REL".equals(Build.VERSION.CODENAME)
- ? null : Build.VERSION.CODENAME;
+ private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
private int mParseError = PackageManager.INSTALL_SUCCEEDED;
@@ -1200,10 +1200,18 @@
sa.recycle();
if (minCode != null) {
- if (!minCode.equals(SDK_CODENAME)) {
- if (SDK_CODENAME != null) {
+ boolean allowedCodename = false;
+ for (String codename : SDK_CODENAMES) {
+ if (minCode.equals(codename)) {
+ allowedCodename = true;
+ break;
+ }
+ }
+ if (!allowedCodename) {
+ if (SDK_CODENAMES.length > 0) {
outError[0] = "Requires development platform " + minCode
- + " (current platform is " + SDK_CODENAME + ")";
+ + " (current platform is any of "
+ + Arrays.toString(SDK_CODENAMES) + ")";
} else {
outError[0] = "Requires development platform " + minCode
+ " but this is a release platform.";
@@ -1219,10 +1227,18 @@
}
if (targetCode != null) {
- if (!targetCode.equals(SDK_CODENAME)) {
- if (SDK_CODENAME != null) {
+ boolean allowedCodename = false;
+ for (String codename : SDK_CODENAMES) {
+ if (targetCode.equals(codename)) {
+ allowedCodename = true;
+ break;
+ }
+ }
+ if (!allowedCodename) {
+ if (SDK_CODENAMES.length > 0) {
outError[0] = "Requires development platform " + targetCode
- + " (current platform is " + SDK_CODENAME + ")";
+ + " (current platform is any of "
+ + Arrays.toString(SDK_CODENAMES) + ")";
} else {
outError[0] = "Requires development platform " + targetCode
+ " but this is a release platform.";
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 19be2c8..11e3795 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -118,14 +118,22 @@
*/
public static final String CODENAME = getString("ro.build.version.codename");
+ private static final String[] ALL_CODENAMES
+ = getString("ro.build.version.all_codenames").split(",");
+
/**
- * The SDK version to use when accessing resources.
- * Use the current SDK version code. If we are a development build,
- * also allow the previous SDK version + 1.
* @hide
*/
- public static final int RESOURCES_SDK_INT = SDK_INT
- + ("REL".equals(CODENAME) ? 0 : 1);
+ public static final String[] ACTIVE_CODENAMES = "REL".equals(ALL_CODENAMES[0])
+ ? new String[0] : ALL_CODENAMES;
+
+ /**
+ * The SDK version to use when accessing resources.
+ * Use the current SDK version code. For every active development codename
+ * we are operating under, we bump the assumed resource platform version by 1.
+ * @hide
+ */
+ public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
}
/**
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index f671ed9..cdde4c7 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -375,6 +375,7 @@
final ConditionVariable condition = new ConditionVariable();
Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER,
android.Manifest.permission.MASTER_CLEAR,
new BroadcastReceiver() {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 63de9a0..1fe9337 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -300,8 +300,7 @@
/**
* Sets all the user-wide restrictions for this user.
- * Requires the MANAGE_USERS permission or profile/device owner
- * privileges.
+ * Requires the MANAGE_USERS permission.
* @param restrictions the Bundle containing all the restrictions.
*/
public void setUserRestrictions(Bundle restrictions) {
@@ -310,8 +309,7 @@
/**
* Sets all the user-wide restrictions for the specified user.
- * Requires the MANAGE_USERS permission or profile/device owner
- * privileges.
+ * Requires the MANAGE_USERS permission.
* @param restrictions the Bundle containing all the restrictions.
* @param userHandle the UserHandle of the user for whom to set the restrictions.
*/
@@ -325,8 +323,7 @@
/**
* Sets the value of a specific restriction.
- * Requires the MANAGE_USERS permission or profile/device owner
- * privileges.
+ * Requires the MANAGE_USERS permission.
* @param key the key of the restriction
* @param value the value for the restriction
*/
@@ -339,8 +336,7 @@
/**
* @hide
* Sets the value of a specific restriction on a specific user.
- * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission or profile/device owner
- * privileges.
+ * Requires the MANAGE_USERS permission.
* @param key the key of the restriction
* @param value the value for the restriction
* @param userHandle the user whose restriction is to be changed.
@@ -492,7 +488,8 @@
ArrayList<UserHandle> profiles = new ArrayList<UserHandle>();
List<UserInfo> users = new ArrayList<UserInfo>();
try {
- users = mService.getProfiles(UserHandle.myUserId(), true /* enabledOnly */);
+ // TODO: Switch enabledOnly to true once client apps are updated
+ users = mService.getProfiles(UserHandle.myUserId(), false /* enabledOnly */);
} catch (RemoteException re) {
Log.w(TAG, "Could not get user list", re);
return null;
@@ -560,7 +557,7 @@
/**
* Returns information for all users on this device. Requires
* {@link android.Manifest.permission#MANAGE_USERS} permission.
- *
+ *
* @param excludeDying specify if the list should exclude users being
* removed.
* @return the list of users that were created.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7f9f862..b578b48 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -723,6 +723,13 @@
= "android.settings.NOTIFICATION_LISTENER_SETTINGS";
/**
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CONDITION_PROVIDER_SETTINGS
+ = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
+
+ /**
* Activity Action: Show settings for video captioning.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -3311,6 +3318,12 @@
"input_method_selector_visibility";
/**
+ * The currently selected voice interaction service flattened ComponentName.
+ * @hide
+ */
+ public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
+
+ /**
* bluetooth HCI snoop log configuration
* @hide
*/
@@ -4510,6 +4523,11 @@
*/
public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+ /**
+ * @hide
+ */
+ public static final String ENABLED_CONDITION_PROVIDERS = "enabled_condition_providers";
+
/** @hide */
public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
diff --git a/core/java/android/service/notification/Condition.aidl b/core/java/android/service/notification/Condition.aidl
new file mode 100644
index 0000000..432852c
--- /dev/null
+++ b/core/java/android/service/notification/Condition.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.notification;
+
+parcelable Condition;
+
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
new file mode 100644
index 0000000..cfd40f3
--- /dev/null
+++ b/core/java/android/service/notification/Condition.java
@@ -0,0 +1,119 @@
+/**
+ * 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.notification;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Condition information from condition providers.
+ *
+ * @hide
+ */
+public class Condition implements Parcelable {
+
+ public static final int FLAG_RELEVANT_NOW = 1 << 0;
+ public static final int FLAG_RELEVANT_ALWAYS = 1 << 1;
+
+ public final Uri id;
+ public String caption;
+ public boolean state;
+ public int flags;
+
+
+ public Condition(Uri id, String caption, boolean state, int flags) {
+ if (id == null) throw new IllegalArgumentException("id is required");
+ if (caption == null) throw new IllegalArgumentException("caption is required");
+ this.id = id;
+ this.caption = caption;
+ this.state = state;
+ this.flags = flags;
+ }
+
+ private Condition(Parcel source) {
+ id = Uri.CREATOR.createFromParcel(source);
+ caption = source.readString();
+ state = source.readInt() == 1;
+ flags = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(id, 0);
+ dest.writeString(caption);
+ dest.writeInt(state ? 1 : 0);
+ dest.writeInt(flags);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder(Condition.class.getSimpleName()).append('[')
+ .append("id=").append(id)
+ .append(",caption=").append(caption)
+ .append(",state=").append(state)
+ .append(",flags=").append(flags)
+ .append(']').toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Condition)) return false;
+ if (o == this) return true;
+ final Condition other = (Condition) o;
+ return Objects.equals(other.id, id)
+ && Objects.equals(other.caption, caption)
+ && other.state == state
+ && other.flags == flags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, caption, state, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public Condition copy() {
+ final Parcel parcel = Parcel.obtain();
+ try {
+ writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return new Condition(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ public static final Parcelable.Creator<Condition> CREATOR
+ = new Parcelable.Creator<Condition>() {
+ @Override
+ public Condition createFromParcel(Parcel source) {
+ return new Condition(source);
+ }
+
+ @Override
+ public Condition[] newArray(int size) {
+ return new Condition[size];
+ }
+ };
+}
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
new file mode 100644
index 0000000..8777e50
--- /dev/null
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -0,0 +1,141 @@
+/*
+ * 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.notification;
+
+import android.annotation.SdkConstant;
+import android.app.INotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * A service that provides conditions about boolean state.
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * <service android:name=".MyConditionProvider"
+ * android:label="@string/service_name"
+ * android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.notification.ConditionProviderService" />
+ * </intent-filter>
+ * </service></pre>
+ *
+ * @hide
+ */
+public abstract class ConditionProviderService extends Service {
+ private final String TAG = ConditionProviderService.class.getSimpleName()
+ + "[" + getClass().getSimpleName() + "]";
+
+ private Provider mProvider;
+ private INotificationManager mNoMan;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "android.service.notification.ConditionProviderService";
+
+
+ abstract public Condition[] queryConditions(int relevance);
+ abstract public Condition[] getConditions(Uri[] conditionIds);
+ abstract public boolean subscribe(Uri conditionId);
+ abstract public boolean unsubscribe(Uri conditionId);
+
+ private final INotificationManager getNotificationInterface() {
+ if (mNoMan == null) {
+ mNoMan = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ }
+ return mNoMan;
+ }
+
+ public final void notifyCondition(Condition condition) {
+ if (!isBound()) return;
+ try {
+ getNotificationInterface().notifyCondition(mProvider, condition);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (mProvider == null) {
+ mProvider = new Provider();
+ }
+ return mProvider;
+ }
+
+ private boolean isBound() {
+ if (mProvider == null) {
+ Log.w(TAG, "Condition provider service not yet bound.");
+ return false;
+ }
+ return true;
+ }
+
+ private final class Provider extends IConditionProvider.Stub {
+ private final ConditionProviderService mService = ConditionProviderService.this;
+
+ @Override
+ public Condition[] queryConditions(int relevance) {
+ try {
+ return mService.queryConditions(relevance);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running queryConditions", t);
+ return null;
+ }
+ }
+
+ @Override
+ public Condition[] getConditions(Uri[] conditionIds) {
+ try {
+ return mService.getConditions(conditionIds);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running getConditions", t);
+ return null;
+ }
+ }
+
+ @Override
+ public boolean subscribe(Uri conditionId) {
+ try {
+ return mService.subscribe(conditionId);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running subscribe", t);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean unsubscribe(Uri conditionId) {
+ try {
+ return mService.unsubscribe(conditionId);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running unsubscribe", t);
+ return false;
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/notification/IConditionProvider.aidl b/core/java/android/service/notification/IConditionProvider.aidl
new file mode 100644
index 0000000..cb582da
--- /dev/null
+++ b/core/java/android/service/notification/IConditionProvider.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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.notification;
+
+import android.net.Uri;
+import android.service.notification.Condition;
+
+/** @hide */
+interface IConditionProvider {
+ Condition[] queryConditions(int relevance);
+ Condition[] getConditions(in Uri[] conditionIds);
+ boolean subscribe(in Uri conditionId);
+ boolean unsubscribe(in Uri conditionId);
+}
\ No newline at end of file
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
new file mode 100644
index 0000000..e9e2f4c
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -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 android.service.voice;
+
+/**
+ * @hide
+ */
+oneway interface IVoiceInteractionService {
+}
diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl
new file mode 100644
index 0000000..7dbf66b
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * @hide
+ */
+interface IVoiceInteractionSession {
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
new file mode 100644
index 0000000..ed93b74
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -0,0 +1,77 @@
+/**
+ * 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.annotation.SdkConstant;
+import android.app.Instrumentation;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.app.IVoiceInteractionManagerService;
+
+public class VoiceInteractionService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.voice.VoiceInteractionService";
+
+ /**
+ * Name under which a VoiceInteractionService component publishes information about itself.
+ * This meta-data should reference an XML resource containing a
+ * <code><{@link
+ * android.R.styleable#VoiceInteractionService voice-interaction-service}></code> tag.
+ */
+ public static final String SERVICE_META_DATA = "android.voice_interaction";
+
+ IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
+ };
+
+ IVoiceInteractionManagerService mSystemService;
+
+ public void startVoiceActivity(Intent intent, VoiceInteractionSession session) {
+ try {
+ int res = mSystemService.startVoiceActivity(intent,
+ intent.resolveType(getContentResolver()),
+ mInterface, session.mSession, session.mInteractor);
+ Instrumentation.checkStartActivityResult(res, intent);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
new file mode 100644
index 0000000..59544be
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -0,0 +1,195 @@
+/**
+ * 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.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+public abstract class VoiceInteractionSession {
+ static final String TAG = "VoiceInteractionSession";
+ static final boolean DEBUG = true;
+
+ final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
+ @Override
+ public IVoiceInteractorRequest startConfirmation(String callingPackage,
+ IVoiceInteractorCallback callback, String prompt, Bundle extras) {
+ Request request = findRequest(callback, true);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ prompt, extras));
+ return request.mInterface;
+ }
+
+ @Override
+ public IVoiceInteractorRequest startCommand(String callingPackage,
+ IVoiceInteractorCallback callback, String command, Bundle extras) {
+ Request request = findRequest(callback, true);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ command, extras));
+ return request.mInterface;
+ }
+
+ @Override
+ public boolean[] supportsCommands(String callingPackage, String[] commands) {
+ Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS,
+ 0, new Caller(callingPackage, Binder.getCallingUid()), commands);
+ SomeArgs args = mHandlerCaller.sendMessageAndWait(msg);
+ if (args != null) {
+ boolean[] res = (boolean[])args.arg1;
+ args.recycle();
+ return res;
+ }
+ return new boolean[commands.length];
+ }
+ };
+
+ final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
+ };
+
+ public static class Request {
+ final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+ }
+ };
+ final IVoiceInteractorCallback mCallback;
+ final HandlerCaller mHandlerCaller;
+ Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) {
+ mCallback = callback;
+ mHandlerCaller = handlerCaller;
+ }
+
+ public void sendConfirmResult(boolean confirmed, Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+ + " confirmed=" + confirmed + " result=" + result);
+ mCallback.deliverConfirmationResult(mInterface, confirmed, result);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void sendCommandResult(Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+ + " result=" + result);
+ mCallback.deliverCommandResult(mInterface, result);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void sendCancelResult() {
+ try {
+ if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
+ mCallback.deliverCancel(mInterface);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public static class Caller {
+ final String packageName;
+ final int uid;
+
+ Caller(String _packageName, int _uid) {
+ packageName = _packageName;
+ uid = _uid;
+ }
+ }
+
+ static final int MSG_START_CONFIRMATION = 1;
+ static final int MSG_START_COMMAND = 2;
+ static final int MSG_SUPPORTS_COMMANDS = 3;
+ static final int MSG_CANCEL = 4;
+
+ final Context mContext;
+ final HandlerCaller mHandlerCaller;
+ final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
+ @Override
+ public void executeMessage(Message msg) {
+ SomeArgs args = (SomeArgs)msg.obj;
+ switch (msg.what) {
+ case MSG_START_CONFIRMATION:
+ if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface
+ + " prompt=" + args.arg3 + " extras=" + args.arg4);
+ onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3,
+ (Bundle)args.arg4);
+ break;
+ case MSG_START_COMMAND:
+ if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface
+ + " command=" + args.arg3 + " extras=" + args.arg4);
+ onCommand((Caller) args.arg1, (Request) args.arg2, (String) args.arg3,
+ (Bundle) args.arg4);
+ break;
+ case MSG_SUPPORTS_COMMANDS:
+ if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2);
+ args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2);
+ break;
+ case MSG_CANCEL:
+ if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface);
+ onCancel((Request)args.arg1);
+ break;
+ }
+ }
+ };
+
+ final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
+
+ public VoiceInteractionSession(Context context) {
+ this(context, new Handler());
+ }
+
+ public VoiceInteractionSession(Context context, Handler handler) {
+ mContext = context;
+ mHandlerCaller = new HandlerCaller(context, handler.getLooper(),
+ mHandlerCallerCallback, true);
+ }
+
+ Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) {
+ synchronized (this) {
+ Request req = mActiveRequests.get(callback.asBinder());
+ if (req != null) {
+ if (newRequest) {
+ throw new IllegalArgumentException("Given request callback " + callback
+ + " is already active");
+ }
+ return req;
+ }
+ req = new Request(callback, mHandlerCaller);
+ mActiveRequests.put(callback.asBinder(), req);
+ return req;
+ }
+ }
+
+ public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands);
+ public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras);
+ public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
+ public abstract void onCancel(Request request);
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 84d1328..c8e229a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2375,24 +2375,19 @@
static final int PFLAG3_CALLED_SUPER = 0x10;
/**
- * Flag indicating that an view will be clipped to its outline.
- */
- static final int PFLAG3_CLIP_TO_OUTLINE = 0x20;
-
- /**
* Flag indicating that a view's outline has been specifically defined.
*/
- static final int PFLAG3_OUTLINE_DEFINED = 0x40;
+ static final int PFLAG3_OUTLINE_DEFINED = 0x20;
/**
* Flag indicating that we're in the process of applying window insets.
*/
- static final int PFLAG3_APPLYING_INSETS = 0x80;
+ static final int PFLAG3_APPLYING_INSETS = 0x40;
/**
* Flag indicating that we're in the process of fitting system windows using the old method.
*/
- static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100;
+ static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
/**
* Flag indicating that nested scrolling is enabled for this view.
@@ -3258,9 +3253,7 @@
/**
* Stores the outline of the view, passed down to the DisplayList level for
- * defining shadow shape and clipping.
- *
- * TODO: once RenderNode is long-lived, remove this and rely on native copy.
+ * defining shadow shape.
*/
private Outline mOutline;
@@ -10572,16 +10565,13 @@
/**
* Sets the outline of the view, which defines the shape of the shadow it
- * casts, and can used for clipping.
+ * casts.
* <p>
* If the outline is not set or is null, shadows will be cast from the
- * bounds of the View, and clipToOutline will be ignored.
+ * bounds of the View.
*
* @param outline The new outline of the view.
* Must be {@link android.graphics.Outline#isValid() valid.}
- *
- * @see #getClipToOutline()
- * @see #setClipToOutline(boolean)
*/
public void setOutline(@Nullable Outline outline) {
if (outline != null && !outline.isValid()) {
@@ -10595,46 +10585,32 @@
} else {
// always copy the path since caller may reuse
if (mOutline == null) {
- mOutline = new Outline();
+ mOutline = new Outline(outline);
}
- mOutline.set(outline);
}
mRenderNode.setOutline(mOutline);
}
- /**
- * Returns whether the outline of the View will be used for clipping.
- *
- * @see #setOutline(Outline)
- */
- public final boolean getClipToOutline() {
- return ((mPrivateFlags3 & PFLAG3_CLIP_TO_OUTLINE) != 0);
- }
+ // TODO: remove
+ public final boolean getClipToOutline() { return false; }
+ public void setClipToOutline(boolean clipToOutline) {}
- /**
- * Sets whether the outline of the View will be used for clipping.
- * <p>
- * The current implementation of outline clipping uses
- * {@link Canvas#clipPath(Path) path clipping},
- * and thus does not support anti-aliasing, and is expensive in terms of
- * graphics performance. Therefore, it is strongly recommended that this
- * property only be set temporarily, as in an animation. For the same
- * reasons, there is no parallel XML attribute for this property.
- * <p>
- * If the outline of the view is not set or is empty, no clipping will be
- * performed.
- *
- * @see #setOutline(Outline)
- */
- public void setClipToOutline(boolean clipToOutline) {
- // TODO : Add a fast invalidation here.
- if (getClipToOutline() != clipToOutline) {
- if (clipToOutline) {
- mPrivateFlags3 |= PFLAG3_CLIP_TO_OUTLINE;
+ private void queryOutlineFromBackgroundIfUndefined() {
+ if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
+ // Outline not currently defined, query from background
+ if (mOutline == null) {
+ mOutline = new Outline();
} else {
- mPrivateFlags3 &= ~PFLAG3_CLIP_TO_OUTLINE;
+ mOutline.markInvalid();
}
- mRenderNode.setClipToOutline(clipToOutline);
+ if (mBackground.getOutline(mOutline)) {
+ if (!mOutline.isValid()) {
+ throw new IllegalStateException("Background drawable failed to build outline");
+ }
+ mRenderNode.setOutline(mOutline);
+ } else {
+ mRenderNode.setOutline(null);
+ }
}
}
@@ -14893,11 +14869,7 @@
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
- if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
- // Outline not currently define, query from background
- mOutline = background.getOutline();
- mRenderNode.setOutline(mOutline);
- }
+ queryOutlineFromBackgroundIfUndefined();
}
// Attempt to use a display list if requested.
@@ -15299,7 +15271,7 @@
* @param drawable the drawable to invalidate
*/
@Override
- public void invalidateDrawable(Drawable drawable) {
+ public void invalidateDrawable(@NonNull Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
@@ -15307,6 +15279,10 @@
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
+
+ if (drawable == mBackground) {
+ queryOutlineFromBackgroundIfUndefined();
+ }
}
}
diff --git a/core/java/android/webkit/ClientCertRequest.java b/core/java/android/webkit/ClientCertRequest.java
new file mode 100644
index 0000000..8951786
--- /dev/null
+++ b/core/java/android/webkit/ClientCertRequest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.webkit;
+
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+/**
+ * ClientCertRequest: The user receives an instance of this class as
+ * a parameter of {@link WebViewClient#onReceivedClientCertRequest}.
+ * The request includes the parameters to choose the client certificate,
+ * such as the host name and the port number requesting the cert, the acceptable
+ * key types and the principals.
+ *
+ * The user should call one of the interface methods to indicate how to deal
+ * with the client certificate request. All methods should be called on
+ * UI thread.
+ *
+ * WebView caches the {@link #proceed} and {@link #cancel} responses in memory
+ * and uses them to handle future client certificate requests for the same
+ * host/port pair. The user can clear the cached data using
+ * {@link WebView#clearClientCertPreferences}.
+ *
+ * TODO(sgurun) unhide
+ * @hide
+ */
+public interface ClientCertRequest {
+ /**
+ * Returns the acceptable types of asymmetric keys (can be null).
+ */
+ public String[] getKeyTypes();
+
+ /**
+ * Returns the acceptable certificate issuers for the certificate
+ * matching the private key (can be null).
+ */
+ public Principal[] getPrincipals();
+
+ /**
+ * Returns the host name of the server requesting the certificate.
+ */
+ public String getHost();
+
+ /**
+ * Returns the port number of the server requesting the certificate.
+ */
+ public int getPort();
+
+ /**
+ * Proceed with the specified private key and client certificate chain.
+ * Remember the user's positive choice and use it for future requests.
+ */
+ public void proceed(PrivateKey privateKey, X509Certificate[] chain);
+
+ /**
+ * Ignore the request for now. Do not remember user's choice.
+ */
+ public void ignore();
+
+ /**
+ * Cancel this request. Remember the user's choice and use it for
+ * future requests.
+ */
+ public void cancel();
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index d2e7324..c914e52 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -485,7 +485,7 @@
* @param privateBrowsing whether this WebView will be initialized in
* private mode
*
- * @deprecated Private browsing is no longer supported directly via
+ * @deprecated Private browsing is no longer supported directly via
* WebView and will be removed in a future release. Prefer using
* {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
* and {@link WebStorage} for fine-grained control of privacy data.
@@ -1476,6 +1476,24 @@
}
/**
+ * Clears the client certificate preferences table stored in response
+ * to proceeding/cancelling client cert requests. Note that webview
+ * automatically clears these preferences when it receives a
+ * {@link KeyChain.ACTION_STORAGE_CHANGED}
+ *
+ * @param resultCallback A callback to be invoked when client certs are cleared.
+ * The embedder can pass null if not interested in the callback.
+ *
+ * TODO(sgurun) unhide
+ * @hide
+ */
+ public void clearClientCertPreferences(ValueCallback<Void> resultCallback) {
+ checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearClientCertPreferences");
+ mProvider.clearClientCertPreferences(resultCallback);
+ }
+
+ /**
* Gets the WebBackForwardList for this WebView. This contains the
* back/forward list for use in querying each item in the history stack.
* This is a copy of the private WebBackForwardList so it contains only a
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index e8974c6..688c251 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -22,6 +22,8 @@
import android.view.KeyEvent;
import android.view.ViewRootImpl;
+import java.security.Principal;
+
public class WebViewClient {
/**
@@ -205,6 +207,30 @@
}
/**
+ * Notify the host application to handle a SSL client certificate
+ * request. The host application is responsible for showing the UI
+ * if desired and providing the keys. There are three ways to
+ * respond: proceed(), cancel() or ignore(). Webview remembers the
+ * response if proceed() or cancel() is called and does not
+ * call onReceivedClientCertRequest() again for the same host and port
+ * pair. Webview does not remember the response if ignore() is called.
+ *
+ * This method is called on the UI thread. During the callback, the
+ * connection is suspended.
+ *
+ * The default behavior is to cancel, returning no client certificate.
+ *
+ * @param view The WebView that is initiating the callback
+ * @param request An instance of a {@link ClientCertRequest}
+ *
+ * TODO(sgurun) unhide
+ * @hide
+ */
+ public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
+ request.cancel();
+ }
+
+ /**
* Notifies the host application that the WebView received an HTTP
* authentication request. The host application can use the supplied
* {@link HttpAuthHandler} to set the WebView's response to the request.
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 5081ff5..efa5497 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -198,6 +198,8 @@
public void clearSslPreferences();
+ public void clearClientCertPreferences(ValueCallback<Void> resultCallback);
+
public WebBackForwardList copyBackForwardList();
public void setFindListener(WebView.FindListener listener);
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
new file mode 100644
index 0000000..e3c0728
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.content.Intent;
+
+import com.android.internal.app.IVoiceInteractor;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+
+interface IVoiceInteractionManagerService {
+ int startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service,
+ IVoiceInteractionSession session, IVoiceInteractor interactor);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
new file mode 100644
index 0000000..737906a
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * IPC interface for an application to perform calls through a VoiceInteractor.
+ */
+interface IVoiceInteractor {
+ IVoiceInteractorRequest startConfirmation(String callingPackage,
+ IVoiceInteractorCallback callback, String prompt, in Bundle extras);
+ IVoiceInteractorRequest startCommand(String callingPackage,
+ IVoiceInteractorCallback callback, String command, in Bundle extras);
+ boolean[] supportsCommands(String callingPackage, in String[] commands);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
new file mode 100644
index 0000000..f5392e9
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * IPC interface for an application to receive callbacks from the voice system.
+ */
+oneway interface IVoiceInteractorCallback {
+ void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+ in Bundle result);
+ void deliverCommandResult(IVoiceInteractorRequest request, in Bundle result);
+ void deliverCancel(IVoiceInteractorRequest request);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl
new file mode 100644
index 0000000..ce2902d
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * IPC interface identifying a request from an application calling through an IVoiceInteractor.
+ */
+interface IVoiceInteractorRequest {
+ void cancel();
+}
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index d9e3ef6..40834ba 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -85,7 +85,27 @@
public void sendMessage(Message msg) {
mH.sendMessage(msg);
}
-
+
+ public SomeArgs sendMessageAndWait(Message msg) {
+ if (Looper.myLooper() == mH.getLooper()) {
+ throw new IllegalStateException("Can't wait on same thread as looper");
+ }
+ SomeArgs args = (SomeArgs)msg.obj;
+ args.mWaitState = SomeArgs.WAIT_WAITING;
+ mH.sendMessage(msg);
+ synchronized (args) {
+ while (args.mWaitState == SomeArgs.WAIT_WAITING) {
+ try {
+ args.wait();
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
+ }
+ args.mWaitState = SomeArgs.WAIT_NONE;
+ return args;
+ }
+
public Message obtainMessage(int what) {
return mH.obtainMessage(what);
}
@@ -136,6 +156,14 @@
return mH.obtainMessage(what, arg1, 0, args);
}
+ public Message obtainMessageIOOO(int what, int arg1, Object arg2, Object arg3, Object arg4) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg2;
+ args.arg2 = arg3;
+ args.arg3 = arg4;
+ return mH.obtainMessage(what, arg1, 0, args);
+ }
+
public Message obtainMessageOO(int what, Object arg1, Object arg2) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = arg1;
@@ -161,6 +189,17 @@
return mH.obtainMessage(what, 0, 0, args);
}
+ public Message obtainMessageOOOOO(int what, Object arg1, Object arg2,
+ Object arg3, Object arg4, Object arg5) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ args.arg4 = arg4;
+ args.arg5 = arg5;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
public Message obtainMessageIIII(int what, int arg1, int arg2,
int arg3, int arg4) {
SomeArgs args = SomeArgs.obtain();
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index 6fb72f1..7edf4cc 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -35,6 +35,11 @@
private boolean mInPool;
+ static final int WAIT_NONE = 0;
+ static final int WAIT_WAITING = 1;
+ static final int WAIT_FINISHED = 2;
+ int mWaitState = WAIT_NONE;
+
public Object arg1;
public Object arg2;
public Object arg3;
@@ -70,6 +75,9 @@
if (mInPool) {
throw new IllegalStateException("Already recycled.");
}
+ if (mWaitState != WAIT_NONE) {
+ return;
+ }
synchronized (sPoolLock) {
clear();
if (sPoolSize < MAX_POOL_SIZE) {
diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java
index e374563..bf36bb1 100644
--- a/core/java/com/android/server/SystemService.java
+++ b/core/java/com/android/server/SystemService.java
@@ -123,6 +123,38 @@
public void onBootPhase(int phase) {}
/**
+ * Called when a new user is starting, for system services to initialize any per-user
+ * state they maintain for running users.
+ * @param userHandle The identifier of the user.
+ */
+ public void onStartUser(int userHandle) {}
+
+ /**
+ * Called when switching to a different foreground user, for system services that have
+ * special behavior for whichever user is currently in the foreground. This is called
+ * before any application processes are aware of the new user.
+ * @param userHandle The identifier of the user.
+ */
+ public void onSwitchUser(int userHandle) {}
+
+ /**
+ * Called when an existing user is stopping, for system services to finalize any per-user
+ * state they maintain for running users. This is called prior to sending the SHUTDOWN
+ * broadcast to the user; it is a good place to stop making use of any resources of that
+ * user (such as binding to a service running in the user).
+ * @param userHandle The identifier of the user.
+ */
+ public void onStopUser(int userHandle) {}
+
+ /**
+ * Called when an existing user is stopping, for system services to finalize any per-user
+ * state they maintain for running users. This is called after all application process
+ * teardown of the user is complete.
+ * @param userHandle The identifier of the user.
+ */
+ public void onCleanupUser(int userHandle) {}
+
+ /**
* Publish the service so it is accessible to other services and apps.
*/
protected final void publishBinderService(String name, IBinder service) {
diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java
index eb8df0e..87a50a9 100644
--- a/core/java/com/android/server/SystemServiceManager.java
+++ b/core/java/com/android/server/SystemServiceManager.java
@@ -131,6 +131,58 @@
}
}
+ public void startUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onStartUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting start of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void switchUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onSwitchUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void stopUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onStopUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void cleanupUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onCleanupUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
/** Sets the safe mode flag for services to query. */
public void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index a4efed7..01f4d3a 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -30,15 +30,16 @@
#include "android_util_Binder.h"
#include "JNIHelp.h"
-#include <sys/errno.h>
-#include <sys/resource.h>
-#include <sys/types.h>
-#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <grp.h>
+#include <inttypes.h>
#include <pwd.h>
#include <signal.h>
+#include <sys/errno.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
#define POLICY_DEBUG 0
@@ -159,7 +160,7 @@
void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint grp)
{
- ALOGV("%s tid=%d grp=%d", __func__, tid, grp);
+ ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp);
SchedPolicy sp = (SchedPolicy) grp;
int res = set_sched_policy(tid, sp);
if (res != NO_ERROR) {
@@ -169,7 +170,7 @@
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
- ALOGV("%s pid=%d grp=%d", __func__, pid, grp);
+ ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
DIR *d;
FILE *fp;
char proc_path[255];
@@ -322,7 +323,7 @@
}
}
- //ALOGI("Setting priority of %d: %d, getpriority returns %d\n",
+ //ALOGI("Setting priority of %" PRId32 ": %" PRId32 ", getpriority returns %d\n",
// pid, pri, getpriority(PRIO_PROCESS, pid));
}
@@ -340,7 +341,7 @@
if (errno != 0) {
signalExceptionForPriorityError(env, errno);
}
- //ALOGI("Returning priority of %d: %d\n", pid, pri);
+ //ALOGI("Returning priority of %" PRId32 ": %" PRId32 "\n", pid, pri);
return pri;
}
@@ -362,7 +363,7 @@
int fd = open(text, O_WRONLY);
if (fd >= 0) {
- sprintf(text, "%d", pid);
+ sprintf(text, "%" PRId32, pid);
write(fd, text, strlen(text));
close(fd);
}
@@ -403,7 +404,7 @@
static int pid_compare(const void* v1, const void* v2)
{
- //ALOGI("Compare %d vs %d\n", *((const jint*)v1), *((const jint*)v2));
+ //ALOGI("Compare %" PRId32 " vs %" PRId32 "\n", *((const jint*)v1), *((const jint*)v2));
return *((const jint*)v1) - *((const jint*)v2);
}
@@ -517,7 +518,7 @@
return;
}
- //ALOGI("Clearing %d sizes", count);
+ //ALOGI("Clearing %" PRId32 " sizes", count);
for (i=0; i<count; i++) {
sizesArray[i] = 0;
}
@@ -556,7 +557,7 @@
}
char* end;
sizesArray[i] = strtoll(num, &end, 10);
- //ALOGI("Field %s = %d", field.string(), sizesArray[i]);
+ //ALOGI("Field %s = %" PRId64, field.string(), sizesArray[i]);
foundCount++;
break;
}
@@ -758,7 +759,7 @@
}
}
- //ALOGI("Field %d: %d-%d dest=%d mode=0x%x\n", i, start, end, di, mode);
+ //ALOGI("Field %" PRId32 ": %" PRId32 "-%" PRId32 " dest=%" PRId32 " mode=0x%" PRIx32 "\n", i, start, end, di, mode);
if ((mode&(PROC_OUT_FLOAT|PROC_OUT_LONG|PROC_OUT_STRING)) != 0) {
char c = buffer[end];
@@ -857,7 +858,7 @@
void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig)
{
if (pid > 0) {
- ALOGI("Sending signal. PID: %d SIG: %d", pid, sig);
+ ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
kill(pid, sig);
}
}
@@ -887,7 +888,7 @@
{
char filename[64];
- snprintf(filename, sizeof(filename), "/proc/%d/smaps", pid);
+ snprintf(filename, sizeof(filename), "/proc/%" PRId32 "/smaps", pid);
FILE * file = fopen(filename, "r");
if (!file) {
@@ -899,7 +900,7 @@
jlong pss = 0;
while (fgets(line, sizeof(line), file)) {
jlong v;
- if (sscanf(line, "Pss: %lld kB", &v) == 1) {
+ if (sscanf(line, "Pss: %" SCNd64 " kB", &v) == 1) {
pss += v;
}
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 8141a00..c293c7a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -508,7 +508,7 @@
nsecs_t presentedTimesNanoSrc[frameCount];
for (size_t i = 0; i < frameCount; i++) {
- nsecs_t presentedTimeNano = stats.desiredPresentTimesNano[i];
+ nsecs_t presentedTimeNano = stats.actualPresentTimesNano[i];
if (presentedTimeNano == INT64_MAX) {
presentedTimeNano = gWindowContentFrameStatsClassInfo.UNDEFINED_TIME_NANO;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57e845f..4f093a8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2056,6 +2056,13 @@
android:description="@string/permdesc_bindWallpaper"
android:protectionLevel="signature|system" />
+ <!-- Must be required by a {@link android.service.voice.VoiceInteractionService},
+ to ensure that only the system can bind to it. -->
+ <permission android:name="android.permission.BIND_VOICE_INTERACTION"
+ android:label="@string/permlab_bindVoiceInteraction"
+ android:description="@string/permdesc_bindVoiceInteraction"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
to ensure that only the system can bind to it.
@hide -->
@@ -2620,6 +2627,15 @@
android:description="@string/permdesc_bindNotificationListenerService"
android:protectionLevel="signature" />
+ <!-- Must be required by an {@link
+ android.service.notification.ConditionProviderService},
+ to ensure that only the system can bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_CONDITION_PROVIDER_SERVICE"
+ android:label="@string/permlab_bindConditionProviderService"
+ android:description="@string/permdesc_bindConditionProviderService"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to call into a carrier setup flow. It is up to the
carrier setup application to enforce that this permission is required
@hide This is not a third-party API (intended for OEMs and system apps). -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 508a557..326485d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6267,13 +6267,21 @@
</declare-styleable>
<!-- Use <code>recognition-service</code> as the root tag of the XML resource that
- describes a {@link android.speech.RecognitionService}, which is reference from
+ describes a {@link android.speech.RecognitionService}, which is referenced from
its {@link android.speech.RecognitionService#SERVICE_META_DATA} meta-data entry.
Described here are the attributes that can be included in that tag. -->
<declare-styleable name="RecognitionService">
<attr name="settingsActivity" />
</declare-styleable>
+ <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that
+ describes a {@link android.service.voice.VoiceInteractionService}, which is referenced from
+ its {@link android.service.voice.VoiceInteractionService#SERVICE_META_DATA} meta-data entry.
+ Described here are the attributes that can be included in that tag. -->
+ <declare-styleable name="VoiceInteractionService">
+ <attr name="settingsActivity" />
+ </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/strings.xml b/core/res/res/values/strings.xml
index 6d4ceef..cb52db2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1068,6 +1068,12 @@
interface of a wallpaper. 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_bindVoiceInteraction">bind to a voice interactor</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_bindVoiceInteraction">Allows the holder to bind to the top-level
+ 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_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
@@ -2048,6 +2054,11 @@
<string name="permdesc_bindNotificationListenerService">Allows the holder to bind to the top-level interface of a notification listener 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_bindConditionProviderService">bind to a condition provider service</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_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider 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_invokeCarrierSetup">invoke the carrier-provided configuration app</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_invokeCarrierSetup">Allows the holder to invoke the carrier-provided configuration app. Should never be needed for normal apps.</string>
@@ -3797,6 +3808,8 @@
<!-- Label to show for a service that is running because it is observing
the user's notifications. -->
<string name="notification_listener_binding_label">Notification listener</string>
+ <!-- Label to show for a service that is running because it is providing conditions. -->
+ <string name="condition_provider_service_binding_label">Condition provider</string>
<!-- Do Not Translate: Alternate eri.xml -->
<string name="alternate_eri_file">/data/eri.xml</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bb0d184..431dab8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1580,6 +1580,7 @@
<java-symbol type="string" name="low_internal_storage_view_text" />
<java-symbol type="string" name="low_internal_storage_view_title" />
<java-symbol type="string" name="notification_listener_binding_label" />
+ <java-symbol type="string" name="condition_provider_service_binding_label" />
<java-symbol type="string" name="report" />
<java-symbol type="string" name="select_input_method" />
<java-symbol type="string" name="select_keyboard_layout_notification_title" />
diff --git a/docs/html/guide/topics/data/backup.jd b/docs/html/guide/topics/data/backup.jd
index 4903852..f09ff9e 100644
--- a/docs/html/guide/topics/data/backup.jd
+++ b/docs/html/guide/topics/data/backup.jd
@@ -899,8 +899,9 @@
{@code tools/} path:
<pre class="no-pretty-print">adb shell bmgr enable true</pre>
</li>
- <li>If using a device, open the system <b>Settings</b>, select <b>Privacy</b>, then enable
-<b>Back up my data</b> and <b>Automatic restore</b>.
+ <li>If using a device, open the system <b>Settings</b>, select
+ <b>Backup & reset</b>, then enable
+ <b>Back up my data</b> and <b>Automatic restore</b>.</li>
</ul>
</li>
<li>Open your application and initialize some data
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 4ea7ec7..d3aac79 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -16,18 +16,19 @@
package android.graphics;
+import android.graphics.drawable.Drawable;
import android.view.View;
/**
* Defines an area of content.
*
- * Can be used with a View or Drawable to drive the shape of shadows cast by a
+ * Can be used with a View, or computed by a Drawable, to drive the shape of shadows cast by a
* View, and allowing Views to clip inner content.
*
* @see View#setOutline(Outline)
- * @see View#setClipToOutline(boolean)
+ * @see Drawable#getOutline(Outline)
*/
-public class Outline {
+public final class Outline {
/** @hide */
public Rect mRect;
@@ -44,22 +45,29 @@
public Outline() {}
/**
+ * Constructs an Outline with a copy of the data in src.
+ */
+ public Outline(Outline src) {
+ set(src);
+ }
+
+ /** @hide */
+ public void markInvalid() {
+ mRadius = 0;
+ mRect = null;
+ mPath = null;
+ }
+
+ /**
* Returns whether the Outline is valid for use with a View.
* <p>
* Outlines are invalid when constructed until a setter method is called.
*/
- public final boolean isValid() {
+ public boolean isValid() {
return mRect != null || mPath != null;
}
/**
- * @hide
- */
- public final boolean canClip() {
- return mPath == null;
- }
-
- /**
* Replace the contents of this Outline with the contents of src.
*/
public void set(Outline src) {
@@ -81,9 +89,20 @@
/**
* Sets the Outline to the rounded rect defined by the input rect, and corner radius.
- * <p>
- * Outlines produced by this method support
- * {@link View#setClipToOutline(boolean) View clipping.}
+ */
+ public void setRect(int left, int top, int right, int bottom) {
+ setRoundRect(left, top, right, bottom, 0.0f);
+ }
+
+ /**
+ * Convenience for {@link #setRect(int, int, int, int)}
+ */
+ public void setRect(Rect rect) {
+ setRect(rect.left, rect.top, rect.right, rect.bottom);
+ }
+
+ /**
+ * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
*/
public void setRoundRect(int left, int top, int right, int bottom, float radius) {
if (mRect == null) mRect = new Rect();
@@ -93,9 +112,16 @@
}
/**
+ * Convenience for {@link #setRoundRect(int, int, int, int, float)}
+ * @param rect
+ * @param radius
+ */
+ public void setRoundRect(Rect rect, float radius) {
+ setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
+ }
+
+ /**
* Sets the Constructs an Outline from a {@link android.graphics.Path#isConvex() convex path}.
- *
- * @hide
*/
public void setConvexPath(Path convexPath) {
if (!convexPath.isConvex()) {
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 74d1219..b9d5e19 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -864,22 +864,21 @@
}
/**
- * Returns the outline for this drawable if defined, null if not.
+ * Called to get the drawable to populate the Outline.
* <p>
- * This method will be called by a View on its background Drawable after
- * bounds change, if the View's Outline isn't set explicitly. This allows
- * the background Drawable to provide the shape of the shadow casting
- * portion of the View. It can also serve to clip the area of the View if
- * if {@link View#setClipToOutline(boolean)} is set on the View.
- * <p>
- * The Outline queried by the View will not be modified, and is treated as
- * a static shape that only needs to be requeried when the drawable's bounds
- * change.
+ * This method will be called by a View on its background Drawable after bounds change, or its
+ * Drawable is invalidated, if the View's Outline isn't set explicitly. This allows the
+ * background Drawable to define the shape of the shadow cast by the View.
*
- * @see View#setOutline(android.view.Outline)
- * @see View#setClipToOutline(boolean)
+ * The default behavior defines the outline to be the bounding rectangle. Subclasses that wish
+ * to convey a different shape must override this method.
+ *
+ * @see View#setOutline(android.graphics.Outline)
*/
- public Outline getOutline() { return null; }
+ public boolean getOutline(Outline outline) {
+ outline.setRect(getBounds());
+ return true;
+ }
/**
* Make this drawable mutable. This operation cannot be reversed. A mutable
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 1b5cefe..708c8b0 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -139,8 +139,7 @@
private final Path mPath = new Path();
private final RectF mRect = new RectF();
- private Outline mOutline;
-
+
private Paint mLayerPaint; // internal, used if we use saveLayer()
private boolean mRectIsDirty; // internal state
private boolean mMutated;
@@ -573,15 +572,11 @@
mStrokePaint.setColorFilter(mColorFilter);
}
}
-
+
switch (st.mShape) {
case RECTANGLE:
if (st.mRadiusArray != null) {
- if (mPathIsDirty || mRectIsDirty) {
- mPath.reset();
- mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
- mPathIsDirty = mRectIsDirty = false;
- }
+ buildPathIfDirty();
canvas.drawPath(mPath, mFillPaint);
if (haveStroke) {
canvas.drawPath(mPath, mStrokePaint);
@@ -638,7 +633,16 @@
}
}
}
-
+
+ private void buildPathIfDirty() {
+ final GradientState st = mGradientState;
+ if (mPathIsDirty || mRectIsDirty) {
+ mPath.reset();
+ mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
+ mPathIsDirty = mRectIsDirty = false;
+ }
+ }
+
private Path buildRing(GradientState st) {
if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
mPathIsDirty = false;
@@ -1428,42 +1432,39 @@
}
@Override
- public Outline getOutline() {
+ public boolean getOutline(Outline outline) {
final GradientState st = mGradientState;
final Rect bounds = getBounds();
switch (st.mShape) {
case RECTANGLE:
if (st.mRadiusArray != null) {
- return null;
+ buildPathIfDirty();
+ outline.setConvexPath(mPath);
+ return true;
}
+
float rad = 0;
if (st.mRadius > 0.0f) {
// clamp the radius based on width & height, matching behavior in draw()
rad = Math.min(st.mRadius,
Math.min(bounds.width(), bounds.height()) * 0.5f);
}
- if (mOutline == null) {
- mOutline = new Outline();
- }
- mOutline.setRoundRect(bounds.left, bounds.top,
+ outline.setRoundRect(bounds.left, bounds.top,
bounds.right, bounds.bottom, rad);
- return mOutline;
+ return true;
case LINE: {
float halfStrokeWidth = mStrokePaint.getStrokeWidth() * 0.5f;
float centerY = bounds.centerY();
int top = (int) Math.floor(centerY - halfStrokeWidth);
int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
- if (mOutline == null) {
- mOutline = new Outline();
- }
- mOutline.setRoundRect(bounds.left, top, bounds.right, bottom, 0);
- return mOutline;
+ outline.setRect(bounds.left, top, bounds.right, bottom);
+ return true;
}
default:
// TODO: investigate
- return null;
+ return false;
}
}
diff --git a/media/java/android/media/DngCreator.java b/media/java/android/media/DngCreator.java
new file mode 100644
index 0000000..b2a38ab
--- /dev/null
+++ b/media/java/android/media/DngCreator.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 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;
+
+import android.graphics.Bitmap;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureResult;
+import android.location.Location;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file.
+ *
+ * <p>
+ * This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR}
+ * buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw
+ * pixel data that is otherwise generated by an application. The DNG metadata tags will be
+ * generated from a {@link android.hardware.camera2.CaptureResult} object or set directly.
+ * </p>
+ *
+ * <p>
+ * The DNG file format is a cross-platform file format that is used to store pixel data from
+ * camera sensors with minimal pre-processing applied. DNG files allow for pixel data to be
+ * defined in a user-defined colorspace, and have associated metadata that allow for this
+ * pixel data to be converted to the standard CIE XYZ colorspace during post-processing.
+ * </p>
+ *
+ * <p>
+ * For more information on the DNG file format and associated metadata, please refer to the
+ * <a href=
+ * "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf">
+ * Adobe DNG 1.4.0.0 specification</a>.
+ * </p>
+ */
+public final class DngCreator {
+
+ /**
+ * Create a new DNG object.
+ *
+ * <p>
+ * It is not necessary to call any set methods to write a well-formatted DNG file.
+ * </p>
+ * <p>
+ * DNG metadata tags will be generated from the corresponding parameters in the
+ * {@link android.hardware.camera2.CaptureResult} object. This removes or overrides
+ * all previous tags set.
+ * </p>
+ *
+ * @param characteristics an object containing the static
+ * {@link android.hardware.camera2.CameraCharacteristics}.
+ * @param metadata a metadata object to generate tags from.
+ */
+ public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {/*TODO*/}
+
+ /**
+ * Set the orientation value to write.
+ *
+ * <p>
+ * This will be written as the TIFF "Orientation" tag {@code (0x0112)}.
+ * Calling this will override any prior settings for this tag.
+ * </p>
+ *
+ * @param orientation the orientation value to set, one of:
+ * <ul>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
+ * </ul>
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setOrientation(int orientation) {
+ return this;
+ }
+
+ /**
+ * Set the thumbnail image.
+ *
+ * <p>
+ * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
+ * The alpha channel will be discarded.
+ * </p>
+ *
+ * <p>
+ * The given bitmap should not be altered while this object is in use.
+ * </p>
+ *
+ * @param pixels a {@link android.graphics.Bitmap} of pixel data.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setThumbnail(Bitmap pixels) {
+ return this;
+ }
+
+ /**
+ * Set the thumbnail image.
+ *
+ * <p>
+ * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
+ * </p>
+ *
+ * <p>
+ * The given image should not be altered while this object is in use.
+ * </p>
+ *
+ * @param pixels an {@link android.media.Image} object with the format
+ * {@link android.graphics.ImageFormat#YUV_420_888}.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setThumbnail(Image pixels) {
+ return this;
+ }
+
+
+ /**
+ * Set image location metadata.
+ *
+ * <p>
+ * The given location object must contain at least a valid time, latitude, and longitude
+ * (equivalent to the values returned by {@link android.location.Location#getTime()},
+ * {@link android.location.Location#getLatitude()}, and
+ * {@link android.location.Location#getLongitude()} methods).
+ * </p>
+ *
+ * @param location an {@link android.location.Location} object to set.
+ * @return this {@link #DngCreator} object.
+ *
+ * @throws java.lang.IllegalArgumentException if the given location object doesn't
+ * contain enough information to set location metadata.
+ */
+ public DngCreator setLocation(Location location) { return this; }
+
+ /**
+ * Set the user description string to write.
+ *
+ * <p>
+ * This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}.
+ * </p>
+ *
+ * @param description the user description string.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setDescription(String description) {
+ return this;
+ }
+
+ /**
+ * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
+ * the currently configured metadata.
+ *
+ * <p>
+ * Raw pixel data must have 16 bits per pixel, and the input must contain at least
+ * {@code offset + 2 * (stride * (height - 1) + width * height)} bytes. The width and height of
+ * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
+ * and will typically be equal to the width and height of
+ * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
+ * If insufficient metadata is set to write a well-formatted DNG file, and
+ * {@link java.lang.IllegalStateException} will be thrown.
+ * </p>
+ *
+ * <p>
+ * When reading from the pixel input, {@code stride} pixels will be skipped
+ * after each row (excluding the last).
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param pixels an {@link java.io.InputStream} of pixel data to write.
+ * @param stride the stride of the raw image in pixels.
+ * @param offset the offset of the raw image in bytes. This indicates how many bytes will
+ * be skipped in the input before any pixel data is read.
+ *
+ * @throws IOException if an error was encountered in the input or output stream.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ */
+ public void writeInputStream(OutputStream dngOutput, InputStream pixels, int stride,
+ long offset) throws IOException {
+ /*TODO*/
+ }
+
+ /**
+ * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
+ * the currently configured metadata.
+ *
+ * <p>
+ * Raw pixel data must have 16 bits per pixel, and the input must contain at least
+ * {@code offset + 2 * (stride * (height - 1) + width * height)} bytes. The width and height of
+ * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
+ * and will typically be equal to the width and height of
+ * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
+ * If insufficient metadata is set to write a well-formatted DNG file, and
+ * {@link java.lang.IllegalStateException} will be thrown.
+ * </p>
+ *
+ * <p>
+ * When reading from the pixel input, {@code stride} pixels will be skipped
+ * after each row (excluding the last).
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
+ * @param stride the stride of the raw image in pixels.
+ * @param offset the offset of the raw image in bytes. This indicates how many bytes will
+ * be skipped in the input before any pixel data is read.
+ *
+ * @throws IOException if an error was encountered in the input or output stream.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ */
+ public void writeByteBuffer(OutputStream dngOutput, ByteBuffer pixels, int stride,
+ long offset) throws IOException {/*TODO*/}
+
+ /**
+ * Write the pixel data to a DNG file with the currently configured metadata.
+ *
+ * <p>
+ * For this method to succeed, the {@link android.media.Image} input must contain
+ * {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an
+ * {@link java.lang.IllegalArgumentException} will be thrown.
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param pixels an {@link android.media.Image} to write.
+ *
+ * @throws java.io.IOException if an error was encountered in the output stream.
+ * @throws java.lang.IllegalArgumentException if an image with an unsupported format was used.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ */
+ public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {/*TODO*/}
+
+}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 72f3e1a..6424a98 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -1281,6 +1281,14 @@
mMediaProvider = null;
}
+ private void releaseResources() {
+ // release the DrmManagerClient resources
+ if (mDrmManagerClient != null) {
+ mDrmManagerClient.release();
+ mDrmManagerClient = null;
+ }
+ }
+
private void initialize(String volumeName) {
mMediaProvider = mContext.getContentResolver().acquireProvider("media");
@@ -1341,6 +1349,8 @@
Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
+ } finally {
+ releaseResources();
}
}
@@ -1364,6 +1374,8 @@
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
return null;
+ } finally {
+ releaseResources();
}
}
@@ -1511,6 +1523,7 @@
if (fileList != null) {
fileList.close();
}
+ releaseResources();
}
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
index 2e4dbdf..ede23ef 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
@@ -27,11 +27,10 @@
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.MutableInt;
import android.view.View;
import android.widget.TextView;
-import libcore.util.MutableInt;
-
import java.lang.ref.WeakReference;
import com.android.internal.widget.LockPatternUtils;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e1c17cb..56f5a3a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -88,7 +88,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
- <uses-permission android:name="android.permission.RENDER_STATS" />
+ <uses-permission android:name="android.permission.FRAME_STATS" />
<application android:label="@string/app_label">
<provider
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 21c2926..ae18aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -50,7 +50,8 @@
@Override
public void start() {
- mUseAlternateRecents = SystemProperties.getBoolean("persist.recents.use_alternate", false);
+ Configuration config = mContext.getResources().getConfiguration();
+ mUseAlternateRecents = (config.smallestScreenWidthDp < 600);
if (mUseAlternateRecents) {
if (mAlternateRecents == null) {
mAlternateRecents = new AlternateRecentsComponent(mContext);
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 f26fa79..a274de6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3002,14 +3002,22 @@
}
private void instantExpandNotificationsPanel() {
+
+ // Make our window larger.
+ mStatusBarWindowManager.setStatusBarExpanded(true);
+
+ // 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() {
- mNotificationPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- mNotificationPanel.setExpandedFraction(1);
- }
- });
+ @Override
+ public void onGlobalLayout() {
+ if (mStatusBarWindow.getHeight() != getStatusBarHeight()) {
+ mNotificationPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mNotificationPanel.setExpandedFraction(1);
+ }
+ }
+ });
}
private void instantCollapseNotificationPanel() {
diff --git a/services/Android.mk b/services/Android.mk
index 165f456..5fcef64 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -25,7 +25,8 @@
backup \
devicepolicy \
print \
- usb
+ usb \
+ voiceinteraction
# The convention is to name each service module 'services.$(module_name)'
LOCAL_STATIC_JAVA_LIBRARIES := $(addprefix services.,$(services))
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 2f56e62..87b1d32 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -50,6 +50,7 @@
import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.AttributeSet;
+import android.util.MutableInt;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -86,8 +87,6 @@
import java.util.Map.Entry;
import java.util.Set;
-import libcore.util.MutableInt;
-
class AppWidgetServiceImpl {
private static final String KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ed007e9..75bf5df 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -28,17 +28,20 @@
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+import android.Manifest;
import android.app.AppOpsManager;
import android.app.IActivityContainer;
import android.app.IActivityContainerCallback;
import android.appwidget.AppWidgetManager;
import android.graphics.Rect;
import android.os.BatteryStats;
+import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.ProcessStats;
import com.android.internal.os.BackgroundThread;
@@ -56,6 +59,7 @@
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.firewall.IntentFirewall;
@@ -333,6 +337,9 @@
// How many bytes to write into the dropbox log before truncating
static final int DROPBOX_MAX_SIZE = 256 * 1024;
+ /** All system services */
+ SystemServiceManager mSystemServiceManager;
+
/** Run all ActivityStacks through this */
ActivityStackSupervisor mStackSupervisor;
@@ -399,7 +406,7 @@
/**
* List of intents that were used to start the most recent tasks.
*/
- private final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
+ final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
@@ -879,17 +886,23 @@
* Set while we are wanting to sleep, to prevent any
* activities from being started/resumed.
*/
- boolean mSleeping = false;
+ private boolean mSleeping = false;
+
+ /**
+ * Set while we are running a voice interaction. This overrides
+ * sleeping while it is active.
+ */
+ private boolean mRunningVoice = false;
/**
* State of external calls telling us if the device is asleep.
*/
- boolean mWentToSleep = false;
+ private boolean mWentToSleep = false;
/**
* State of external call telling us if the lock screen is shown.
*/
- boolean mLockScreenShown = false;
+ private boolean mLockScreenShown = false;
/**
* Set if we are shutting down the system, similar to sleeping.
@@ -1117,6 +1130,8 @@
static final int REQUEST_ALL_PSS_MSG = 39;
static final int START_PROFILES_MSG = 40;
static final int UPDATE_TIME = 41;
+ static final int SYSTEM_USER_START_MSG = 42;
+ static final int SYSTEM_USER_CURRENT_MSG = 43;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1696,15 +1711,15 @@
break;
}
case REPORT_USER_SWITCH_MSG: {
- dispatchUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ dispatchUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case CONTINUE_USER_SWITCH_MSG: {
- continueUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ continueUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case USER_SWITCH_TIMEOUT_MSG: {
- timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ timeoutUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case IMMERSIVE_MODE_LOCK_MSG: {
@@ -1751,6 +1766,14 @@
}
break;
}
+ case SYSTEM_USER_START_MSG: {
+ mSystemServiceManager.startUser(msg.arg1);
+ break;
+ }
+ case SYSTEM_USER_CURRENT_MSG: {
+ mSystemServiceManager.switchUser(msg.arg1);
+ break;
+ }
}
}
};
@@ -2059,6 +2082,10 @@
Watchdog.getInstance().addThread(mHandler);
}
+ public void setSystemServiceManager(SystemServiceManager mgr) {
+ mSystemServiceManager = mgr;
+ }
+
private void start() {
mProcessCpuThread.start();
@@ -2254,6 +2281,11 @@
if (mFocusedActivity != r) {
if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r);
mFocusedActivity = r;
+ if (r.task != null && r.task.voiceInteractor != null) {
+ startRunningVoiceLocked();
+ } else {
+ finishRunningVoiceLocked();
+ }
mStackSupervisor.setFocusedStack(r);
if (r != null) {
mWindowManager.setFocusedApp(r.appToken, true);
@@ -2262,6 +2294,12 @@
}
}
+ final void clearFocusedActivity(ActivityRecord r) {
+ if (mFocusedActivity == r) {
+ mFocusedActivity = null;
+ }
+ }
+
@Override
public void setFocusedStack(int stackId) {
if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: stackId=" + stackId);
@@ -2990,7 +3028,7 @@
intent.setComponent(new ComponentName(
ri.activityInfo.packageName, ri.activityInfo.name));
mStackSupervisor.startActivityLocked(null, intent, null, ri.activityInfo,
- null, null, 0, 0, 0, null, 0, null, false, null, null);
+ null, null, null, null, 0, 0, 0, null, 0, null, false, null, null);
}
}
}
@@ -3121,7 +3159,7 @@
}
for (int i=0; i<N; i++) {
PendingActivityLaunch pal = mPendingActivityLaunches.get(i);
- mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.startFlags,
+ mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
doResume && i == (N-1), null);
}
mPendingActivityLaunches.clear();
@@ -3147,7 +3185,7 @@
false, true, "startActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+ null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
null, null, options, userId, null);
}
@@ -3162,7 +3200,7 @@
WaitResult res = new WaitResult();
// TODO: Switch to user app stacks here.
mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+ null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
res, null, options, UserHandle.getCallingUserId(), null);
return res;
}
@@ -3177,7 +3215,7 @@
false, true, "startActivityWithConfig", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
- resolvedType, resultTo, resultWho, requestCode, startFlags,
+ resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
null, null, null, config, options, userId, null);
return ret;
}
@@ -3215,6 +3253,31 @@
}
@Override
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) {
+ if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: startVoiceActivity() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (session == null || interactor == null) {
+ throw new NullPointerException("null session or interactor");
+ }
+ userId = handleIncomingUser(callingPid, callingUid, userId,
+ false, true, "startVoiceActivity", null);
+ // TODO: Switch to user app stacks here.
+ return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
+ resolvedType, session, interactor, null, null, 0, startFlags,
+ profileFile, profileFd, null, null, options, userId, null);
+ }
+
+ @Override
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) {
// Refuse possible leaked file descriptors
@@ -3307,7 +3370,7 @@
final long origId = Binder.clearCallingIdentity();
int res = mStackSupervisor.startActivityLocked(r.app.thread, intent,
- r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null,
+ r.resolvedType, aInfo, null, null, resultTo != null ? resultTo.appToken : null,
resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0,
options, false, null, null);
Binder.restoreCallingIdentity(origId);
@@ -3330,7 +3393,7 @@
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags,
+ null, null, resultTo, resultWho, requestCode, startFlags,
null, null, null, null, options, userId, container);
return ret;
}
@@ -3366,6 +3429,10 @@
if (N > 0 && mRecentTasks.get(0) == task) {
return;
}
+ // Another quick case: never add voice sessions.
+ if (task.voiceSession != null) {
+ return;
+ }
// Remove any existing entries that are the same kind of task.
final Intent intent = task.intent;
final boolean document = intent != null && intent.isDocument();
@@ -8509,11 +8576,27 @@
return mSleeping || mShuttingDown;
}
+ public boolean isSleeping() {
+ return mSleeping;
+ }
+
void goingToSleep() {
synchronized(this) {
mWentToSleep = true;
updateEventDispatchingLocked();
+ goToSleepIfNeededLocked();
+ }
+ }
+ void finishRunningVoiceLocked() {
+ if (mRunningVoice) {
+ mRunningVoice = false;
+ goToSleepIfNeededLocked();
+ }
+ }
+
+ void goToSleepIfNeededLocked() {
+ if (mWentToSleep && !mRunningVoice) {
if (!mSleeping) {
mSleeping = true;
mStackSupervisor.goingToSleepLocked();
@@ -8576,7 +8659,7 @@
}
private void comeOutOfSleepIfNeededLocked() {
- if (!mWentToSleep && !mLockScreenShown) {
+ if ((!mWentToSleep && !mLockScreenShown) || mRunningVoice) {
if (mSleeping) {
mSleeping = false;
mStackSupervisor.comeOutOfSleepIfNeededLocked();
@@ -8592,6 +8675,13 @@
}
}
+ void startRunningVoiceLocked() {
+ if (!mRunningVoice) {
+ mRunningVoice = true;
+ comeOutOfSleepIfNeededLocked();
+ }
+ }
+
private void updateEventDispatchingLocked() {
mWindowManager.setEventDispatching(mBooted && !mWentToSleep && !mShuttingDown);
}
@@ -9298,7 +9388,7 @@
proc.notCachedSinceIdle = true;
proc.initialIdlePss = 0;
proc.nextPssTime = ProcessList.computeNextPssTime(proc.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
}
}
@@ -9597,6 +9687,8 @@
if (goingCallback != null) goingCallback.run();
+ mSystemServiceManager.startUser(mCurrentUserId);
+
synchronized (this) {
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
try {
@@ -9653,6 +9745,8 @@
}, 0, null, null,
android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+ } catch (Throwable t) {
+ Slog.wtf(TAG, "Failed sending first user broadcasts", t);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -11152,8 +11246,8 @@
pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep
+ " mLockScreenShown " + mLockScreenShown);
}
- if (mShuttingDown) {
- pw.println(" mShuttingDown=" + mShuttingDown);
+ if (mShuttingDown || mRunningVoice) {
+ pw.print(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice);
}
}
if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
@@ -15273,7 +15367,7 @@
if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
app.pssProcState = app.setProcState;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
mPendingPssProcesses.add(app);
}
}
@@ -15310,7 +15404,7 @@
}
}
return !processingBroadcasts
- && (mSleeping || mStackSupervisor.allResumedActivitiesIdle());
+ && (isSleeping() || mStackSupervisor.allResumedActivitiesIdle());
}
/**
@@ -15585,7 +15679,7 @@
app.setProcState)) {
app.lastStateTime = now;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
if (DEBUG_PSS) Slog.d(TAG, "Process state change from "
+ ProcessList.makeProcStateString(app.setProcState) + " to "
+ ProcessList.makeProcStateString(app.curProcState) + " next pss in "
@@ -15595,7 +15689,7 @@
&& now > (app.lastStateTime+ProcessList.PSS_MIN_TIME_FROM_STATE_CHANGE))) {
requestPssLocked(app, app.setProcState);
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false,
- mSleeping, now);
+ isSleeping(), now);
} else if (false && DEBUG_PSS) {
Slog.d(TAG, "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now));
}
@@ -15932,7 +16026,7 @@
}
mLastMemoryLevel = memFactor;
mLastNumProcesses = mLruProcesses.size();
- boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !mSleeping, now);
+ boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleeping(), now);
final int trackerMemFactor = mProcessStats.getMemFactorLocked();
if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {
if (mLowRamStartTime == 0) {
@@ -16475,7 +16569,15 @@
needStart = true;
}
+ if (uss.mState == UserStartedState.STATE_BOOTING) {
+ // Booting up a new user, need to tell system services about it.
+ // Note that this is on the same handler as scheduling of broadcasts,
+ // which is important because it needs to go first.
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId));
+ }
+
if (foreground) {
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId));
mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
@@ -16830,6 +16932,7 @@
}
uss.mState = UserStartedState.STATE_SHUTDOWN;
}
+ mSystemServiceManager.stopUser(userId);
broadcastIntentLocked(null, null, shutdownIntent,
null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, userId);
@@ -16879,7 +16982,12 @@
}
}
- mStackSupervisor.removeUserLocked(userId);
+ if (stopped) {
+ mSystemServiceManager.cleanupUser(userId);
+ synchronized (this) {
+ mStackSupervisor.removeUserLocked(userId);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 3e59def..7a44473 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -633,7 +633,7 @@
// case we will deliver it if this is the current top activity on its
// stack.
boolean unsent = true;
- if ((state == ActivityState.RESUMED || (service.mSleeping
+ if ((state == ActivityState.RESUMED || (service.isSleeping()
&& task.stack.topRunningActivityLocked(null) == this))
&& app != null && app.thread != null) {
try {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9f57728..bea926f 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -38,6 +38,8 @@
import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.CONTAINER_STATE_HAS_SURFACE;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ItemMatcher;
@@ -501,6 +503,11 @@
if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
+ if (task.voiceSession != null) {
+ // We never match voice sessions; those always run independently.
+ if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": voice session");
+ continue;
+ }
if (task.userId != userId) {
// Looking for a different task.
if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user");
@@ -735,7 +742,9 @@
int w = mThumbnailWidth;
int h = mThumbnailHeight;
if (w < 0) {
- if (SystemProperties.getBoolean("persist.recents.use_alternate", false)) {
+ Configuration config = res.getConfiguration();
+ boolean useAlternateRecents = (config.smallestScreenWidthDp < 600);
+ if (useAlternateRecents) {
mThumbnailWidth = w =
res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_width);
mThumbnailHeight = h =
@@ -1501,7 +1510,7 @@
// If the most recent activity was noHistory but was only stopped rather
// than stopped+finished because the device went to sleep, we need to make
// sure to finish it as we're making a new activity topmost.
- if (mService.mSleeping && mLastNoHistoryActivity != null &&
+ if (mService.isSleeping() && mLastNoHistoryActivity != null &&
!mLastNoHistoryActivity.finishing) {
if (DEBUG_STATES) Slog.d(TAG, "no-history finish of " + mLastNoHistoryActivity +
" on new resume");
@@ -2032,7 +2041,7 @@
+ " out to bottom task " + bottom.task);
} else {
targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
- null, false);
+ null, null, null, false);
newThumbHolder = targetTask;
targetTask.affinityIntent = target.intent;
if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
@@ -2329,7 +2338,7 @@
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
|| (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {
if (!r.finishing) {
- if (!mService.mSleeping) {
+ if (!mService.isSleeping()) {
if (DEBUG_STATES) {
Slog.d(TAG, "no-history finish of " + r);
}
@@ -2708,7 +2717,7 @@
ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
destIntent.getComponent(), 0, srec.userId);
int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent,
- null, aInfo, parent.appToken, null,
+ null, aInfo, null, null, parent.appToken, null,
0, -1, parent.launchedFromUid, parent.launchedFromPackage,
0, null, true, null, null);
foundParentInTask = res == ActivityManager.START_SUCCESS;
@@ -2737,9 +2746,7 @@
if (mPausingActivity == r) {
mPausingActivity = null;
}
- if (mService.mFocusedActivity == r) {
- mService.mFocusedActivity = null;
- }
+ mService.clearFocusedActivity(r);
r.configDestroy = false;
r.frozenBeforeDestroy = false;
@@ -3721,6 +3728,11 @@
mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true;
}
mTaskHistory.remove(task);
+ if (task.voiceInteractor != null) {
+ // This task was a voice interaction, so it should not remain on the
+ // recent tasks list.
+ mService.mRecentTasks.remove(task);
+ }
if (mTaskHistory.isEmpty()) {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this);
@@ -3734,8 +3746,10 @@
}
}
- TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, boolean toTop) {
- TaskRecord task = new TaskRecord(taskId, info, intent);
+ TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ boolean toTop) {
+ TaskRecord task = new TaskRecord(taskId, info, intent, voiceSession, voiceInteractor);
addTask(task, toTop);
return task;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e044d3f..274e011 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -17,7 +17,6 @@
package com.android.server.am;
import static android.Manifest.permission.START_ANY_ACTIVITY;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -77,6 +76,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -87,6 +87,7 @@
import android.view.InputEvent;
import android.view.Surface;
import com.android.internal.app.HeavyWeightSwitcherActivity;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.TransferPipe;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
@@ -687,13 +688,14 @@
void startHomeActivity(Intent intent, ActivityInfo aInfo) {
moveHomeToTop();
- startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0,
+ startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0,
null, false, null, null);
}
final int startActivityMayWait(IApplicationThread caller, int callingUid,
- String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, int startFlags, String profileFile,
+ String callingPackage, Intent intent, String resolvedType,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
Bundle options, int userId, IActivityContainer iContainer) {
// Refuse possible leaked file descriptors
@@ -802,7 +804,8 @@
}
}
- int res = startActivityLocked(caller, intent, resolvedType, aInfo, resultTo, resultWho,
+ int res = startActivityLocked(caller, intent, resolvedType, aInfo,
+ voiceSession, voiceInteractor, resultTo, resultWho,
requestCode, callingPid, callingUid, callingPackage, startFlags, options,
componentSpecified, null, container);
@@ -918,7 +921,7 @@
theseOptions = null;
}
int res = startActivityLocked(caller, intent, resolvedTypes[i],
- aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage,
+ aInfo, null, null, resultTo, null, -1, callingPid, callingUid, callingPackage,
0, theseOptions, componentSpecified, outActivity, null);
if (res < 0) {
return res;
@@ -1034,8 +1037,8 @@
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
new Configuration(mService.mConfiguration), r.compat,
- app.repProcState, r.icicle, results, newIntents, !andResume,
- mService.isNextTransitionForward(), profileFile, profileFd,
+ r.task.voiceInteractor, app.repProcState, r.icicle, results, newIntents,
+ !andResume, mService.isNextTransitionForward(), profileFile, profileFd,
profileAutoStop, options);
if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
@@ -1143,8 +1146,9 @@
}
final int startActivityLocked(IApplicationThread caller,
- Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo,
- String resultWho, int requestCode,
+ Intent intent, String resolvedType, ActivityInfo aInfo,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ IBinder resultTo, String resultWho, int requestCode,
int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options,
boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container) {
int err = ActivityManager.START_SUCCESS;
@@ -1187,7 +1191,7 @@
}
ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
- int launchFlags = intent.getFlags();
+ final int launchFlags = intent.getFlags();
if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0
&& sourceRecord != null) {
@@ -1232,6 +1236,38 @@
err = ActivityManager.START_CLASS_NOT_FOUND;
}
+ if (err == ActivityManager.START_SUCCESS && sourceRecord != null
+ && sourceRecord.task.voiceSession != null) {
+ // If this activity is being launched as part of a voice session, we need
+ // to ensure that it is safe to do so. If the upcoming activity will also
+ // be part of the voice session, we can only launch it if it has explicitly
+ // said it supports the VOICE category, or it is a part of the calling app.
+ if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
+ && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
+ try {
+ if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+ intent, resolvedType)) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ } catch (RemoteException e) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ }
+ }
+
+ if (err == ActivityManager.START_SUCCESS && voiceSession != null) {
+ // If the caller is starting a new voice session, just make sure the target
+ // is actually allowing it to run this way.
+ try {
+ if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+ intent, resolvedType)) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ } catch (RemoteException e) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ }
+
if (err != ActivityManager.START_SUCCESS) {
if (resultRecord != null) {
resultStack.sendActivityResultLocked(-1,
@@ -1305,8 +1341,8 @@
}
final ActivityStack stack = getFocusedStack();
- if (stack.mResumedActivity == null
- || stack.mResumedActivity.info.applicationInfo.uid != callingUid) {
+ if (voiceSession == null && (stack.mResumedActivity == null
+ || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) {
PendingActivityLaunch pal =
new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
@@ -1330,7 +1366,8 @@
mService.doPendingActivityLaunchesLocked(false);
- err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options);
+ err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
+ startFlags, true, options);
if (allPausedActivitiesComplete()) {
// If someone asked to have the keyguard dismissed on the next
@@ -1410,8 +1447,9 @@
}
final int startActivityUncheckedLocked(ActivityRecord r,
- ActivityRecord sourceRecord, int startFlags, boolean doResume,
- Bundle options) {
+ ActivityRecord sourceRecord,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
+ boolean doResume, Bundle options) {
final Intent intent = r.intent;
final int callingUid = r.launchedFromUid;
@@ -1755,7 +1793,7 @@
r.setTask(targetStack.createTaskRecord(getNextTaskId(),
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
- true), null, true);
+ voiceSession, voiceInteractor, true), null, true);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
} else {
@@ -1833,7 +1871,7 @@
targetStack.moveToFront();
ActivityRecord prev = targetStack.topActivity();
r.setTask(prev != null ? prev.task
- : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),
+ : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, null, null, true),
null, true);
mWindowManager.moveTaskToTop(r.task.taskId);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
@@ -3104,7 +3142,7 @@
&& "content".equals(intent.getData().getScheme())) {
mimeType = mService.getProviderMimeType(intent.getData(), userId);
}
- return startActivityMayWait(null, -1, null, intent, mimeType, null, null, 0, 0, null,
+ return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, 0, 0, null,
null, null, null, null, userId, this);
}
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index 443218c..d42d415 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -17,14 +17,13 @@
package com.android.server.am;
import android.app.ApplicationErrorReport.CrashInfo;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.system.StructUcred;
import android.util.Slog;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-import libcore.io.StructTimeval;
-import libcore.io.StructUcred;
-
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -77,7 +76,7 @@
try {
CrashInfo ci = new CrashInfo();
ci.exceptionClassName = "Native crash";
- ci.exceptionMessage = Libcore.os.strsignal(mSignal);
+ ci.exceptionMessage = Os.strsignal(mSignal);
ci.throwFileName = "unknown";
ci.throwClassName = "unknown";
ci.throwMethodName = "unknown";
@@ -117,22 +116,22 @@
}
try {
- FileDescriptor serverFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0);
+ FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH);
- Libcore.os.bind(serverFd, sockAddr, 0);
- Libcore.os.listen(serverFd, 1);
+ Os.bind(serverFd, sockAddr, 0);
+ Os.listen(serverFd, 1);
while (true) {
InetSocketAddress peer = new InetSocketAddress();
FileDescriptor peerFd = null;
try {
if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
- peerFd = Libcore.os.accept(serverFd, peer);
+ peerFd = Os.accept(serverFd, peer);
if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
if (peerFd != null) {
// Only the superuser is allowed to talk to us over this socket
StructUcred credentials =
- Libcore.os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
+ Os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
if (credentials.uid == 0) {
// the reporting thread may take responsibility for
// acking the debugger; make sure we play along.
@@ -146,7 +145,7 @@
// byte written is irrelevant.
if (peerFd != null) {
try {
- Libcore.os.write(peerFd, ackSignal, 0, 1);
+ Os.write(peerFd, ackSignal, 0, 1);
} catch (Exception e) {
/* we don't care about failures here */
if (MORE_DEBUG) {
@@ -154,7 +153,7 @@
}
}
try {
- Libcore.os.close(peerFd);
+ Os.close(peerFd);
} catch (ErrnoException e) {
if (MORE_DEBUG) {
Slog.d(TAG, "Exception closing socket: " + e.getMessage());
@@ -182,7 +181,7 @@
throws ErrnoException, InterruptedIOException {
int totalRead = 0;
while (numBytes > 0) {
- int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes);
+ int n = Os.read(fd, buffer, offset + totalRead, numBytes);
if (n <= 0) {
if (DEBUG) {
Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
@@ -203,8 +202,8 @@
try {
StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
- Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
- Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
+ Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
+ Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
// first, the pid and signal number
int headerBytes = readExactly(fd, buf, 0, 8);
@@ -238,7 +237,7 @@
int bytes;
do {
// get some data
- bytes = Libcore.os.read(fd, buf, 0, buf.length);
+ bytes = Os.read(fd, buf, 0, buf.length);
if (bytes > 0) {
if (MORE_DEBUG) {
String s = new String(buf, 0, bytes, "UTF-8");
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 80a219dd..68da54d 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -28,7 +28,9 @@
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
import android.util.Slog;
+import com.android.internal.app.IVoiceInteractor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -36,6 +38,8 @@
final class TaskRecord extends ThumbnailHolder {
final int taskId; // Unique identifier for this task.
final String affinity; // The affinity name for this task, or null.
+ final IVoiceInteractionSession voiceSession; // Voice interaction session driving task
+ final IVoiceInteractor voiceInteractor; // Associated interactor to provide to app
Intent intent; // The original intent that started the task.
Intent affinityIntent; // Intent of affinity-moved activity that started this task.
ComponentName origActivity; // The non-alias activity component of the intent.
@@ -64,9 +68,12 @@
* Display.DEFAULT_DISPLAY. */
boolean mOnTopOfHome = false;
- TaskRecord(int _taskId, ActivityInfo info, Intent _intent) {
+ TaskRecord(int _taskId, ActivityInfo info, Intent _intent,
+ IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
taskId = _taskId;
affinity = info.taskAffinity;
+ voiceSession = _voiceSession;
+ voiceInteractor = _voiceInteractor;
setIntent(_intent, info);
}
@@ -473,6 +480,12 @@
if (affinity != null) {
pw.print(prefix); pw.print("affinity="); pw.println(affinity);
}
+ if (voiceSession != null || voiceInteractor != null) {
+ pw.print(prefix); pw.print("VOICE: session=0x");
+ pw.print(Integer.toHexString(System.identityHashCode(voiceSession)));
+ pw.print(" interactor=0x");
+ pw.println(Integer.toHexString(System.identityHashCode(voiceInteractor)));
+ }
if (intent != null) {
StringBuilder sb = new StringBuilder(128);
sb.append(prefix); sb.append("intent={");
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
new file mode 100644
index 0000000..5f07108
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -0,0 +1,94 @@
+/*
+ * 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.hdmi;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
+ * and pass it to CEC HAL so that it sends message to other device. For incoming
+ * message it translates the message and delegates it to proper module.
+ *
+ * <p>It can be created only by {@link HdmiCecController#create}
+ *
+ * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
+ */
+class HdmiCecController {
+ private static final String TAG = "HdmiCecController";
+
+ // Handler instance to process synchronous I/O (mainly send) message.
+ private Handler mIoHandler;
+
+ // Handler instance to process various messages coming from other CEC
+ // device or issued by internal state change.
+ private Handler mMessageHandler;
+
+ // Stores the pointer to the native implementation of the service that
+ // interacts with HAL.
+ private long mNativePtr;
+
+ // Private constructor. Use HdmiCecController.create().
+ private HdmiCecController() {
+ }
+
+ /**
+ * A factory method to get {@link HdmiCecController}. If it fails to initialize
+ * inner device or has no device it will return {@code null}.
+ *
+ * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
+ *
+ * @param ioLooper a Looper instance to handle IO (mainly send message) operation.
+ * @param messageHandler a message handler that processes a message coming from other
+ * CEC compatible device or callback of internal state change.
+ * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
+ * returns {@code null}.
+ */
+ static HdmiCecController create(Looper ioLooper, Handler messageHandler) {
+ HdmiCecController handler = new HdmiCecController();
+ long nativePtr = nativeInit(handler);
+ if (nativePtr == 0L) {
+ handler = null;
+ return null;
+ }
+
+ handler.init(ioLooper, messageHandler, nativePtr);
+ return handler;
+ }
+
+ private void init(Looper ioLooper, Handler messageHandler, long nativePtr) {
+ mIoHandler = new Handler(ioLooper) {
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO: Call native sendMessage.
+ }
+ };
+
+ mMessageHandler = messageHandler;
+ mNativePtr = nativePtr;
+ }
+
+ /**
+ * Called by native when an HDMI-CEC message arrived.
+ */
+ private void handleMessage(int srcAddress, int dstAddres, int opcode, byte[] params) {
+ // TODO: Translate message and delegate it to main message handler.
+ }
+
+ private static native long nativeInit(HdmiCecController handler);
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
new file mode 100644
index 0000000..56c5b49
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -0,0 +1,70 @@
+/*
+ * 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.hdmi;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+/**
+ * Provides a service for sending and processing HDMI control messages,
+ * HDMI-CEC and MHL control command, and providing the information on both standard.
+ */
+public final class HdmiControlService extends SystemService {
+ private static final String TAG = "HdmiControlService";
+
+ // A thread to handle synchronous IO of CEC and MHL control service.
+ // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
+ // and sparse call it shares a thread to handle IO operations.
+ private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
+
+ // Main handler class to handle incoming message from each controller.
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO: Add handler for each message type.
+ }
+ };
+
+ @Nullable
+ private HdmiCecController mCecController;
+
+ @Nullable
+ private HdmiMhlController mMhlController;
+
+ public HdmiControlService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mCecController = HdmiCecController.create(mIoThread.getLooper(), mHandler);
+ if (mCecController == null) {
+ Slog.i(TAG, "Device does not support HDMI-CEC.");
+ }
+
+ mMhlController = HdmiMhlController.create(mIoThread.getLooper(), mHandler);
+ if (mMhlController == null) {
+ Slog.i(TAG, "Device does not support MHL-control.");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
new file mode 100644
index 0000000..a282270
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -0,0 +1,58 @@
+/**
+ * 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.notification;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.provider.Settings;
+import android.service.notification.IConditionProvider;
+import android.service.notification.ConditionProviderService;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+public class ConditionProviders extends ManagedServices {
+
+ public ConditionProviders(Context context, Handler handler,
+ Object mutex, UserProfiles userProfiles) {
+ super(context, handler, mutex, userProfiles);
+ }
+
+ @Override
+ protected Config getConfig() {
+ Config c = new Config();
+ c.caption = "condition provider";
+ c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
+ c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
+ c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
+ c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
+ c.clientLabel = R.string.condition_provider_service_binding_label;
+ return c;
+ }
+
+ @Override
+ protected IInterface asInterface(IBinder binder) {
+ return IConditionProvider.Stub.asInterface(binder);
+ }
+
+ @Override
+ protected void onServiceAdded(IInterface service) {
+ Slog.d(TAG, "onServiceAdded " + service);
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
new file mode 100644
index 0000000..81b28e8
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -0,0 +1,594 @@
+/**
+ * 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.notification;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Manages the lifecycle of application-provided services bound by system server.
+ *
+ * Services managed by this helper must have:
+ * - An associated system settings value with a list of enabled component names.
+ * - A well-known action for services to use in their intent-filter.
+ * - A system permission for services to require in order to ensure system has exclusive binding.
+ * - A settings page for user configuration of enabled services, and associated intent action.
+ * - A remote interface definition (aidl) provided by the service used for communication.
+ */
+abstract public class ManagedServices {
+ protected final String TAG = getClass().getSimpleName();
+ protected static final boolean DEBUG = true;
+
+ private static final String ENABLED_SERVICES_SEPARATOR = ":";
+
+ private final Context mContext;
+ private final Object mMutex;
+ private final UserProfiles mUserProfiles;
+ private final SettingsObserver mSettingsObserver;
+ private final Config mConfig;
+
+ // contains connections to all connected services, including app services
+ // and system services
+ protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();
+ // things that will be put into mServices as soon as they're ready
+ private final ArrayList<String> mServicesBinding = new ArrayList<String>();
+ // lists the component names of all enabled (and therefore connected)
+ // app services for current profiles.
+ private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles
+ = new ArraySet<ComponentName>();
+ // Just the packages from mEnabledServicesForCurrentProfiles
+ private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>();
+
+ public ManagedServices(Context context, Handler handler, Object mutex,
+ UserProfiles userProfiles) {
+ mContext = context;
+ mMutex = mutex;
+ mUserProfiles = userProfiles;
+ mConfig = getConfig();
+ mSettingsObserver = new SettingsObserver(handler);
+ }
+
+ abstract protected Config getConfig();
+
+ private String getCaption() {
+ return mConfig.caption;
+ }
+
+ abstract protected IInterface asInterface(IBinder binder);
+
+ abstract protected void onServiceAdded(IInterface service);
+
+ private ManagedServiceInfo newServiceInfo(IInterface service,
+ ComponentName component, int userid, boolean isSystem, ServiceConnection connection,
+ int targetSdkVersion) {
+ return new ManagedServiceInfo(service, component, userid, isSystem, connection,
+ targetSdkVersion);
+ }
+
+ public void onBootPhaseAppsCanStart() {
+ mSettingsObserver.observe();
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
+ + ") enabled for current profiles:");
+ for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+ pw.println(" " + cmpt);
+ }
+
+ pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
+ for (ManagedServiceInfo info : mServices) {
+ pw.println(" " + info.component
+ + " (user " + info.userid + "): " + info.service
+ + (info.isSystem?" SYSTEM":""));
+ }
+ }
+
+ public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
+ boolean anyServicesInvolved = false;
+ if (pkgList != null && (pkgList.length > 0)) {
+ for (String pkgName : pkgList) {
+ if (mEnabledServicesPackageNames.contains(pkgName)) {
+ anyServicesInvolved = true;
+ }
+ }
+ }
+
+ if (anyServicesInvolved) {
+ // if we're not replacing a package, clean up orphaned bits
+ if (!queryReplace) {
+ disableNonexistentServices();
+ }
+ // make sure we're still bound to any of our services who may have just upgraded
+ rebindServices();
+ }
+ }
+
+ public ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
+ checkNotNull(service);
+ final IBinder token = service.asBinder();
+ final int N = mServices.size();
+ for (int i=0; i<N; i++) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (info.service.asBinder() == token) return info;
+ }
+ throw new SecurityException("Disallowed call from unknown " + getCaption() + ": "
+ + service);
+ }
+
+ public void unregisterService(IInterface service, int userid) {
+ checkNotNull(service);
+ // no need to check permissions; if your service binder is in the list,
+ // that's proof that you had permission to add it in the first place
+ unregisterServiceImpl(service, userid);
+ }
+
+ public void registerService(IInterface service, ComponentName component, int userid) {
+ checkNotNull(service);
+ registerServiceImpl(service, component, userid);
+ }
+
+ /**
+ * Remove access for any services that no longer exist.
+ */
+ private void disableNonexistentServices() {
+ int[] userIds = mUserProfiles.getCurrentProfileIds();
+ final int N = userIds.length;
+ for (int i = 0 ; i < N; ++i) {
+ disableNonexistentServices(userIds[i]);
+ }
+ }
+
+ private void disableNonexistentServices(int userId) {
+ String flatIn = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ userId);
+ if (!TextUtils.isEmpty(flatIn)) {
+ if (DEBUG) Slog.v(TAG, "flat before: " + flatIn);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
+ new Intent(mConfig.serviceInterface),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ userId);
+
+ Set<ComponentName> installed = new ArraySet<ComponentName>();
+ for (int i = 0, count = installedServices.size(); i < count; i++) {
+ ResolveInfo resolveInfo = installedServices.get(i);
+ ServiceInfo info = resolveInfo.serviceInfo;
+
+ if (!mConfig.bindPermission.equals(info.permission)) {
+ Slog.w(TAG, "Skipping " + getCaption() + " service "
+ + info.packageName + "/" + info.name
+ + ": it does not require the permission "
+ + mConfig.bindPermission);
+ continue;
+ }
+ installed.add(new ComponentName(info.packageName, info.name));
+ }
+
+ String flatOut = "";
+ if (!installed.isEmpty()) {
+ String[] enabled = flatIn.split(ENABLED_SERVICES_SEPARATOR);
+ ArrayList<String> remaining = new ArrayList<String>(enabled.length);
+ for (int i = 0; i < enabled.length; i++) {
+ ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
+ if (installed.contains(enabledComponent)) {
+ remaining.add(enabled[i]);
+ }
+ }
+ flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining);
+ }
+ if (DEBUG) Slog.v(TAG, "flat after: " + flatOut);
+ if (!flatIn.equals(flatOut)) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ flatOut, userId);
+ }
+ }
+ }
+
+ /**
+ * Called whenever packages change, the user switches, or the secure setting
+ * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
+ */
+ private void rebindServices() {
+ if (DEBUG) Slog.d(TAG, "rebindServices");
+ final int[] userIds = mUserProfiles.getCurrentProfileIds();
+ final int nUserIds = userIds.length;
+
+ final SparseArray<String> flat = new SparseArray<String>();
+
+ for (int i = 0; i < nUserIds; ++i) {
+ flat.put(userIds[i], Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ userIds[i]));
+ }
+
+ ManagedServiceInfo[] toRemove = new ManagedServiceInfo[mServices.size()];
+ final SparseArray<ArrayList<ComponentName>> toAdd
+ = new SparseArray<ArrayList<ComponentName>>();
+
+ synchronized (mMutex) {
+ // unbind and remove all existing services
+ toRemove = mServices.toArray(toRemove);
+
+ final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>();
+ final ArraySet<String> newPackages = new ArraySet<String>();
+
+ for (int i = 0; i < nUserIds; ++i) {
+ final ArrayList<ComponentName> add = new ArrayList<ComponentName>();
+ toAdd.put(userIds[i], add);
+
+ // decode the list of components
+ String toDecode = flat.get(userIds[i]);
+ if (toDecode != null) {
+ String[] components = toDecode.split(ENABLED_SERVICES_SEPARATOR);
+ for (int j = 0; j < components.length; j++) {
+ final ComponentName component
+ = ComponentName.unflattenFromString(components[j]);
+ if (component != null) {
+ newEnabled.add(component);
+ add.add(component);
+ newPackages.add(component.getPackageName());
+ }
+ }
+
+ }
+ }
+ mEnabledServicesForCurrentProfiles = newEnabled;
+ mEnabledServicesPackageNames = newPackages;
+ }
+
+ for (ManagedServiceInfo info : toRemove) {
+ final ComponentName component = info.component;
+ final int oldUser = info.userid;
+ Slog.v(TAG, "disabling " + getCaption() + " for user "
+ + oldUser + ": " + component);
+ unregisterService(component, info.userid);
+ }
+
+ for (int i = 0; i < nUserIds; ++i) {
+ final ArrayList<ComponentName> add = toAdd.get(userIds[i]);
+ final int N = add.size();
+ for (int j = 0; j < N; j++) {
+ final ComponentName component = add.get(j);
+ Slog.v(TAG, "enabling " + getCaption() + " for user " + userIds[i] + ": "
+ + component);
+ registerService(component, userIds[i]);
+ }
+ }
+ }
+
+ /**
+ * Version of registerService that takes the name of a service component to bind to.
+ */
+ private void registerService(final ComponentName name, final int userid) {
+ if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
+
+ synchronized (mMutex) {
+ final String servicesBindingTag = name.toString() + "/" + userid;
+ if (mServicesBinding.contains(servicesBindingTag)) {
+ // stop registering this thing already! we're working on it
+ return;
+ }
+ mServicesBinding.add(servicesBindingTag);
+
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ // cut old connections
+ if (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": "
+ + info.service);
+ mServices.remove(i);
+ if (info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+ }
+
+ Intent intent = new Intent(mConfig.serviceInterface);
+ intent.setComponent(name);
+
+ intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(
+ mContext, 0, new Intent(mConfig.settingsAction), 0);
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mContext.getPackageManager().getApplicationInfo(
+ name.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ // Ignore if the package doesn't exist we won't be able to bind to the service.
+ }
+ final int targetSdkVersion =
+ appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
+
+ try {
+ if (DEBUG) Slog.v(TAG, "binding: " + intent);
+ if (!mContext.bindServiceAsUser(intent,
+ new ServiceConnection() {
+ IInterface mService;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ boolean added = false;
+ synchronized (mMutex) {
+ mServicesBinding.remove(servicesBindingTag);
+ try {
+ mService = asInterface(binder);
+ ManagedServiceInfo info = newServiceInfo(mService, name,
+ userid, false /*isSystem*/, this, targetSdkVersion);
+ binder.linkToDeath(info, 0);
+ added = mServices.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ if (added) {
+ onServiceAdded(mService);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Slog.v(TAG, getCaption() + " connection lost: " + name);
+ }
+ },
+ Context.BIND_AUTO_CREATE,
+ new UserHandle(userid)))
+ {
+ mServicesBinding.remove(servicesBindingTag);
+ Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
+ return;
+ }
+ } catch (SecurityException ex) {
+ Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Remove a service for the given user by ComponentName
+ */
+ private void unregisterService(ComponentName name, int userid) {
+ synchronized (mMutex) {
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ mServices.remove(i);
+ if (info.connection != null) {
+ try {
+ mContext.unbindService(info.connection);
+ } catch (IllegalArgumentException ex) {
+ // something happened to the service: we think we have a connection
+ // but it's bogus.
+ Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a service from the list but does not unbind
+ *
+ * @return the removed service.
+ */
+ private ManagedServiceInfo removeServiceImpl(IInterface service, final int userid) {
+ ManagedServiceInfo serviceInfo = null;
+ synchronized (mMutex) {
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (info.service.asBinder() == service.asBinder()
+ && info.userid == userid) {
+ serviceInfo = mServices.remove(i);
+ }
+ }
+ }
+ return serviceInfo;
+ }
+
+ private void checkNotNull(IInterface service) {
+ if (service == null) {
+ throw new IllegalArgumentException(getCaption() + " must not be null");
+ }
+ }
+
+ private void registerServiceImpl(final IInterface service,
+ final ComponentName component, final int userid) {
+ synchronized (mMutex) {
+ try {
+ ManagedServiceInfo info = newServiceInfo(service, component, userid,
+ true /*isSystem*/, null, Build.VERSION_CODES.L);
+ service.asBinder().linkToDeath(info, 0);
+ mServices.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ }
+
+ /**
+ * Removes a service from the list and unbinds.
+ */
+ private void unregisterServiceImpl(IInterface service, int userid) {
+ ManagedServiceInfo info = removeServiceImpl(service, userid);
+ if (info != null && info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName);
+
+ private SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ private void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(mSecureSettingsUri,
+ false, this, UserHandle.USER_ALL);
+ update(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update(uri);
+ }
+
+ private void update(Uri uri) {
+ if (uri == null || mSecureSettingsUri.equals(uri)) {
+ rebindServices();
+ }
+ }
+ }
+
+ public class ManagedServiceInfo implements IBinder.DeathRecipient {
+ public IInterface service;
+ public ComponentName component;
+ public int userid;
+ public boolean isSystem;
+ public ServiceConnection connection;
+ public int targetSdkVersion;
+
+ public ManagedServiceInfo(IInterface service, ComponentName component,
+ int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion) {
+ this.service = service;
+ this.component = component;
+ this.userid = userid;
+ this.isSystem = isSystem;
+ this.connection = connection;
+ this.targetSdkVersion = targetSdkVersion;
+ }
+
+ public boolean enabledAndUserMatches(int nid) {
+ if (!isEnabledForCurrentProfiles()) {
+ return false;
+ }
+ if (this.userid == UserHandle.USER_ALL) return true;
+ if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
+ return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
+ }
+
+ public boolean supportsProfiles() {
+ return targetSdkVersion >= Build.VERSION_CODES.L;
+ }
+
+ @Override
+ public void binderDied() {
+ // Remove the service, but don't unbind from the service. The system will bring the
+ // service back up, and the onServiceConnected handler will readd the service with the
+ // new binding. If this isn't a bound service, and is just a registered
+ // service, just removing it from the list is all we need to do anyway.
+ removeServiceImpl(this.service, this.userid);
+ }
+
+ /** convenience method for looking in mEnabledServicesForCurrentProfiles */
+ public boolean isEnabledForCurrentProfiles() {
+ if (this.isSystem) return true;
+ if (this.connection == null) return false;
+ return mEnabledServicesForCurrentProfiles.contains(this.component);
+ }
+ }
+
+ public static class UserProfiles {
+ // Profiles of the current user.
+ private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+
+ public void updateCache(Context context) {
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (userManager != null) {
+ int currentUserId = ActivityManager.getCurrentUser();
+ List<UserInfo> profiles = userManager.getProfiles(currentUserId);
+ synchronized (mCurrentProfiles) {
+ mCurrentProfiles.clear();
+ for (UserInfo user : profiles) {
+ mCurrentProfiles.put(user.id, user);
+ }
+ }
+ }
+ }
+
+ public int[] getCurrentProfileIds() {
+ synchronized (mCurrentProfiles) {
+ int[] users = new int[mCurrentProfiles.size()];
+ final int N = mCurrentProfiles.size();
+ for (int i = 0; i < N; ++i) {
+ users[i] = mCurrentProfiles.keyAt(i);
+ }
+ return users;
+ }
+ }
+
+ public boolean isCurrentProfile(int userId) {
+ synchronized (mCurrentProfiles) {
+ return mCurrentProfiles.get(userId) != null;
+ }
+ }
+ }
+
+ protected static class Config {
+ String caption;
+ String serviceInterface;
+ String secureSettingName;
+ String bindPermission;
+ String settingsAction;
+ int clientLabel;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationListeners.java b/services/core/java/com/android/server/notification/NotificationListeners.java
deleted file mode 100644
index 91d2f98..0000000
--- a/services/core/java/com/android/server/notification/NotificationListeners.java
+++ /dev/null
@@ -1,608 +0,0 @@
-/**
- * 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.notification;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.service.notification.INotificationListener;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.UserProfiles;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-public class NotificationListeners {
- private static final String TAG = "NotificationListeners";
- private static final boolean DBG = NotificationManagerService.DBG;
-
- private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
-
- private final Context mContext;
- private final Handler mHandler;
- private final Object mMutex;
- private final UserProfiles mUserProfiles;
- private final SettingsObserver mSettingsObserver;
-
- // contains connections to all connected listeners, including app services
- // and system listeners
- private final ArrayList<NotificationListenerInfo> mListeners
- = new ArrayList<NotificationListenerInfo>();
- // things that will be put into mListeners as soon as they're ready
- private final ArrayList<String> mServicesBinding = new ArrayList<String>();
- // lists the component names of all enabled (and therefore connected) listener
- // app services for current profiles.
- private ArraySet<ComponentName> mEnabledListenersForCurrentProfiles
- = new ArraySet<ComponentName>();
- // Just the packages from mEnabledListenersForCurrentProfiles
- private ArraySet<String> mEnabledListenerPackageNames = new ArraySet<String>();
-
- public NotificationListeners(Context context, Handler handler, Object mutex,
- UserProfiles userProfiles) {
- mContext = context;
- mHandler = handler;
- mMutex = mutex;
- mUserProfiles = userProfiles;
- mSettingsObserver = new SettingsObserver(mHandler);
- }
-
- public void onBootPhaseAppsCanStart() {
- mSettingsObserver.observe();
- }
-
- protected void onServiceAdded(INotificationListener mListener) {
- // for subclasses
- }
-
- public void dump(PrintWriter pw) {
- pw.println(" Listeners (" + mEnabledListenersForCurrentProfiles.size()
- + ") enabled for current profiles:");
- for (ComponentName cmpt : mEnabledListenersForCurrentProfiles) {
- pw.println(" " + cmpt);
- }
-
- pw.println(" Live listeners (" + mListeners.size() + "):");
- for (NotificationListenerInfo info : mListeners) {
- pw.println(" " + info.component
- + " (user " + info.userid + "): " + info.listener
- + (info.isSystem?" SYSTEM":""));
- }
- }
-
- public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
- boolean anyListenersInvolved = false;
- if (pkgList != null && (pkgList.length > 0)) {
- for (String pkgName : pkgList) {
- if (mEnabledListenerPackageNames.contains(pkgName)) {
- anyListenersInvolved = true;
- }
- }
- }
-
- if (anyListenersInvolved) {
- // if we're not replacing a package, clean up orphaned bits
- if (!queryReplace) {
- disableNonexistentListeners();
- }
- // make sure we're still bound to any of our
- // listeners who may have just upgraded
- rebindListenerServices();
- }
- }
-
- /**
- * asynchronously notify all listeners about a new notification
- */
- public void notifyPostedLocked(StatusBarNotification sbn) {
- // make a copy in case changes are made to the underlying Notification object
- final StatusBarNotification sbnClone = sbn.clone();
- for (final NotificationListenerInfo info : mListeners) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- info.notifyPostedIfUserMatch(sbnClone);
- }
- });
- }
- }
-
- /**
- * asynchronously notify all listeners about a removed notification
- */
- public void notifyRemovedLocked(StatusBarNotification sbn) {
- // make a copy in case changes are made to the underlying Notification object
- // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
- final StatusBarNotification sbnLight = sbn.cloneLight();
-
- for (final NotificationListenerInfo info : mListeners) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- info.notifyRemovedIfUserMatch(sbnLight);
- }
- });
- }
- }
-
- public NotificationListenerInfo checkListenerTokenLocked(INotificationListener listener) {
- checkNullListener(listener);
- final IBinder token = listener.asBinder();
- final int N = mListeners.size();
- for (int i=0; i<N; i++) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (info.listener.asBinder() == token) return info;
- }
- throw new SecurityException("Disallowed call from unknown listener: " + listener);
- }
-
- public void unregisterListener(INotificationListener listener, int userid) {
- checkNullListener(listener);
- // no need to check permissions; if your listener binder is in the list,
- // that's proof that you had permission to add it in the first place
- unregisterListenerImpl(listener, userid);
- }
-
- public void registerListener(INotificationListener listener,
- ComponentName component, int userid) {
- checkNullListener(listener);
- registerListenerImpl(listener, component, userid);
- }
-
- /**
- * Remove notification access for any services that no longer exist.
- */
- private void disableNonexistentListeners() {
- int[] userIds = mUserProfiles.getCurrentProfileIds();
- final int N = userIds.length;
- for (int i = 0 ; i < N; ++i) {
- disableNonexistentListeners(userIds[i]);
- }
- }
-
- private void disableNonexistentListeners(int userId) {
- String flatIn = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- userId);
- if (!TextUtils.isEmpty(flatIn)) {
- if (DBG) Slog.v(TAG, "flat before: " + flatIn);
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
- new Intent(NotificationListenerService.SERVICE_INTERFACE),
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
- userId);
-
- Set<ComponentName> installed = new ArraySet<ComponentName>();
- for (int i = 0, count = installedServices.size(); i < count; i++) {
- ResolveInfo resolveInfo = installedServices.get(i);
- ServiceInfo info = resolveInfo.serviceInfo;
-
- if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
- info.permission)) {
- Slog.w(TAG, "Skipping notification listener service "
- + info.packageName + "/" + info.name
- + ": it does not require the permission "
- + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
- continue;
- }
- installed.add(new ComponentName(info.packageName, info.name));
- }
-
- String flatOut = "";
- if (!installed.isEmpty()) {
- String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
- ArrayList<String> remaining = new ArrayList<String>(enabled.length);
- for (int i = 0; i < enabled.length; i++) {
- ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
- if (installed.contains(enabledComponent)) {
- remaining.add(enabled[i]);
- }
- }
- flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining);
- }
- if (DBG) Slog.v(TAG, "flat after: " + flatOut);
- if (!flatIn.equals(flatOut)) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- flatOut, userId);
- }
- }
- }
-
- /**
- * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS
- * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
- */
- private void rebindListenerServices() {
- final int[] userIds = mUserProfiles.getCurrentProfileIds();
- final int nUserIds = userIds.length;
-
- final SparseArray<String> flat = new SparseArray<String>();
-
- for (int i = 0; i < nUserIds; ++i) {
- flat.put(userIds[i], Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- userIds[i]));
- }
-
- NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
- final SparseArray<ArrayList<ComponentName>> toAdd
- = new SparseArray<ArrayList<ComponentName>>();
-
- synchronized (mMutex) {
- // unbind and remove all existing listeners
- toRemove = mListeners.toArray(toRemove);
-
- final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>();
- final ArraySet<String> newPackages = new ArraySet<String>();
-
- for (int i = 0; i < nUserIds; ++i) {
- final ArrayList<ComponentName> add = new ArrayList<ComponentName>();
- toAdd.put(userIds[i], add);
-
- // decode the list of components
- String toDecode = flat.get(userIds[i]);
- if (toDecode != null) {
- String[] components = toDecode.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
- for (int j = 0; j < components.length; j++) {
- final ComponentName component
- = ComponentName.unflattenFromString(components[j]);
- if (component != null) {
- newEnabled.add(component);
- add.add(component);
- newPackages.add(component.getPackageName());
- }
- }
-
- }
- }
- mEnabledListenersForCurrentProfiles = newEnabled;
- mEnabledListenerPackageNames = newPackages;
- }
-
- for (NotificationListenerInfo info : toRemove) {
- final ComponentName component = info.component;
- final int oldUser = info.userid;
- Slog.v(TAG, "disabling notification listener for user "
- + oldUser + ": " + component);
- unregisterListenerService(component, info.userid);
- }
-
- for (int i = 0; i < nUserIds; ++i) {
- final ArrayList<ComponentName> add = toAdd.get(userIds[i]);
- final int N = add.size();
- for (int j = 0; j < N; j++) {
- final ComponentName component = add.get(j);
- Slog.v(TAG, "enabling notification listener for user " + userIds[i] + ": "
- + component);
- registerListenerService(component, userIds[i]);
- }
- }
- }
-
- /**
- * Version of registerListener that takes the name of a
- * {@link android.service.notification.NotificationListenerService} to bind to.
- *
- * This is the mechanism by which third parties may subscribe to notifications.
- */
- private void registerListenerService(final ComponentName name, final int userid) {
- NotificationUtil.checkCallerIsSystem();
-
- if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid);
-
- synchronized (mMutex) {
- final String servicesBindingTag = name.toString() + "/" + userid;
- if (mServicesBinding.contains(servicesBindingTag)) {
- // stop registering this thing already! we're working on it
- return;
- }
- mServicesBinding.add(servicesBindingTag);
-
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
- // cut old connections
- if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener);
- mListeners.remove(i);
- if (info.connection != null) {
- mContext.unbindService(info.connection);
- }
- }
- }
-
- Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
- intent.setComponent(name);
-
- intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
- R.string.notification_listener_binding_label);
-
- final PendingIntent pendingIntent = PendingIntent.getActivity(
- mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
-
- ApplicationInfo appInfo = null;
- try {
- appInfo = mContext.getPackageManager().getApplicationInfo(
- name.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- // Ignore if the package doesn't exist we won't be able to bind to the service.
- }
- final int targetSdkVersion =
- appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
-
- try {
- if (DBG) Slog.v(TAG, "binding: " + intent);
- if (!mContext.bindServiceAsUser(intent,
- new ServiceConnection() {
- INotificationListener mListener;
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- boolean added = false;
- synchronized (mMutex) {
- mServicesBinding.remove(servicesBindingTag);
- try {
- mListener = INotificationListener.Stub.asInterface(service);
- NotificationListenerInfo info
- = new NotificationListenerInfo(
- mListener, name, userid, this,
- targetSdkVersion);
- service.linkToDeath(info, 0);
- added = mListeners.add(info);
- } catch (RemoteException e) {
- // already dead
- }
- }
- if (added) {
- onServiceAdded(mListener);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Slog.v(TAG, "notification listener connection lost: " + name);
- }
- },
- Context.BIND_AUTO_CREATE,
- new UserHandle(userid)))
- {
- mServicesBinding.remove(servicesBindingTag);
- Slog.w(TAG, "Unable to bind listener service: " + intent);
- return;
- }
- } catch (SecurityException ex) {
- Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
- return;
- }
- }
- }
-
- /**
- * Remove a listener service for the given user by ComponentName
- */
- private void unregisterListenerService(ComponentName name, int userid) {
- NotificationUtil.checkCallerIsSystem();
-
- synchronized (mMutex) {
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
- mListeners.remove(i);
- if (info.connection != null) {
- try {
- mContext.unbindService(info.connection);
- } catch (IllegalArgumentException ex) {
- // something happened to the service: we think we have a connection
- // but it's bogus.
- Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex);
- }
- }
- }
- }
- }
- }
-
- /**
- * Removes a listener from the list but does not unbind from the listener's service.
- *
- * @return the removed listener.
- */
- private NotificationListenerInfo removeListenerImpl(
- final INotificationListener listener, final int userid) {
- NotificationListenerInfo listenerInfo = null;
- synchronized (mMutex) {
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (info.listener.asBinder() == listener.asBinder()
- && info.userid == userid) {
- listenerInfo = mListeners.remove(i);
- }
- }
- }
- return listenerInfo;
- }
-
- private void checkNullListener(INotificationListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("Listener must not be null");
- }
- }
-
- private void registerListenerImpl(final INotificationListener listener,
- final ComponentName component, final int userid) {
- synchronized (mMutex) {
- try {
- NotificationListenerInfo info
- = new NotificationListenerInfo(listener, component, userid,
- /*isSystem*/ true, Build.VERSION_CODES.L);
- listener.asBinder().linkToDeath(info, 0);
- mListeners.add(info);
- } catch (RemoteException e) {
- // already dead
- }
- }
- }
-
- /**
- * Removes a listener from the list and unbinds from its service.
- */
- private void unregisterListenerImpl(final INotificationListener listener, final int userid) {
- NotificationListenerInfo info = removeListenerImpl(listener, userid);
- if (info != null && info.connection != null) {
- mContext.unbindService(info.connection);
- }
- }
-
- private class SettingsObserver extends ContentObserver {
- private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
- = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
- private SettingsObserver(Handler handler) {
- super(handler);
- }
-
- private void observe() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
- false, this, UserHandle.USER_ALL);
- update(null);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- update(uri);
- }
-
- private void update(Uri uri) {
- if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
- rebindListenerServices();
- }
- }
- }
-
- public class NotificationListenerInfo implements IBinder.DeathRecipient {
- public INotificationListener listener;
- public ComponentName component;
- public int userid;
- public boolean isSystem;
- public ServiceConnection connection;
- public int targetSdkVersion;
-
- public NotificationListenerInfo(INotificationListener listener, ComponentName component,
- int userid, boolean isSystem, int targetSdkVersion) {
- this.listener = listener;
- this.component = component;
- this.userid = userid;
- this.isSystem = isSystem;
- this.connection = null;
- this.targetSdkVersion = targetSdkVersion;
- }
-
- public NotificationListenerInfo(INotificationListener listener, ComponentName component,
- int userid, ServiceConnection connection, int targetSdkVersion) {
- this.listener = listener;
- this.component = component;
- this.userid = userid;
- this.isSystem = false;
- this.connection = connection;
- this.targetSdkVersion = targetSdkVersion;
- }
-
- public boolean enabledAndUserMatches(StatusBarNotification sbn) {
- final int nid = sbn.getUserId();
- if (!isEnabledForCurrentProfiles()) {
- return false;
- }
- if (this.userid == UserHandle.USER_ALL) return true;
- if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
- return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
- }
-
- public boolean supportsProfiles() {
- return targetSdkVersion >= Build.VERSION_CODES.L;
- }
-
- public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
- if (!enabledAndUserMatches(sbn)) {
- return;
- }
- try {
- listener.onNotificationPosted(sbn);
- } catch (RemoteException ex) {
- Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
- }
- }
-
- public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
- if (!enabledAndUserMatches(sbn)) return;
- try {
- listener.onNotificationRemoved(sbn);
- } catch (RemoteException ex) {
- Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
- }
- }
-
- @Override
- public void binderDied() {
- // Remove the listener, but don't unbind from the service. The system will bring the
- // service back up, and the onServiceConnected handler will readd the listener with the
- // new binding. If this isn't a bound service, and is just a registered
- // INotificationListener, just removing it from the list is all we need to do anyway.
- removeListenerImpl(this.listener, this.userid);
- }
-
- /** convenience method for looking in mEnabledListenersForCurrentProfiles */
- public boolean isEnabledForCurrentProfiles() {
- if (this.isSystem) return true;
- if (this.connection == null) return false;
- return mEnabledListenersForCurrentProfiles.contains(this.component);
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5f096cb..5a1f9b2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -22,6 +22,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -35,10 +36,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -49,15 +50,18 @@
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IInterface;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.INotificationListener;
+import android.service.notification.IConditionProvider;
+import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
+import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -65,7 +69,6 @@
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.Xml;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -78,7 +81,8 @@
import com.android.server.SystemService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
-import com.android.server.notification.NotificationListeners.NotificationListenerInfo;
+import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.notification.NotificationUsageStats.SingleNotificationStats;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -101,7 +105,6 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.NoSuchElementException;
/** {@hide} */
@@ -191,8 +194,9 @@
final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
- private NotificationListeners mListeners;
private final UserProfiles mUserProfiles = new UserProfiles();
+ private NotificationListeners mListeners;
+ private ConditionProviders mConditionProviders;
private final NotificationUsageStats mUsageStats = new NotificationUsageStats();
@@ -745,6 +749,7 @@
}
}
mListeners.onPackagesChanged(queryReplace, pkgList);
+ mConditionProviders.onPackagesChanged(queryReplace, pkgList);
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@@ -845,18 +850,9 @@
importOldBlockDb();
- mListeners = new NotificationListeners(getContext(),
- mHandler, mNotificationList, mUserProfiles) {
- @Override
- public void onServiceAdded(INotificationListener listener) {
- final String[] keys = getActiveNotificationKeysFromListener(listener);
- try {
- listener.onListenerConnected(keys);
- } catch (RemoteException e) {
- // we tried
- }
- }
- };
+ mListeners = new NotificationListeners();
+ mConditionProviders = new ConditionProviders(getContext(),
+ mHandler, mNotificationList, mUserProfiles);
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -972,6 +968,7 @@
// bind to listener services.
mSettingsObserver.observe();
mListeners.onBootPhaseAppsCanStart();
+ mConditionProviders.onBootPhaseAppsCanStart();
}
}
@@ -1005,8 +1002,7 @@
return ;
}
- final boolean isSystemToast =
- NotificationUtil.isCallerSystem() || ("android".equals(pkg));
+ final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
if (!isSystemToast) {
@@ -1097,7 +1093,7 @@
@Override
public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
- NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
+ checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
// Don't allow client applications to cancel foreground service notis.
@@ -1109,7 +1105,7 @@
@Override
public void cancelAllNotifications(String pkg, int userId) {
- NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
+ checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
@@ -1123,7 +1119,7 @@
@Override
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
- NotificationUtil.checkCallerIsSystem();
+ checkCallerIsSystem();
setNotificationsEnabledForPackageImpl(pkg, uid, enabled);
}
@@ -1133,7 +1129,7 @@
*/
@Override
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
- NotificationUtil.checkCallerIsSystem();
+ checkCallerIsSystem();
return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
== AppOpsManager.MODE_ALLOWED);
}
@@ -1201,8 +1197,8 @@
@Override
public void registerListener(final INotificationListener listener,
final ComponentName component, final int userid) {
- NotificationUtil.checkCallerIsSystem();
- mListeners.registerListener(listener, component, userid);
+ checkCallerIsSystem();
+ mListeners.registerService(listener, component, userid);
}
/**
@@ -1210,7 +1206,7 @@
*/
@Override
public void unregisterListener(INotificationListener listener, int userid) {
- mListeners.unregisterListener(listener, userid);
+ mListeners.unregisterService(listener, userid);
}
/**
@@ -1227,8 +1223,7 @@
long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
- final NotificationListenerInfo info =
- mListeners.checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (keys != null) {
final int N = keys.length;
for (int i = 0; i < N; i++) {
@@ -1237,7 +1232,7 @@
if (userId != info.userid && userId != UserHandle.USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
throw new SecurityException("Disallowed call from listener: "
- + info.listener);
+ + info.service);
}
if (r != null) {
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
@@ -1255,7 +1250,7 @@
}
}
- private void cancelNotificationFromListenerLocked(NotificationListenerInfo info,
+ private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
@@ -1278,8 +1273,7 @@
long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
- final NotificationListenerInfo info =
- mListeners.checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (info.supportsProfiles()) {
Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
+ "from " + info.component
@@ -1305,14 +1299,14 @@
public StatusBarNotification[] getActiveNotificationsFromListener(
INotificationListener token, String[] keys) {
synchronized (mNotificationList) {
- final NotificationListenerInfo info = mListeners.checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final ArrayList<StatusBarNotification> list
= new ArrayList<StatusBarNotification>();
if (keys == null) {
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn)) {
+ if (info.enabledAndUserMatches(sbn.getUserId())) {
list.add(sbn);
}
}
@@ -1320,7 +1314,7 @@
final int N = keys.length;
for (int i=0; i<N; i++) {
NotificationRecord r = mNotificationsByKey.get(keys[i]);
- if (r != null && info.enabledAndUserMatches(r.sbn)) {
+ if (r != null && info.enabledAndUserMatches(r.sbn.getUserId())) {
list.add(r.sbn);
}
}
@@ -1336,17 +1330,23 @@
@Override
public ZenModeConfig getZenModeConfig() {
- NotificationUtil.checkCallerIsSystem();
+ checkCallerIsSystem();
return mZenModeHelper.getConfig();
}
@Override
public boolean setZenModeConfig(ZenModeConfig config) {
- NotificationUtil.checkCallerIsSystem();
+ checkCallerIsSystem();
return mZenModeHelper.setConfig(config);
}
@Override
+ public void notifyCondition(IConditionProvider provider, Condition condition) {
+ // TODO check token
+ mZenModeHelper.notifyCondition(condition);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -1362,12 +1362,12 @@
private String[] getActiveNotificationKeysFromListener(INotificationListener token) {
synchronized (mNotificationList) {
- final NotificationListenerInfo info = mListeners.checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final ArrayList<String> keys = new ArrayList<String>();
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
final StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn)) {
+ if (info.enabledAndUserMatches(sbn.getUserId())) {
keys.add(sbn.getKey());
}
}
@@ -1379,6 +1379,7 @@
pw.println("Current Notification Manager state:");
mListeners.dump(pw);
+ mConditionProviders.dump(pw);
int N;
@@ -1455,9 +1456,8 @@
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
- NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
- final boolean isSystemNotification =
- NotificationUtil.isUidSystem(callingUid) || ("android".equals(pkg));
+ checkCallerIsSystemOrSameApp(pkg);
+ final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
@@ -2028,7 +2028,7 @@
void cancelNotification(final int callingUid, final int callingPid,
final String pkg, final String tag, final int id,
final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
- final int userId, final int reason, final NotificationListenerInfo listener) {
+ final int userId, final int reason, final ManagedServiceInfo listener) {
// In enqueueNotificationInternal notifications are added by scheduling the
// work on the worker handler. Hence, we also schedule the cancel on this
// handler to avoid a scenario where an add notification call followed by a
@@ -2099,7 +2099,7 @@
*/
boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags,
int mustNotHaveFlags, boolean doit, int userId, int reason,
- NotificationListenerInfo listener) {
+ ManagedServiceInfo listener) {
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
listener == null ? null : listener.component.toShortString());
@@ -2141,7 +2141,7 @@
}
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
- NotificationListenerInfo listener, boolean includeCurrentProfiles) {
+ ManagedServiceInfo listener, boolean includeCurrentProfiles) {
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
null, userId, 0, 0, reason,
listener == null ? null : listener.component.toShortString());
@@ -2234,38 +2234,129 @@
}
}
- public static class UserProfiles {
- // Profiles of the current user.
- private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+ private static boolean isUidSystem(int uid) {
+ final int appid = UserHandle.getAppId(uid);
+ return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
+ }
- private void updateCache(Context context) {
- UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- if (userManager != null) {
- int currentUserId = ActivityManager.getCurrentUser();
- List<UserInfo> profiles = userManager.getProfiles(currentUserId);
- synchronized (mCurrentProfiles) {
- mCurrentProfiles.clear();
- for (UserInfo user : profiles) {
- mCurrentProfiles.put(user.id, user);
+ private static boolean isCallerSystem() {
+ return isUidSystem(Binder.getCallingUid());
+ }
+
+ private static void checkCallerIsSystem() {
+ if (isCallerSystem()) {
+ return;
+ }
+ throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
+ }
+
+ private static void checkCallerIsSystemOrSameApp(String pkg) {
+ if (isCallerSystem()) {
+ return;
+ }
+ final int uid = Binder.getCallingUid();
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
+ pkg, 0, UserHandle.getCallingUserId());
+ if (!UserHandle.isSameApp(ai.uid, uid)) {
+ throw new SecurityException("Calling uid " + uid + " gave package"
+ + pkg + " which is owned by uid " + ai.uid);
+ }
+ } catch (RemoteException re) {
+ throw new SecurityException("Unknown package " + pkg + "\n" + re);
+ }
+ }
+
+ public class NotificationListeners extends ManagedServices {
+
+ public NotificationListeners() {
+ super(getContext(), mHandler, mNotificationList, mUserProfiles);
+ }
+
+ @Override
+ protected Config getConfig() {
+ Config c = new Config();
+ c.caption = "notification listener";
+ c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
+ c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
+ c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
+ c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
+ c.clientLabel = R.string.notification_listener_binding_label;
+ return c;
+ }
+
+ @Override
+ protected IInterface asInterface(IBinder binder) {
+ return INotificationListener.Stub.asInterface(binder);
+ }
+
+ @Override
+ public void onServiceAdded(IInterface service) {
+ final INotificationListener listener = (INotificationListener) service;
+ final String[] keys = getActiveNotificationKeysFromListener(listener);
+ try {
+ listener.onListenerConnected(keys);
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+
+ /**
+ * asynchronously notify all listeners about a new notification
+ */
+ public void notifyPostedLocked(StatusBarNotification sbn) {
+ // make a copy in case changes are made to the underlying Notification object
+ final StatusBarNotification sbnClone = sbn.clone();
+ for (final ManagedServiceInfo info : mServices) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyPostedIfUserMatch(info, sbnClone);
}
- }
+ });
}
}
- public int[] getCurrentProfileIds() {
- synchronized (mCurrentProfiles) {
- int[] users = new int[mCurrentProfiles.size()];
- final int N = mCurrentProfiles.size();
- for (int i = 0; i < N; ++i) {
- users[i] = mCurrentProfiles.keyAt(i);
- }
- return users;
+ /**
+ * asynchronously notify all listeners about a removed notification
+ */
+ public void notifyRemovedLocked(StatusBarNotification sbn) {
+ // make a copy in case changes are made to the underlying Notification object
+ // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
+ // notification
+ final StatusBarNotification sbnLight = sbn.cloneLight();
+ for (ManagedServiceInfo serviceInfo : mServices) {
+ final ManagedServiceInfo info = (ManagedServiceInfo) serviceInfo;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyRemovedIfUserMatch(info, sbnLight);
+ }
+ });
}
}
- public boolean isCurrentProfile(int userId) {
- synchronized (mCurrentProfiles) {
- return mCurrentProfiles.get(userId) != null;
+ private void notifyPostedIfUserMatch(ManagedServiceInfo info, StatusBarNotification sbn) {
+ if (!info.enabledAndUserMatches(sbn.getUserId())) {
+ return;
+ }
+ final INotificationListener listener = (INotificationListener)info.service;
+ try {
+ listener.onNotificationPosted(sbn);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
+ }
+ }
+
+ private void notifyRemovedIfUserMatch(ManagedServiceInfo info, StatusBarNotification sbn) {
+ if (!info.enabledAndUserMatches(sbn.getUserId())) {
+ return;
+ }
+ final INotificationListener listener = (INotificationListener)info.service;
+ try {
+ listener.onNotificationRemoved(sbn);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationUtil.java b/services/core/java/com/android/server/notification/NotificationUtil.java
deleted file mode 100644
index 459adce..0000000
--- a/services/core/java/com/android/server/notification/NotificationUtil.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * 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.notification;
-
-import android.app.AppGlobals;
-import android.content.pm.ApplicationInfo;
-import android.os.Binder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-
-public class NotificationUtil {
-
- // Return true if the UID is a system or phone UID and therefore should not have
- // any notifications or toasts blocked.
- public static boolean isUidSystem(int uid) {
- final int appid = UserHandle.getAppId(uid);
- return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
- }
-
- // same as isUidSystem(int, int) for the Binder caller's UID.
- public static boolean isCallerSystem() {
- return isUidSystem(Binder.getCallingUid());
- }
-
- public static void checkCallerIsSystem() {
- if (isCallerSystem()) {
- return;
- }
- throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
- }
-
- public static void checkCallerIsSystemOrSameApp(String pkg) {
- if (isCallerSystem()) {
- return;
- }
- final int uid = Binder.getCallingUid();
- try {
- ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
- pkg, 0, UserHandle.getCallingUserId());
- if (!UserHandle.isSameApp(ai.uid, uid)) {
- throw new SecurityException("Calling uid " + uid + " gave package"
- + pkg + " which is owned by uid " + ai.uid);
- }
- } catch (RemoteException re) {
- throw new SecurityException("Unknown package " + pkg + "\n" + re);
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 80f5b5c..b00beb6 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -33,6 +33,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.provider.Settings.Global;
+import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.util.Slog;
@@ -201,6 +202,10 @@
return true;
}
+ public void notifyCondition(Condition condition) {
+ Slog.d(TAG, "notifyCondition " + condition);
+ }
+
private boolean isCall(String pkg, Notification n) {
return CALL_PACKAGES.contains(pkg);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2756a1c..ad07084 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -104,7 +104,6 @@
import android.os.FileObserver;
import android.os.FileUtils;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -276,8 +275,6 @@
final PackageHandler mHandler;
final int mSdkVersion = Build.VERSION.SDK_INT;
- final String mSdkCodename = "REL".equals(Build.VERSION.CODENAME)
- ? null : Build.VERSION.CODENAME;
final Context mContext;
final boolean mFactoryTest;
@@ -2163,6 +2160,24 @@
}
@Override
+ public boolean activitySupportsIntent(ComponentName component, Intent intent,
+ String resolvedType) {
+ synchronized (mPackages) {
+ PackageParser.Activity a = mActivities.mActivities.get(component);
+ if (a == null) {
+ return false;
+ }
+ for (int i=0; i<a.intents.size(); i++) {
+ if (a.intents.get(i).match(intent.getAction(), resolvedType, intent.getScheme(),
+ intent.getData(), intent.getCategories(), TAG) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get receiver info");
@@ -4835,7 +4850,6 @@
pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString;
}
}
-
pkgSetting.uidError = uidError;
}
@@ -5416,7 +5430,8 @@
}
}
- private String calculateApkRoot(final File codePath) {
+ private String calculateApkRoot(final String codePathString) {
+ final File codePath = new File(codePathString);
final File codeRoot;
if (FileUtils.contains(Environment.getRootDirectory(), codePath)) {
codeRoot = Environment.getRootDirectory();
@@ -5453,12 +5468,12 @@
PackageSetting pkgSetting) {
// "bundled" here means system-installed with no overriding update
final boolean bundledApk = isSystemApp(pkg) && !isUpdatedSystemApp(pkg);
- final String apkName = getApkName(pkgSetting.codePathString);
+ final String apkName = getApkName(pkg.applicationInfo.sourceDir);
final File libDir;
if (bundledApk) {
// If "/system/lib64/apkname" exists, assume that is the per-package
// native library directory to use; otherwise use "/system/lib/apkname".
- String apkRoot = calculateApkRoot(pkgSetting.codePath);
+ String apkRoot = calculateApkRoot(pkg.applicationInfo.sourceDir);
File lib64 = new File(apkRoot, LIB64_DIR_NAME);
File packLib64 = new File(lib64, apkName);
libDir = (packLib64.exists()) ? lib64 : new File(apkRoot, LIB_DIR_NAME);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 210e151b..f8103de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -22,7 +22,6 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityThread;
-import android.app.admin.DevicePolicyManager;
import android.app.IStopUserCallback;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -263,46 +262,58 @@
if (userId != UserHandle.getCallingUserId()) {
checkManageUsersPermission("getting profiles related to user " + userId);
}
- synchronized (mPackagesLock) {
- // Getting the service here is not good for testing purposes. However, this service
- // is not available when UserManagerService starts up so we need a lazy load.
-
- DevicePolicyManager dpm = null;
- if (enabledOnly) {
- dpm = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mPackagesLock) {
+ return getProfilesLocked(userId, enabledOnly);
}
-
- UserInfo user = getUserInfoLocked(userId);
- ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
- for (int i = 0; i < mUsers.size(); i++) {
- UserInfo profile = mUsers.valueAt(i);
- if (!isProfileOf(user, profile)) {
- continue;
- }
-
- if (enabledOnly && profile.isManagedProfile()) {
- if (dpm != null) {
- if(!dpm.isProfileEnabled(profile.id)) {
- continue;
- }
- } else {
- Log.w(LOG_TAG,
- "Attempting to reach DevicePolicyManager before it was started");
- // TODO: There might be system apps that need to call this. Make sure that
- // DevicePolicyManagerService is ready at that time (otherwise, any default
- // value is a bad one).
- throw new IllegalArgumentException(String.format(
- "Attempting to get enabled profiles for %d before "
- + "DevicePolicyManagerService has been started.", userId));
- }
- }
- users.add(profile);
- }
- return users;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
+ /** Assume permissions already checked and caller's identity cleared */
+ private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
+ // Getting the service here is not good for testing purposes.
+ // However, this service is not available when UserManagerService starts
+ // up so we need a lazy load.
+
+ DevicePolicyManager dpm = null;
+ if (enabledOnly) {
+ dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ }
+
+ UserInfo user = getUserInfoLocked(userId);
+ ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo profile = mUsers.valueAt(i);
+ if (!isProfileOf(user, profile)) {
+ continue;
+ }
+
+ if (enabledOnly && profile.isManagedProfile()) {
+ if (dpm != null) {
+ if (!dpm.isProfileEnabled(profile.id)) {
+ continue;
+ }
+ } else {
+ Log.w(LOG_TAG,
+ "Attempting to reach DevicePolicyManager before it is started");
+ // TODO: There might be system apps that need to call this.
+ // Make sure that DevicePolicyManagerService is ready at that
+ // time (otherwise, any default value is a bad one).
+ throw new IllegalArgumentException(String.format(
+ "Attempting to get enabled profiles for %d before "
+ + "DevicePolicyManagerService has been started.",
+ userId));
+ }
+ }
+ users.add(profile);
+ }
+ return users;
+ }
+
private boolean isProfileOf(UserInfo user, UserInfo profile) {
return user.id == profile.id ||
(user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
@@ -459,13 +470,13 @@
synchronized (mPackagesLock) {
Bundle restrictions = mUserRestrictions.get(userId);
- return restrictions != null ? restrictions : Bundle.EMPTY;
+ return restrictions != null ? new Bundle(restrictions) : new Bundle();
}
}
@Override
public void setUserRestrictions(Bundle restrictions, int userId) {
- checkProfileOwnerOrManageUsersPermission("setUserRestrictions");
+ checkManageUsersPermission("setUserRestrictions");
if (restrictions == null) return;
synchronized (mPackagesLock) {
@@ -491,51 +502,14 @@
* @param message used as message if SecurityException is thrown
* @throws SecurityException if the caller is not system or root
*/
- private final void checkManageUsersPermission(String message) {
+ private static final void checkManageUsersPermission(String message) {
final int uid = Binder.getCallingUid();
-
- if (missingManageUsersPermission(uid)) {
- throw new SecurityException("You need MANAGE_USERS permission to: " + message);
- }
- }
-
- /**
- * Enforces that only the system UID, root's UID, apps that have the
- * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS}
- * permission, the profile owner, or the device owner can make certain calls to the
- * UserManager.
- *
- * @param message used as message if SecurityException is thrown
- * @throws SecurityException if the caller is not system, root, or device
- * owner
- */
- private final void checkProfileOwnerOrManageUsersPermission(String message) {
- final int uid = Binder.getCallingUid();
- boolean isProfileOwner = false;
- if (mContext != null && mContext.getPackageManager() != null) {
- String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid);
- DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- if (dpm != null) {
- for (String pkg : pkgs) {
- if (dpm.isDeviceOwnerApp(pkg) || dpm.isProfileOwnerApp(pkg)) {
- isProfileOwner = true;
- }
- }
- }
- }
-
- if (missingManageUsersPermission(uid) && !isProfileOwner) {
- throw new SecurityException(
- "You need MANAGE_USERS permission or device owner privileges to: " + message);
- }
- }
-
- private boolean missingManageUsersPermission(int uid) {
- return uid != Process.SYSTEM_UID && uid != 0
+ if (uid != Process.SYSTEM_UID && uid != 0
&& ActivityManager.checkComponentPermission(
android.Manifest.permission.MANAGE_USERS,
- uid, -1, true) != PackageManager.PERMISSION_GRANTED;
+ uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need MANAGE_USERS permission to: " + message);
+ }
}
private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
@@ -1240,8 +1214,7 @@
public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
if (UserHandle.getCallingUserId() != userId
|| !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
- checkProfileOwnerOrManageUsersPermission(
- "Only system or device owner can get restrictions for other users/apps");
+ checkManageUsersPermission("Only system can get restrictions for other users/apps");
}
synchronized (mPackagesLock) {
// Read the restrictions from XML
@@ -1254,8 +1227,7 @@
int userId) {
if (UserHandle.getCallingUserId() != userId
|| !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
- checkProfileOwnerOrManageUsersPermission(
- "Only system or device owner can set restrictions for other users/apps");
+ checkManageUsersPermission("Only system can set restrictions for other users/apps");
}
synchronized (mPackagesLock) {
// Write the restrictions to XML
@@ -1357,8 +1329,7 @@
@Override
public void removeRestrictions() {
- checkProfileOwnerOrManageUsersPermission(
- "Only system or device owner can remove restrictions");
+ checkManageUsersPermission("Only system can remove restrictions");
final int userHandle = UserHandle.getCallingUserId();
removeRestrictionsForUser(userHandle, true);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f17b2f4..9039236 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -162,13 +162,10 @@
private final Interpolator mThumbnailFadeoutInterpolator;
private int mCurrentUserId = 0;
- private boolean mUseAlternateThumbnailAnimation;
AppTransition(Context context, Handler h) {
mContext = context;
mH = h;
- mUseAlternateThumbnailAnimation =
- SystemProperties.getBoolean("persist.anim.use_alt_thumbnail", false);
mConfigShortAnimTime = context.getResources().getInteger(
com.android.internal.R.integer.config_shortAnimTime);
mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
@@ -668,7 +665,7 @@
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
int appWidth, int appHeight, int orientation,
- Rect containingFrame, Rect contentInsets) {
+ Rect containingFrame, Rect contentInsets, Configuration configuration) {
Animation a;
if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
a = loadAnimation(mNextAppTransitionPackage, enter ?
@@ -689,7 +686,8 @@
mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
- if (mUseAlternateThumbnailAnimation) {
+ boolean useAlternateThumbnailAnimation = (configuration.smallestScreenWidthDp < 600);
+ if (useAlternateThumbnailAnimation) {
a = createAlternateThumbnailEnterExitAnimationLocked(
getThumbnailTransitionState(enter), appWidth, appHeight, orientation,
transit, containingFrame, contentInsets);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b56377c..637beec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3190,7 +3190,7 @@
}
Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
- mCurConfiguration.orientation, containingFrame, contentInsets);
+ mCurConfiguration.orientation, containingFrame, contentInsets, mCurConfiguration);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -8660,8 +8660,7 @@
wtoken.deferClearAllDrawn = false;
}
- boolean useAlternateThumbnailAnimation =
- SystemProperties.getBoolean("persist.anim.use_alt_thumbnail", false);
+ boolean useAlternateThumbnailAnimation = (mCurConfiguration.smallestScreenWidthDp < 600);
AppWindowAnimator appAnimator =
topOpeningApp == null ? null : topOpeningApp.mAppAnimator;
Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail();
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 1b3887c..51583a5 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -11,7 +11,9 @@
$(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
$(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \
$(LOCAL_REL_DIR)/com_android_server_dreams_McuHal.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecController.cpp \
$(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiMhlController.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputApplicationHandle.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputWindowHandle.cpp \
diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
new file mode 100644
index 0000000..f3e8f3c
--- /dev/null
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "HdmiCecControllerJni"
+
+#define LOG_NDEBUG 1
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <hardware/hdmi_cec.h>
+
+namespace android {
+
+static struct {
+ jmethodID handleMessage;
+} gHdmiCecControllerClassInfo;
+
+
+class HdmiCecController {
+public:
+ HdmiCecController(jobject callbacksObj);
+
+private:
+ static void onReceived(const hdmi_event_t* event, void* arg);
+
+ jobject mCallbacksObj;
+};
+
+HdmiCecController::HdmiCecController(jobject callbacksObj) :
+ mCallbacksObj(callbacksObj) {
+}
+
+// static
+void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
+ HdmiCecController* handler = static_cast<HdmiCecController*>(arg);
+ if (handler == NULL) {
+ return;
+ }
+
+ // TODO: propagate message to Java layer.
+}
+
+
+//------------------------------------------------------------------------------
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj) {
+ // TODO: initialize hal and pass it to controller if ready.
+
+ HdmiCecController* controller = new HdmiCecController(
+ env->NewGlobalRef(callbacksObj));
+
+ return reinterpret_cast<jlong>(controller);
+}
+
+static JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeInit", "(Lcom/android/server/hdmi/HdmiCecController;)J",
+ (void *) nativeInit },
+};
+
+#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
+
+int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
+ LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+ return 0;
+}
+
+} /* namespace android */
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index bf9f7f4..1feb325 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -38,7 +38,9 @@
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_dreams_McuHal(JNIEnv* env);
+int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
int register_android_server_hdmi_HdmiCecService(JNIEnv* env);
+int register_android_server_hdmi_HdmiMhlController(JNIEnv* env);
};
using namespace android;
@@ -72,7 +74,10 @@
register_android_server_ConsumerIrService(env);
register_android_server_dreams_McuHal(env);
register_android_server_BatteryStatsService(env);
+ register_android_server_hdmi_HdmiCecController(env);
+ // TODO: remove this once replaces HdmiCecService with HdmiControlService.
register_android_server_hdmi_HdmiCecService(env);
+ register_android_server_hdmi_HdmiMhlController(env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b82a126..f1ee280 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3118,4 +3118,24 @@
}
}
}
+
+ @Override
+ public void setUserRestriction(ComponentName who, String key, boolean enabled) {
+ final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ UserManager um = UserManager.get(mContext);
+ long id = Binder.clearCallingIdentity();
+ try {
+ um.setUserRestriction(key, enabled, userHandle);
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f08d69f..00d5468 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -61,6 +61,7 @@
import com.android.server.content.ContentService;
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
+import com.android.server.hdmi.HdmiControlService;
import com.android.server.input.InputManagerService;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LightsService;
@@ -114,6 +115,8 @@
"com.android.server.devicepolicy.DevicePolicyManagerService$Lifecycle";
private static final String APPWIDGET_SERVICE_CLASS =
"com.android.server.appwidget.AppWidgetService";
+ private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
+ "com.android.server.voiceinteraction.VoiceInteractionManagerService";
private static final String PRINT_MANAGER_SERVICE_CLASS =
"com.android.server.print.PrintManagerService";
private static final String USB_SERVICE_CLASS =
@@ -290,6 +293,7 @@
// Activity manager runs the show.
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
+ mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
}
private void startCoreServices() {
@@ -826,6 +830,15 @@
} catch (Throwable e) {
reportWtf("starting Recognition Service", e);
}
+
+ try {
+ if (pm.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) {
+ Slog.i(TAG, "Voice Recognition Service");
+ mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+ }
+ } catch (Throwable e) {
+ reportWtf("starting Voice Recognition Service", e);
+ }
}
try {
@@ -919,6 +932,12 @@
}
try {
+ mSystemServiceManager.startService(HdmiControlService.class);
+ } catch (Throwable e) {
+ reportWtf("starting HdmiControlService", e);
+ }
+
+ try {
Slog.i(TAG, "TvInputManagerService");
mSystemServiceManager.startService(TvInputManagerService.class);
} catch (Throwable e) {
diff --git a/services/voiceinteraction/Android.mk b/services/voiceinteraction/Android.mk
new file mode 100644
index 0000000..c9e5dd0
--- /dev/null
+++ b/services/voiceinteraction/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.voiceinteraction
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,java)
+
+LOCAL_JAVA_LIBRARIES := services.core
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
new file mode 100644
index 0000000..9e2bcab
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -0,0 +1,209 @@
+/*
+ * 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.voiceinteraction;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+import android.util.Slog;
+import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.SystemService;
+import com.android.server.UiThread;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+
+/**
+ * SystemService that publishes an IVoiceInteractionManagerService.
+ */
+public class VoiceInteractionManagerService extends SystemService {
+
+ static final String TAG = "VoiceInteractionManagerService";
+
+ final Context mContext;
+ final ContentResolver mResolver;
+
+ public VoiceInteractionManagerService(Context context) {
+ super(context);
+ mContext = context;
+ mResolver = context.getContentResolver();
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.VOICE_INTERACTION_MANAGER_SERVICE, mServiceStub);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mServiceStub.systemRunning(isSafeMode());
+ }
+ }
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ mServiceStub.switchUser(userHandle);
+ }
+
+ // implementation entry point and binder service
+ private final VoiceInteractionManagerServiceStub mServiceStub
+ = new VoiceInteractionManagerServiceStub();
+
+ class VoiceInteractionManagerServiceStub extends IVoiceInteractionManagerService.Stub {
+
+ VoiceInteractionManagerServiceImpl mImpl;
+
+ private boolean mSafeMode;
+ private int mCurUser;
+
+ public void systemRunning(boolean safeMode) {
+ mSafeMode = safeMode;
+
+ mPackageMonitor.register(mContext, BackgroundThread.getHandler().getLooper(),
+ UserHandle.ALL, true);
+ new SettingsObserver(UiThread.getHandler());
+
+ synchronized (this) {
+ mCurUser = ActivityManager.getCurrentUser();
+ switchImplementationIfNeededLocked();
+ }
+ }
+
+ public void switchUser(int userHandle) {
+ synchronized (this) {
+ mCurUser = userHandle;
+ switchImplementationIfNeededLocked();
+ }
+ }
+
+ void switchImplementationIfNeededLocked() {
+ if (!mSafeMode) {
+ String curService = Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, mCurUser);
+ ComponentName serviceComponent = null;
+ if (curService != null && !curService.isEmpty()) {
+ try {
+ serviceComponent = ComponentName.unflattenFromString(curService);
+ } catch (RuntimeException e) {
+ Slog.wtf(TAG, "Bad voice interaction service name " + curService, e);
+ serviceComponent = null;
+ }
+ }
+ if (mImpl == null || mImpl.mUser != mCurUser
+ || !mImpl.mComponent.equals(serviceComponent)) {
+ if (mImpl != null) {
+ mImpl.shutdownLocked();
+ }
+ if (serviceComponent != null) {
+ mImpl = new VoiceInteractionManagerServiceImpl(mContext,
+ UiThread.getHandler(), this, mCurUser, serviceComponent);
+ mImpl.startLocked();
+ } else {
+ mImpl = null;
+ }
+ }
+ }
+ }
+
+ @Override
+ public int startVoiceActivity(Intent intent, String resolvedType,
+ IVoiceInteractionService service,
+ IVoiceInteractionSession session, IVoiceInteractor interactor) {
+ synchronized (this) {
+ if (mImpl == null || service.asBinder() != mImpl.mService.asBinder()) {
+ throw new SecurityException(
+ "Caller is not the current voice interaction service");
+ }
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mImpl.startVoiceActivityLocked(callingPid, callingUid,
+ intent, resolvedType, session, interactor);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ }
+
+ class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.VOICE_INTERACTION_SERVICE), false, this);
+ }
+
+ @Override public void onChange(boolean selfChange) {
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ switchImplementationIfNeededLocked();
+ }
+ }
+ }
+
+ PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+ return super.onHandleForceStop(intent, packages, uid, doit);
+ }
+
+ @Override
+ public void onHandleUserStop(Intent intent, int userHandle) {
+ super.onHandleUserStop(intent, userHandle);
+ }
+
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ super.onPackageDisappeared(packageName, reason);
+ }
+
+ @Override
+ public void onPackageAppeared(String packageName, int reason) {
+ super.onPackageAppeared(packageName, reason);
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ super.onPackageModified(packageName);
+ }
+
+ @Override
+ public void onSomePackagesChanged() {
+ super.onSomePackagesChanged();
+ }
+ };
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
new file mode 100644
index 0000000..af8ae1e
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -0,0 +1,125 @@
+/*
+ * 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.voiceinteraction;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VoiceInteractionService;
+import android.util.Slog;
+import com.android.internal.app.IVoiceInteractor;
+
+class VoiceInteractionManagerServiceImpl {
+ final static String TAG = "VoiceInteractionServiceManager";
+
+ final Context mContext;
+ final Handler mHandler;
+ final Object mLock;
+ final int mUser;
+ final ComponentName mComponent;
+ final IActivityManager mAm;
+ boolean mBound = false;
+ IVoiceInteractionService mService;
+ IVoiceInteractionSession mActiveSession;
+ IVoiceInteractor mActiveInteractor;
+
+ final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mService = IVoiceInteractionService.Stub.asInterface(service);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+ };
+
+ VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock,
+ int userHandle, ComponentName service) {
+ mContext = context;
+ mHandler = handler;
+ mLock = lock;
+ mUser = userHandle;
+ mComponent = service;
+ mAm = ActivityManagerNative.getDefault();
+ }
+
+ public int startVoiceActivityLocked(int callingPid, int callingUid, Intent intent,
+ String resolvedType, IVoiceInteractionSession session, IVoiceInteractor interactor) {
+ if (session == null) {
+ throw new NullPointerException("session is null");
+ }
+ if (interactor == null) {
+ throw new NullPointerException("interactor is null");
+ }
+ if (mActiveSession != null) {
+ // XXX cancel current session.
+ }
+ intent.addCategory(Intent.CATEGORY_VOICE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ mActiveSession = session;
+ mActiveInteractor = interactor;
+ try {
+ return mAm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid,
+ intent, resolvedType, mActiveSession, mActiveInteractor,
+ 0, null, null, null, mUser);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Unexpected remote error", e);
+ }
+ }
+
+ void startLocked() {
+ Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+ intent.setComponent(mComponent);
+ try {
+ ServiceInfo si = mContext.getPackageManager().getServiceInfo(mComponent, 0);
+ if (!android.Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
+ Slog.w(TAG, "Not using voice interaction service " + mComponent
+ + ": does not require permission "
+ + android.Manifest.permission.BIND_VOICE_INTERACTION);
+ return;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Unable to find voice interaction service: " + mComponent, e);
+ return;
+ }
+ mContext.bindServiceAsUser(intent, mConnection,
+ Context.BIND_AUTO_CREATE, new UserHandle(mUser));
+ mBound = true;
+ }
+
+ void shutdownLocked() {
+ if (mBound) {
+ mContext.unbindService(mConnection);
+ mBound = false;
+ }
+ }
+}
diff --git a/tests/VoiceInteraction/Android.mk b/tests/VoiceInteraction/Android.mk
new file mode 100644
index 0000000..8decca7
--- /dev/null
+++ b/tests/VoiceInteraction/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := VoiceInteraction
+
+include $(BUILD_PACKAGE)
diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml
new file mode 100644
index 0000000..9c5acf9
--- /dev/null
+++ b/tests/VoiceInteraction/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.voiceinteraction">
+
+ <application>
+ <activity android:name="VoiceInteractionMain" android:label="Voice Interaction">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <service android:name="MainInteractionService"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:process=":interactor">
+ <intent-filter>
+ <action android:name="android.service.voice.VoiceInteractionService" />
+ </intent-filter>
+ </service>
+ <activity android:name="TestInteractionActivity" android:label="Voice Interaction Target">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.VOICE" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/VoiceInteraction/res/layout/main.xml b/tests/VoiceInteraction/res/layout/main.xml
new file mode 100644
index 0000000..3d7a418
--- /dev/null
+++ b/tests/VoiceInteraction/res/layout/main.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <Button android:id="@+id/start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/start"
+ />
+
+</LinearLayout>
+
+
diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml
new file mode 100644
index 0000000..2abf65194
--- /dev/null
+++ b/tests/VoiceInteraction/res/layout/test_interaction.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="We are interacting!"
+ />
+
+ <TextView android:id="@+id/log"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ android:layout_marginTop="10dp"
+ android:textSize="12sp"
+ android:textColor="#ffffffff"
+ />
+
+</LinearLayout>
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
new file mode 100644
index 0000000..12edb31
--- /dev/null
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<resources>
+
+ <string name="start">Start!</string>
+
+</resources>
+
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
new file mode 100644
index 0000000..35702f1
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -0,0 +1,39 @@
+/*
+ * 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.voiceinteraction;
+
+import android.content.Intent;
+import android.service.voice.VoiceInteractionService;
+import android.util.Log;
+
+public class MainInteractionService extends VoiceInteractionService {
+ static final String TAG = "MainInteractionService";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i(TAG, "Creating " + this);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ startVoiceActivity(new Intent(this, TestInteractionActivity.class),
+ new MainInteractionSession(this));
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
new file mode 100644
index 0000000..adc0df4
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -0,0 +1,53 @@
+/*
+ * 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.voiceinteraction;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.util.Log;
+
+public class MainInteractionSession extends VoiceInteractionSession {
+ static final String TAG = "MainInteractionSession";
+
+ MainInteractionSession(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean[] onGetSupportedCommands(Caller caller, String[] commands) {
+ return new boolean[commands.length];
+ }
+
+ @Override
+ public void onConfirm(Caller caller, Request request, String prompt, Bundle extras) {
+ Log.i(TAG, "onConform: prompt=" + prompt + " extras=" + extras);
+ request.sendConfirmResult(true, null);
+ }
+
+ @Override
+ public void onCommand(Caller caller, Request request, String command, Bundle extras) {
+ Log.i(TAG, "onCommand: command=" + command + " extras=" + extras);
+ request.sendCommandResult(null);
+ }
+
+ @Override
+ public void onCancel(Request request) {
+ Log.i(TAG, "onCancel");
+ request.sendCancelResult();
+ }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
new file mode 100644
index 0000000..016a80e
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.voiceinteraction;
+
+import android.app.Activity;
+import android.app.VoiceInteractor;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestInteractionActivity extends Activity {
+ static final String TAG = "TestInteractionActivity";
+
+ VoiceInteractor mInteractor;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.test_interaction);
+ if (!isVoiceInteraction()) {
+ Log.w(TAG, "Not running as a voice interaction!");
+ finish();
+ return;
+ }
+
+ mInteractor = getVoiceInteractor();
+ mInteractor.startConfirmation(new VoiceInteractor.Callback() {
+ @Override
+ public void onConfirmationResult(VoiceInteractor.Request request, boolean confirmed,
+ Bundle result) {
+ Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result);
+ finish();
+ }
+
+ @Override
+ public void onCancel(VoiceInteractor.Request request) {
+ Log.i(TAG, "Canceled!");
+ finish();
+ }
+ }, "This is a confirmation", null);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java
new file mode 100644
index 0000000..5d212a4
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java
@@ -0,0 +1,49 @@
+/*
+ * 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.voiceinteraction;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+public class VoiceInteractionMain extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main);
+ findViewById(R.id.start).setOnClickListener(mStartListener);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ View.OnClickListener mStartListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ startService(new Intent(VoiceInteractionMain.this, MainInteractionService.class));
+ }
+ };
+}