diff options
119 files changed, 4297 insertions, 1123 deletions
diff --git a/Android.mk b/Android.mk index be7e055b2ec8..df8fbd92ac25 100644 --- a/Android.mk +++ b/Android.mk @@ -180,6 +180,7 @@ LOCAL_SRC_FILES += \ 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 @@ LOCAL_SRC_FILES += \ 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 @@ LOCAL_SRC_FILES += \ 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 63be1ce2266b..81362f17ce17 100644 --- a/api/current.txt +++ b/api/current.txt @@ -31,6 +31,7 @@ package android { 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 @@ package android.app { 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 @@ package android.app { 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 @@ package android.app { 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(); @@ -6999,6 +7019,7 @@ package android.content { 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"; @@ -9712,6 +9733,7 @@ package android.graphics { method public void drawRect(android.graphics.Rect, android.graphics.Paint); method public void drawRect(float, float, float, float, android.graphics.Paint); method public void drawRoundRect(android.graphics.RectF, float, float, android.graphics.Paint); + method public void drawRoundRect(float, float, float, float, float, float, android.graphics.Paint); method public void drawText(char[], int, int, float, float, android.graphics.Paint); method public void drawText(java.lang.String, float, float, android.graphics.Paint); method public void drawText(java.lang.String, int, int, float, float, android.graphics.Paint); @@ -10024,11 +10046,16 @@ package android.graphics { 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 { @@ -10751,7 +10778,7 @@ package android.graphics.drawable { 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(); @@ -13378,6 +13405,18 @@ package android.media { 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); @@ -24867,6 +24906,36 @@ package android.service.trust { } +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 7df55a5fd10e..6b55b7b24dda 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 @@ public class Am extends BaseCommand { "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 599a6086be3b..197eaf8d1cb3 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -22,6 +22,7 @@ import android.transition.TransitionManager; 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 @@ public class Activity extends ContextThemeWrapper /*package*/ ActionBar mActionBar = null; private boolean mEnableDefaultActionBarUp; + private VoiceInteractor mVoiceInteractor; + private CharSequence mTitle; private int mTitleColor = 0; @@ -1134,6 +1137,23 @@ public class Activity extends ContextThemeWrapper } /** + * 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 @@ public class Activity extends ContextThemeWrapper 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 @@ public class Activity extends ContextThemeWrapper 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 @@ public class Activity extends ContextThemeWrapper 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 c027e990ec86..018e94914110 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -76,6 +76,13 @@ public class ActivityManager { 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 10831f226348..b1c37de8ca2b 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -43,9 +43,11 @@ import android.os.Parcelable; 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 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM 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 @@ class ActivityManagerProxy implements IActivityManager 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 88eae7f2638c..7dc21b4a77a7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -94,6 +94,7 @@ import android.view.WindowManagerGlobal; 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 @@ public final class ActivityThread { IBinder token; int ident; Intent intent; + IVoiceInteractor voiceInteractor; Bundle state; Activity activity; Window window; @@ -603,6 +605,7 @@ public final class ActivityThread { // 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 @@ public final class ActivityThread { 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 @@ public final class ActivityThread { + 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 @@ public final class ActivityThread { 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 f1c632e4e727..fcc7f8e7f78f 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -33,6 +33,7 @@ import android.os.RemoteException; 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 @@ public abstract class ApplicationThreadNative extends Binder 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 @@ public abstract class ApplicationThreadNative extends Binder ? 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 @@ class ApplicationThreadProxy implements IApplicationThread { 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 @@ class ApplicationThreadProxy implements IApplicationThread { 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 52003f111a94..6b94c4e1b5ba 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -47,6 +47,8 @@ import android.os.ParcelFileDescriptor; 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 @@ public interface IActivityManager extends IInterface { 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 @@ public interface IActivityManager extends IInterface { 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 ac8ac8fe827f..f290e94d2c73 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -31,6 +31,8 @@ import android.os.ParcelFileDescriptor; 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 @@ public interface IApplicationThread extends IInterface { 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 8681f5c85b12..045fab18fbae 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -22,6 +22,8 @@ import android.service.notification.StatusBarNotification; 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 @@ interface INotificationManager 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 028fa6816b96..e58ccb81d479 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1428,7 +1428,7 @@ public class Instrumentation { } /** - * 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 @@ public class Instrumentation { } /** - * 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 @@ public class Instrumentation { } /** - * 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 @@ public class Instrumentation { 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 @@ public class Instrumentation { 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 000000000000..6820dfd3b2ea --- /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/content/Context.java b/core/java/android/content/Context.java index cbb6cf5028c2..de223a3a1c86 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2442,6 +2442,14 @@ public abstract class Context { 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 c0f04aff2a81..ae5437bd1237 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2804,6 +2804,14 @@ public class Intent implements Parcelable, Cloneable { @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 ae0899f33bd0..488e25fb0dee 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -74,6 +74,9 @@ interface IPackageManager { 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 d981cc1a103a..484a2a1367b3 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1235,7 +1235,6 @@ public abstract class PackageManager { */ @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 abstract class PackageManager { 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 8b6ca834ddfd..8f19f013c9ae 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.EncodedKeySpec; 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 @@ public class PackageParser { 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 @@ public class PackageParser { 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 @@ public class PackageParser { } 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 19be2c83cfb5..11e379523bc2 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -118,14 +118,22 @@ public class Build { */ public static final String CODENAME = getString("ro.build.version.codename"); + private static final String[] ALL_CODENAMES + = getString("ro.build.version.all_codenames").split(","); + + /** + * @hide + */ + 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. If we are a development build, - * also allow the previous SDK version + 1. + * 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 - + ("REL".equals(CODENAME) ? 0 : 1); + 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 f671ed955659..cdde4c723da0 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -375,6 +375,7 @@ public class RecoverySystem { 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 59b918bd45f4..1fe9337e7518 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -488,7 +488,8 @@ public class UserManager { 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; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7f9f862aa136..b578b48692be 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -723,6 +723,13 @@ public final class Settings { = "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 @@ public final class Settings { "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 final class Settings { */ 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 000000000000..432852cfcbae --- /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 000000000000..cfd40f3880fd --- /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 000000000000..8777e500b719 --- /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 000000000000..cb582daa261e --- /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 000000000000..e9e2f4c81aa7 --- /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 000000000000..7dbf66b003f5 --- /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 000000000000..ed93b74193cf --- /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 000000000000..59544bee450c --- /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/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index f0d8a6167a1e..34b85d90ae53 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -1117,11 +1117,11 @@ class GLES20Canvas extends HardwareCanvas { } @Override - public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { + public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, + Paint paint) { int modifiers = setupModifiers(paint, MODIFIER_SHADER); try { - nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, - rx, ry, paint.mNativePaint); + nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 84d1328cfe6e..c8e229a5d08a 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2375,24 +2375,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } 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 { + mOutline.markInvalid(); + } + if (mBackground.getOutline(mOutline)) { + if (!mOutline.isValid()) { + throw new IllegalStateException("Background drawable failed to build outline"); + } + mRenderNode.setOutline(mOutline); } else { - mPrivateFlags3 &= ~PFLAG3_CLIP_TO_OUTLINE; + mRenderNode.setOutline(null); } - mRenderNode.setClipToOutline(clipToOutline); } } @@ -14893,11 +14869,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 000000000000..89517863d212 --- /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 d2e7324f7518..c914e52849fd 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -485,7 +485,7 @@ public class WebView extends AbsoluteLayout * @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 @@ public class WebView extends AbsoluteLayout } /** + * 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 e8974c6cda33..688c2518bf67 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -22,6 +22,8 @@ import android.os.Message; import android.view.KeyEvent; import android.view.ViewRootImpl; +import java.security.Principal; + public class WebViewClient { /** @@ -205,6 +207,30 @@ public class WebViewClient { } /** + * 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 5081ff578b4a..efa5497e2104 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -198,6 +198,8 @@ public interface WebViewProvider { 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 000000000000..e3c072852943 --- /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 000000000000..737906a112a1 --- /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 000000000000..f5392e9650cb --- /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 000000000000..ce2902da2eeb --- /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 d9e3ef661cce..40834babc422 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -85,7 +85,27 @@ public class HandlerCaller { 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 @@ public class HandlerCaller { 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 @@ public class HandlerCaller { 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 6fb72f136399..7edf4cc0aa18 100644 --- a/core/java/com/android/internal/os/SomeArgs.java +++ b/core/java/com/android/internal/os/SomeArgs.java @@ -35,6 +35,11 @@ public final class SomeArgs { 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 @@ public final class SomeArgs { 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 e37456364530..bf36bb12fb82 100644 --- a/core/java/com/android/server/SystemService.java +++ b/core/java/com/android/server/SystemService.java @@ -123,6 +123,38 @@ public abstract class SystemService { 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 eb8df0e3ad17..87a50a9940af 100644 --- a/core/java/com/android/server/SystemServiceManager.java +++ b/core/java/com/android/server/SystemServiceManager.java @@ -131,6 +131,58 @@ public class SystemServiceManager { } } + 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/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 514826607125..2adbf3a9ef4e 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -509,12 +509,11 @@ public: } static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle, - jobject jrect, jfloat rx, jfloat ry, - jlong paintHandle) { + jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry, + jlong paintHandle) { SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); - SkRect rect; - GraphicsJNI::jrectf_to_rect(env, jrect, &rect); + SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); canvas->drawRoundRect(rect, rx, ry, *paint); } @@ -1176,7 +1175,7 @@ static JNINativeMethod gCanvasMethods[] = { {"native_drawCircle","(JFFFJ)V", (void*) SkCanvasGlue::drawCircle}, {"native_drawArc","(JLandroid/graphics/RectF;FFZJ)V", (void*) SkCanvasGlue::drawArc}, - {"native_drawRoundRect","(JLandroid/graphics/RectF;FFJ)V", + {"native_drawRoundRect","(JFFFFFFJ)V", (void*) SkCanvasGlue::drawRoundRect}, {"native_drawPath","(JJJ)V", (void*) SkCanvasGlue::drawPath}, {"native_drawBitmap","(JJFFJIII)V", diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index a4efed7e65e3..01f4d3a784e6 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 @@ jint android_os_Process_getGidForName(JNIEnv* env, jobject clazz, jstring name) 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_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint 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 @@ void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz, } } - //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 @@ jint android_os_Process_getThreadPriority(JNIEnv* env, jobject clazz, 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 @@ jboolean android_os_Process_setSwappiness(JNIEnv *env, jobject clazz, 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 @@ jint android_os_Process_setGid(JNIEnv* env, jobject clazz, jint uid) 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 @@ void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileSt return; } - //ALOGI("Clearing %d sizes", count); + //ALOGI("Clearing %" PRId32 " sizes", count); for (i=0; i<count; i++) { sizesArray[i] = 0; } @@ -556,7 +557,7 @@ void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileSt } 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 @@ jboolean android_os_Process_parseProcLineArray(JNIEnv* env, jobject clazz, } } - //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_setApplicationObject(JNIEnv* env, jobject clazz, 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 @@ static jlong android_os_Process_getPss(JNIEnv* env, jobject clazz, jint pid) { 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 @@ static jlong android_os_Process_getPss(JNIEnv* env, jobject clazz, jint pid) 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 8141a00c5a35..c293c7af56d4 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -508,7 +508,7 @@ static jboolean nativeGetAnimationFrameStats(JNIEnv* env, jclass clazz, jobject 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 57e845f2df9e..4f093a874598 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 508a557410af..326485d8cc45 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 6d4ceefdec24..cb52db291e84 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 bb0d18464dfd..431dab83c0f3 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 4903852de9f1..f09ff9e4f68c 100644 --- a/docs/html/guide/topics/data/backup.jd +++ b/docs/html/guide/topics/data/backup.jd @@ -899,8 +899,9 @@ Play built in.</li> {@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/Canvas.java b/graphics/java/android/graphics/Canvas.java index f80ce28e1dab..ae3eae17370f 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1075,11 +1075,20 @@ public class Canvas { * @param paint The paint used to draw the roundRect */ public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { - if (rect == null) { - throw new NullPointerException(); - } - native_drawRoundRect(mNativeCanvas, rect, rx, ry, - paint.mNativePaint); + drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint); + } + + /** + * Draw the specified round-rect using the specified paint. The roundrect + * will be filled or framed based on the Style in the paint. + * + * @param rx The x-radius of the oval used to round the corners + * @param ry The y-radius of the oval used to round the corners + * @param paint The paint used to draw the roundRect + */ + public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, + Paint paint) { + native_drawRoundRect(mNativeCanvas, left, top, right, bottom, rx, ry, paint.mNativePaint); } /** @@ -1816,8 +1825,8 @@ public class Canvas { boolean useCenter, long nativePaint); private static native void native_drawRoundRect(long nativeCanvas, - RectF rect, float rx, - float ry, long nativePaint); + float left, float top, float right, float bottom, + float rx, float ry, long nativePaint); private static native void native_drawPath(long nativeCanvas, long nativePath, long nativePaint); diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index 4ea7ec72bf70..d3aac79d58ca 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,19 +45,26 @@ public class Outline { public Outline() {} /** - * Returns whether the Outline is valid for use with a View. - * <p> - * Outlines are invalid when constructed until a setter method is called. + * Constructs an Outline with a copy of the data in src. */ - public final boolean isValid() { - return mRect != null || mPath != null; + public Outline(Outline src) { + set(src); + } + + /** @hide */ + public void markInvalid() { + mRadius = 0; + mRect = null; + mPath = null; } /** - * @hide + * 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 canClip() { - return mPath == null; + public boolean isValid() { + return mRect != null || mPath != null; } /** @@ -81,9 +89,20 @@ public class Outline { /** * 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 @@ public class Outline { } /** + * 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 74d1219c26b0..b9d5e19a5556 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -864,22 +864,21 @@ public abstract class Drawable { } /** - * 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. + * + * 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.view.Outline) - * @see View#setClipToOutline(boolean) + * @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 1b5cefe7d7d8..708c8b06b6be 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -139,8 +139,7 @@ public class GradientDrawable extends Drawable { 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 @@ public class GradientDrawable extends Drawable { 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 @@ public class GradientDrawable extends Drawable { } } } - + + 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 @@ public class GradientDrawable extends Drawable { } @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 000000000000..b2a38abcbe31 --- /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 72f3e1a4dd0e..6424a9875405 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -1281,6 +1281,14 @@ public class MediaScanner 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 @@ public class MediaScanner 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 @@ public class MediaScanner } catch (RemoteException e) { Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e); return null; + } finally { + releaseResources(); } } @@ -1511,6 +1523,7 @@ public class MediaScanner 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 2e4dbdf7945b..ede23efcf03f 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.Looper; 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 e1c17cb4f4c8..56f5a3a80099 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/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 88b01c7525e7..809adcd2ff23 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -31,4 +31,14 @@ android:scaleType="center" android:contentDescription="@string/accessibility_camera_button" systemui:glowBackground="@drawable/ic_sysbar_highlight_land" /> + + <com.android.systemui.statusbar.phone.KeyguardIndicationTextView + android:id="@+id/keyguard_indication_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="100dp" + android:layout_gravity="bottom|center_horizontal" + android:textStyle="italic" + android:textAppearance="?android:attr/textAppearanceMedium"/> + </com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f3c956ce70e5..b4a13d40fbcd 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -546,4 +546,10 @@ <plurals name="keyguard_more_overflow_text"> <item quantity="other">%d more</item> </plurals> + + <!-- Shows to explain the double tap interaction with notifications: After tapping a notification on Keyguard, this will explain users to tap again to launch a notification. [CHAR LIMIT=60] --> + <string name="notification_tap_again">Tap again to open</string> + + <!-- Shows when people have pressed the unlock icon to explain how to unlock. [CHAR LIMIT=60] --> + <string name="keyguard_unlock">Swipe up to unlock</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java index 21c292668e88..ae18aa8ea30f 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 @@ public class Recents extends SystemUI implements RecentsComponent { @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/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java new file mode 100644 index 000000000000..769b1b1f1f05 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -0,0 +1,62 @@ +/* + * 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.systemui.statusbar.phone; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +/** + * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). + */ +public class KeyguardIndicationTextView extends TextView { + + public KeyguardIndicationTextView(Context context) { + super(context); + } + + public KeyguardIndicationTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public KeyguardIndicationTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public KeyguardIndicationTextView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * Changes the text with an animation and makes sure a single indication is shown long enough. + * + * @param text The text to show. + */ + public void switchIndication(CharSequence text) { + + // TODO: Animation, make sure that we will show one indication long enough. + setText(text); + } + + /** + * See {@link #switchIndication}. + */ + public void switchIndication(int textResId) { + switchIndication(getResources().getText(textResId)); + } +} 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 7f1ddafa4b9f..81e6b75358c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -232,6 +232,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { View mNotificationPanelHeader; View mKeyguardStatusView; View mKeyguardBottomArea; + KeyguardIndicationTextView mKeyguardIndicationTextView; + + // TODO: Fetch phrase from search/hotword provider. + String mKeyguardHotwordPhrase = ""; int mKeyguardMaxNotificationCount; View mDateTimeView; View mClearButton; @@ -618,7 +622,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { mNotificationPanelHeader = mStatusBarWindow.findViewById(R.id.header); mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view); mKeyguardBottomArea = mStatusBarWindow.findViewById(R.id.keyguard_bottom_area); - + mKeyguardIndicationTextView = (KeyguardIndicationTextView) mStatusBarWindow.findViewById( + R.id.keyguard_indication_text); mClearButton = mStatusBarWindow.findViewById(R.id.clear_all_button); mClearButton.setOnClickListener(mClearButtonListener); mClearButton.setAlpha(0f); @@ -2942,6 +2947,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } mKeyguardStatusView.setVisibility(View.VISIBLE); mKeyguardBottomArea.setVisibility(View.VISIBLE); + mKeyguardIndicationTextView.setVisibility(View.VISIBLE); + mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); mNotificationPanelHeader.setVisibility(View.GONE); mKeyguardFlipper.setVisibility(View.VISIBLE); @@ -2949,6 +2956,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } else { mKeyguardStatusView.setVisibility(View.GONE); mKeyguardBottomArea.setVisibility(View.GONE); + mKeyguardIndicationTextView.setVisibility(View.GONE); mNotificationPanelHeader.setVisibility(View.VISIBLE); mKeyguardFlipper.setVisibility(View.GONE); @@ -2987,14 +2995,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } 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() { @@ -3004,10 +3020,29 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { @Override public void onActivated(View view) { userActivity(); + mKeyguardIndicationTextView.switchIndication(R.string.notification_tap_again); super.onActivated(view); } @Override + public void onReset(View view) { + super.onReset(view); + mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); + } + + public void onTrackingStarted() { + if (mOnKeyguard) { + mKeyguardIndicationTextView.switchIndication(R.string.keyguard_unlock); + } + } + + public void onTrackingStopped() { + if (mOnKeyguard) { + mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); + } + } + + @Override protected int getMaxKeyguardNotifications() { return mKeyguardMaxNotificationCount; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 6ba18b35faa1..bf7dd5c67c1f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -160,6 +160,18 @@ public class PhoneStatusBarView extends PanelBar { } @Override + public void onTrackingStarted(PanelView panel) { + super.onTrackingStarted(panel); + mBar.onTrackingStarted(); + } + + @Override + public void onTrackingStopped(PanelView panel) { + super.onTrackingStopped(panel); + mBar.onTrackingStopped(); + } + + @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mBar.interceptTouchEvent(event) || super.onInterceptTouchEvent(event); } diff --git a/services/Android.mk b/services/Android.mk index 165f456bf7a2..5fcef64cb963 100644 --- a/services/Android.mk +++ b/services/Android.mk @@ -25,7 +25,8 @@ services := \ 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 2f56e62cbcf8..87b1d3203c7c 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.SystemClock; 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.Locale; 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 ed007e94dced..75bf5df2e624 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.END_DOCUMENT; 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.IntentResolver; 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 @@ public final class ActivityManagerService extends ActivityManagerNative // 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 @@ public final class ActivityManagerService extends ActivityManagerNative /** * 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 @@ public final class ActivityManagerService extends ActivityManagerNative * 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } 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 @@ public final class ActivityManagerService extends ActivityManagerNative Watchdog.getInstance().addThread(mHandler); } + public void setSystemServiceManager(SystemServiceManager mgr) { + mSystemServiceManager = mgr; + } + private void start() { mProcessCpuThread.start(); @@ -2254,6 +2281,11 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } } + 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } @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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative // 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } private void comeOutOfSleepIfNeededLocked() { - if (!mWentToSleep && !mLockScreenShown) { + if ((!mWentToSleep && !mLockScreenShown) || mRunningVoice) { if (mSleeping) { mSleeping = false; mStackSupervisor.comeOutOfSleepIfNeededLocked(); @@ -8592,6 +8675,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } + void startRunningVoiceLocked() { + if (!mRunningVoice) { + mRunningVoice = true; + comeOutOfSleepIfNeededLocked(); + } + } + private void updateEventDispatchingLocked() { mWindowManager.setEventDispatching(mBooted && !mWentToSleep && !mShuttingDown); } @@ -9298,7 +9388,7 @@ public final class ActivityManagerService extends ActivityManagerNative proc.notCachedSinceIdle = true; proc.initialIdlePss = 0; proc.nextPssTime = ProcessList.computeNextPssTime(proc.curProcState, true, - mSleeping, now); + isSleeping(), now); } } @@ -9597,6 +9687,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (goingCallback != null) goingCallback.run(); + mSystemServiceManager.startUser(mCurrentUserId); + synchronized (this) { if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) { try { @@ -9653,6 +9745,8 @@ public final class ActivityManagerService extends ActivityManagerNative }, 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } } return !processingBroadcasts - && (mSleeping || mStackSupervisor.allResumedActivitiesIdle()); + && (isSleeping() || mStackSupervisor.allResumedActivitiesIdle()); } /** @@ -15585,7 +15679,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative && 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 @@ public final class ActivityManagerService extends ActivityManagerNative } 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } 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 @@ public final class ActivityManagerService extends ActivityManagerNative } } - 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 3e59def20274..7a4447370881 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 @@ final class ActivityRecord { // 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 9f577283e08e..bea926fbbd8a 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.HOME_STACK_ID; 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { // 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 @@ final class ActivityStack { + " 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { } } - 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 e044d3f13841..274e011e6804 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.Process; 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.DisplayInfo; 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { } 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 @@ public final class ActivityStackSupervisor implements DisplayListener { } 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { } 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { } 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { && "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 443218cb818c..d42d41535411 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 @@ final class NativeCrashListener extends Thread { 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 @@ final class NativeCrashListener extends Thread { } 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 @@ final class NativeCrashListener extends Thread { // 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 @@ final class NativeCrashListener extends Thread { } } 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 @@ final class NativeCrashListener extends Thread { 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 @@ final class NativeCrashListener extends Thread { 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 @@ final class NativeCrashListener extends Thread { 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 80a219dd4baa..68da54dcf4b2 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.Intent; 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 @@ import java.util.ArrayList; 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 @@ final class TaskRecord extends ThumbnailHolder { * 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 @@ final class TaskRecord extends ThumbnailHolder { 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 000000000000..5f07108bc125 --- /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 000000000000..56c5b498d026 --- /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 000000000000..a28227082e52 --- /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/NotificationListeners.java b/services/core/java/com/android/server/notification/ManagedServices.java index 91d2f98ab0a8..81b28e807464 100644 --- a/services/core/java/com/android/server/notification/NotificationListeners.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import android.app.ActivityManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.ContentResolver; @@ -24,191 +25,174 @@ 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.PackageManager.NameNotFoundException; +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.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; +/** + * 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_NOTIFICATION_LISTENERS_SEPARATOR = ":"; + private static final String ENABLED_SERVICES_SEPARATOR = ":"; private final Context mContext; - private final Handler mHandler; private final Object mMutex; private final UserProfiles mUserProfiles; private final SettingsObserver mSettingsObserver; + private final Config mConfig; - // 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 + // 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) listener + // lists the component names of all enabled (and therefore connected) // app services for current profiles. - private ArraySet<ComponentName> mEnabledListenersForCurrentProfiles + private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles = new ArraySet<ComponentName>(); - // Just the packages from mEnabledListenersForCurrentProfiles - private ArraySet<String> mEnabledListenerPackageNames = new ArraySet<String>(); + // Just the packages from mEnabledServicesForCurrentProfiles + private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>(); - public NotificationListeners(Context context, Handler handler, Object mutex, + public ManagedServices(Context context, Handler handler, Object mutex, UserProfiles userProfiles) { mContext = context; - mHandler = handler; mMutex = mutex; mUserProfiles = userProfiles; - mSettingsObserver = new SettingsObserver(mHandler); + mConfig = getConfig(); + mSettingsObserver = new SettingsObserver(handler); } - public void onBootPhaseAppsCanStart() { - mSettingsObserver.observe(); + abstract protected Config getConfig(); + + private String getCaption() { + return mConfig.caption; } - protected void onServiceAdded(INotificationListener mListener) { - // for subclasses + 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(" Listeners (" + mEnabledListenersForCurrentProfiles.size() + pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size() + ") enabled for current profiles:"); - for (ComponentName cmpt : mEnabledListenersForCurrentProfiles) { + for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { pw.println(" " + cmpt); } - pw.println(" Live listeners (" + mListeners.size() + "):"); - for (NotificationListenerInfo info : mListeners) { + pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); + for (ManagedServiceInfo info : mServices) { pw.println(" " + info.component - + " (user " + info.userid + "): " + info.listener + + " (user " + info.userid + "): " + info.service + (info.isSystem?" SYSTEM":"")); } } public void onPackagesChanged(boolean queryReplace, String[] pkgList) { - boolean anyListenersInvolved = false; + boolean anyServicesInvolved = false; if (pkgList != null && (pkgList.length > 0)) { for (String pkgName : pkgList) { - if (mEnabledListenerPackageNames.contains(pkgName)) { - anyListenersInvolved = true; + if (mEnabledServicesPackageNames.contains(pkgName)) { + anyServicesInvolved = true; } } } - if (anyListenersInvolved) { + if (anyServicesInvolved) { // if we're not replacing a package, clean up orphaned bits if (!queryReplace) { - disableNonexistentListeners(); + disableNonexistentServices(); } - // make sure we're still bound to any of our - // listeners who may have just upgraded - rebindListenerServices(); + // make sure we're still bound to any of our services who may have just upgraded + rebindServices(); } } - /** - * 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(); + 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 NotificationListenerInfo info = mListeners.get(i); - if (info.listener.asBinder() == token) return info; + final ManagedServiceInfo info = mServices.get(i); + if (info.service.asBinder() == token) return info; } - throw new SecurityException("Disallowed call from unknown listener: " + listener); + throw new SecurityException("Disallowed call from unknown " + getCaption() + ": " + + service); } - public void unregisterListener(INotificationListener listener, int userid) { - checkNullListener(listener); - // no need to check permissions; if your listener binder is in the list, + 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 - unregisterListenerImpl(listener, userid); + unregisterServiceImpl(service, userid); } - public void registerListener(INotificationListener listener, - ComponentName component, int userid) { - checkNullListener(listener); - registerListenerImpl(listener, component, userid); + public void registerService(IInterface service, ComponentName component, int userid) { + checkNotNull(service); + registerServiceImpl(service, component, userid); } /** - * Remove notification access for any services that no longer exist. + * Remove access for any services that no longer exist. */ - private void disableNonexistentListeners() { + private void disableNonexistentServices() { int[] userIds = mUserProfiles.getCurrentProfileIds(); final int N = userIds.length; for (int i = 0 ; i < N; ++i) { - disableNonexistentListeners(userIds[i]); + disableNonexistentServices(userIds[i]); } } - private void disableNonexistentListeners(int userId) { + private void disableNonexistentServices(int userId) { String flatIn = Settings.Secure.getStringForUser( mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + mConfig.secureSettingName, userId); if (!TextUtils.isEmpty(flatIn)) { - if (DBG) Slog.v(TAG, "flat before: " + flatIn); + if (DEBUG) Slog.v(TAG, "flat before: " + flatIn); PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( - new Intent(NotificationListenerService.SERVICE_INTERFACE), + new Intent(mConfig.serviceInterface), PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId); @@ -217,12 +201,11 @@ public class NotificationListeners { 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 " + if (!mConfig.bindPermission.equals(info.permission)) { + Slog.w(TAG, "Skipping " + getCaption() + " service " + info.packageName + "/" + info.name + ": it does not require the permission " - + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE); + + mConfig.bindPermission); continue; } installed.add(new ComponentName(info.packageName, info.name)); @@ -230,7 +213,7 @@ public class NotificationListeners { String flatOut = ""; if (!installed.isEmpty()) { - String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); + 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]); @@ -238,22 +221,23 @@ public class NotificationListeners { remaining.add(enabled[i]); } } - flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining); + flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining); } - if (DBG) Slog.v(TAG, "flat after: " + flatOut); + if (DEBUG) Slog.v(TAG, "flat after: " + flatOut); if (!flatIn.equals(flatOut)) { Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + mConfig.secureSettingName, flatOut, userId); } } } /** - * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS + * 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 rebindListenerServices() { + private void rebindServices() { + if (DEBUG) Slog.d(TAG, "rebindServices"); final int[] userIds = mUserProfiles.getCurrentProfileIds(); final int nUserIds = userIds.length; @@ -262,17 +246,17 @@ public class NotificationListeners { for (int i = 0; i < nUserIds; ++i) { flat.put(userIds[i], Settings.Secure.getStringForUser( mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + mConfig.secureSettingName, userIds[i])); } - NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()]; + ManagedServiceInfo[] toRemove = new ManagedServiceInfo[mServices.size()]; final SparseArray<ArrayList<ComponentName>> toAdd = new SparseArray<ArrayList<ComponentName>>(); synchronized (mMutex) { - // unbind and remove all existing listeners - toRemove = mListeners.toArray(toRemove); + // unbind and remove all existing services + toRemove = mServices.toArray(toRemove); final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>(); final ArraySet<String> newPackages = new ArraySet<String>(); @@ -284,7 +268,7 @@ public class NotificationListeners { // decode the list of components String toDecode = flat.get(userIds[i]); if (toDecode != null) { - String[] components = toDecode.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); + String[] components = toDecode.split(ENABLED_SERVICES_SEPARATOR); for (int j = 0; j < components.length; j++) { final ComponentName component = ComponentName.unflattenFromString(components[j]); @@ -297,16 +281,16 @@ public class NotificationListeners { } } - mEnabledListenersForCurrentProfiles = newEnabled; - mEnabledListenerPackageNames = newPackages; + mEnabledServicesForCurrentProfiles = newEnabled; + mEnabledServicesPackageNames = newPackages; } - for (NotificationListenerInfo info : toRemove) { + for (ManagedServiceInfo info : toRemove) { final ComponentName component = info.component; final int oldUser = info.userid; - Slog.v(TAG, "disabling notification listener for user " + Slog.v(TAG, "disabling " + getCaption() + " for user " + oldUser + ": " + component); - unregisterListenerService(component, info.userid); + unregisterService(component, info.userid); } for (int i = 0; i < nUserIds; ++i) { @@ -314,23 +298,18 @@ public class NotificationListeners { 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] + ": " + Slog.v(TAG, "enabling " + getCaption() + " for user " + userIds[i] + ": " + component); - registerListenerService(component, userIds[i]); + registerService(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. + * Version of registerService that takes the name of a service component to bind to. */ - private void registerListenerService(final ComponentName name, final int userid) { - NotificationUtil.checkCallerIsSystem(); - - if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid); + 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; @@ -340,28 +319,28 @@ public class NotificationListeners { } mServicesBinding.add(servicesBindingTag); - final int N = mListeners.size(); + final int N = mServices.size(); for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); + final ManagedServiceInfo info = mServices.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 (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": " + + info.service); + mServices.remove(i); if (info.connection != null) { mContext.unbindService(info.connection); } } } - Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE); + Intent intent = new Intent(mConfig.serviceInterface); intent.setComponent(name); - intent.putExtra(Intent.EXTRA_CLIENT_LABEL, - R.string.notification_listener_binding_label); + intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel); final PendingIntent pendingIntent = PendingIntent.getActivity( - mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0); + mContext, 0, new Intent(mConfig.settingsAction), 0); intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent); ApplicationInfo appInfo = null; @@ -375,72 +354,68 @@ public class NotificationListeners { appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE; try { - if (DBG) Slog.v(TAG, "binding: " + intent); + if (DEBUG) Slog.v(TAG, "binding: " + intent); if (!mContext.bindServiceAsUser(intent, new ServiceConnection() { - INotificationListener mListener; + IInterface mService; @Override - public void onServiceConnected(ComponentName name, IBinder service) { + public void onServiceConnected(ComponentName name, IBinder binder) { 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); + 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(mListener); + onServiceAdded(mService); } } @Override public void onServiceDisconnected(ComponentName name) { - Slog.v(TAG, "notification listener connection lost: " + name); + Slog.v(TAG, getCaption() + " connection lost: " + name); } }, Context.BIND_AUTO_CREATE, new UserHandle(userid))) { mServicesBinding.remove(servicesBindingTag); - Slog.w(TAG, "Unable to bind listener service: " + intent); + Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent); return; } } catch (SecurityException ex) { - Slog.e(TAG, "Unable to bind listener service: " + intent, ex); + Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex); return; } } } /** - * Remove a listener service for the given user by ComponentName + * Remove a service for the given user by ComponentName */ - private void unregisterListenerService(ComponentName name, int userid) { - NotificationUtil.checkCallerIsSystem(); - + private void unregisterService(ComponentName name, int userid) { synchronized (mMutex) { - final int N = mListeners.size(); + final int N = mServices.size(); for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); + final ManagedServiceInfo info = mServices.get(i); if (name.equals(info.component) && info.userid == userid) { - mListeners.remove(i); + 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, "Listener " + name + " could not be unbound: " + ex); + Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex); } } } @@ -449,41 +424,39 @@ public class NotificationListeners { } /** - * Removes a listener from the list but does not unbind from the listener's service. + * Removes a service from the list but does not unbind * - * @return the removed listener. + * @return the removed service. */ - private NotificationListenerInfo removeListenerImpl( - final INotificationListener listener, final int userid) { - NotificationListenerInfo listenerInfo = null; + private ManagedServiceInfo removeServiceImpl(IInterface service, final int userid) { + ManagedServiceInfo serviceInfo = null; synchronized (mMutex) { - final int N = mListeners.size(); + final int N = mServices.size(); for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (info.listener.asBinder() == listener.asBinder() + final ManagedServiceInfo info = mServices.get(i); + if (info.service.asBinder() == service.asBinder() && info.userid == userid) { - listenerInfo = mListeners.remove(i); + serviceInfo = mServices.remove(i); } } } - return listenerInfo; + return serviceInfo; } - private void checkNullListener(INotificationListener listener) { - if (listener == null) { - throw new IllegalArgumentException("Listener must not be null"); + private void checkNotNull(IInterface service) { + if (service == null) { + throw new IllegalArgumentException(getCaption() + " must not be null"); } } - private void registerListenerImpl(final INotificationListener listener, + private void registerServiceImpl(final IInterface service, 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); + 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 } @@ -491,18 +464,17 @@ public class NotificationListeners { } /** - * Removes a listener from the list and unbinds from its service. + * Removes a service from the list and unbinds. */ - private void unregisterListenerImpl(final INotificationListener listener, final int userid) { - NotificationListenerInfo info = removeListenerImpl(listener, userid); + 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 ENABLED_NOTIFICATION_LISTENERS_URI - = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName); private SettingsObserver(Handler handler) { super(handler); @@ -510,7 +482,7 @@ public class NotificationListeners { private void observe() { ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, + resolver.registerContentObserver(mSecureSettingsUri, false, this, UserHandle.USER_ALL); update(null); } @@ -521,42 +493,31 @@ public class NotificationListeners { } private void update(Uri uri) { - if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { - rebindListenerServices(); + if (uri == null || mSecureSettingsUri.equals(uri)) { + rebindServices(); } } } - public class NotificationListenerInfo implements IBinder.DeathRecipient { - public INotificationListener listener; + 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 NotificationListenerInfo(INotificationListener listener, ComponentName component, - int userid, boolean isSystem, int targetSdkVersion) { - this.listener = listener; + 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 = 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(); + public boolean enabledAndUserMatches(int nid) { if (!isEnabledForCurrentProfiles()) { return false; } @@ -569,40 +530,65 @@ public class NotificationListeners { 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 + // 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 - // INotificationListener, just removing it from the list is all we need to do anyway. - removeListenerImpl(this.listener, this.userid); + // service, just removing it from the list is all we need to do anyway. + removeServiceImpl(this.service, this.userid); } - /** convenience method for looking in mEnabledListenersForCurrentProfiles */ + /** convenience method for looking in mEnabledServicesForCurrentProfiles */ public boolean isEnabledForCurrentProfiles() { if (this.isSystem) return true; if (this.connection == null) return false; - return mEnabledListenersForCurrentProfiles.contains(this.component); + 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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5f096cb2ae27..5a1f9b2d9506 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 static org.xmlpull.v1.XmlPullParser.START_TAG; 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.ContentResolver; 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.Binder; 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.ArrayMap; 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.EventLogTags; 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.ArrayList; 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } } 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { // bind to listener services. mSettingsObserver.observe(); mListeners.onBootPhaseAppsCanStart(); + mConditionProviders.onBootPhaseAppsCanStart(); } } @@ -1005,8 +1002,7 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { @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 @@ public class NotificationManagerService extends SystemService { @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 @@ public class NotificationManagerService extends SystemService { @Override public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) { - NotificationUtil.checkCallerIsSystem(); + checkCallerIsSystem(); setNotificationsEnabledForPackageImpl(pkg, uid, enabled); } @@ -1133,7 +1129,7 @@ public class NotificationManagerService extends SystemService { */ @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 @@ public class NotificationManagerService extends SystemService { @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 @@ public class NotificationManagerService extends SystemService { */ @Override public void unregisterListener(INotificationListener listener, int userid) { - mListeners.unregisterListener(listener, userid); + mListeners.unregisterService(listener, userid); } /** @@ -1227,8 +1223,7 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } } - 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 @@ public class NotificationManagerService extends SystemService { 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 class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { @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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { pw.println("Current Notification Manager state:"); mListeners.dump(pw); + mConditionProviders.dump(pw); int N; @@ -1455,9 +1456,8 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { */ 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 @@ public class NotificationManagerService extends SystemService { } 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 class NotificationManagerService extends SystemService { } } - public static class UserProfiles { - // Profiles of the current user. - private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); - - 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 isUidSystem(int uid) { + final int appid = UserHandle.getAppId(uid); + return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); + } + + 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); + } + }); + } + } + + 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); } } - public boolean isCurrentProfile(int userId) { - synchronized (mCurrentProfiles) { - return mCurrentProfiles.get(userId) != null; + 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 459adce9f312..000000000000 --- 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 80f5b5c1d541..b00beb62bce5 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.net.Uri; 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 @@ public class ZenModeHelper { 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 702d9d263248..ad07084d5b79 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.Environment.UserEnvironment; 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; @@ -199,8 +198,6 @@ public class PackageManagerService extends IPackageManager.Stub { private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID; private static final int SHELL_UID = Process.SHELL_UID; - private static final boolean GET_CERTIFICATES = true; - // Cap the size of permission trees that 3rd party apps can define private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; // characters of text @@ -278,8 +275,6 @@ public class PackageManagerService extends IPackageManager.Stub { 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; @@ -2165,6 +2160,24 @@ public class PackageManagerService extends IPackageManager.Stub { } @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"); @@ -3783,27 +3796,25 @@ public class PackageManagerService extends IPackageManager.Stub { private boolean collectCertificatesLI(PackageParser pp, PackageSetting ps, PackageParser.Package pkg, File srcFile, int parseFlags) { - if (GET_CERTIFICATES) { - if (ps != null - && ps.codePath.equals(srcFile) - && ps.timeStamp == srcFile.lastModified()) { - if (ps.signatures.mSignatures != null - && ps.signatures.mSignatures.length != 0) { - // Optimization: reuse the existing cached certificates - // if the package appears to be unchanged. - pkg.mSignatures = ps.signatures.mSignatures; - return true; - } - - Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them."); - } else { - Log.i(TAG, srcFile.toString() + " changed; collecting certs"); - } - - if (!pp.collectCertificates(pkg, parseFlags)) { - mLastScanError = pp.getParseError(); - return false; + if (ps != null + && ps.codePath.equals(srcFile) + && ps.timeStamp == srcFile.lastModified()) { + if (ps.signatures.mSignatures != null + && ps.signatures.mSignatures.length != 0) { + // Optimization: reuse the existing cached certificates + // if the package appears to be unchanged. + pkg.mSignatures = ps.signatures.mSignatures; + return true; } + + Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them."); + } else { + Log.i(TAG, srcFile.toString() + " changed; collecting certs"); + } + + if (!pp.collectCertificates(pkg, parseFlags)) { + mLastScanError = pp.getParseError(); + return false; } return true; } @@ -4839,7 +4850,6 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString; } } - pkgSetting.uidError = uidError; } @@ -5420,7 +5430,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } - 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(); @@ -5457,12 +5468,12 @@ public class PackageManagerService extends IPackageManager.Stub { 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); @@ -9452,7 +9463,7 @@ public class PackageManagerService extends IPackageManager.Stub { return; } } - if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) { + if (!pp.collectCertificates(pkg, parseFlags)) { res.returnCode = pp.getParseError(); return; } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f17b2f48ce4b..90392369b856 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 @@ public class AppTransition implements Dump { 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 @@ public class AppTransition implements Dump { 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 @@ public class AppTransition implements Dump { 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 b56377c2fa23..637beec52439 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 @@ public class WindowManagerService extends IWindowManager.Stub } 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 1b3887c6585b..51583a5e7452 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -11,7 +11,9 @@ LOCAL_SRC_FILES += \ $(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 000000000000..f3e8f3c26ba0 --- /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 bf9f7f436127..1feb325c0b5e 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -38,7 +38,9 @@ int register_android_server_location_GpsLocationProvider(JNIEnv* env); 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) 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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f08d69f3b825..00d5468c84b9 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.clipboard.ClipboardService; 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 @@ public final class SystemServer { "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 @@ public final class SystemServer { // Activity manager runs the show. mActivityManagerService = mSystemServiceManager.startService( ActivityManagerService.Lifecycle.class).getService(); + mActivityManagerService.setSystemServiceManager(mSystemServiceManager); } private void startCoreServices() { @@ -826,6 +830,15 @@ public final class SystemServer { } 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 @@ public final class SystemServer { } 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 000000000000..c9e5dd02cbbe --- /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 000000000000..9e2bcab1f1cd --- /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 000000000000..af8ae1e8eaac --- /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/VectorDrawableTest/res/drawable/vector_drawable05.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml index bcf3ae6ceb67..0be67558856d 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,26 +16,25 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> <viewport - android:viewportWidth="7.30625" - android:viewportHeight="12.25"/> + android:viewportHeight="12.25" + android:viewportWidth="7.30625" /> <group> <path - android:name="one" - android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125 + android:name="one" + android:fill="#ffff00" + android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125 l 2.09375,-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0 - l -5.046875,0.0 0.0,-1.0Z" - android:fill="#ffff00" - /> - - + l -5.046875,0.0 0.0,-1.0Z" /> <path - android:name="two" - android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375 + android:name="two" + android:fill="#ffff00" + android:fillOpacity="0" + android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375 q 1.1718752,-1.1875 1.4687502,-1.53125 0.578125,-0.625 0.796875,-1.0625 q 0.234375,-0.453125 0.234375,-0.875 0.0,-0.703125 -0.5,-1.140625 q -0.484375,-0.4375 -1.2656252,-0.4375 -0.5625,0.0 -1.1875,0.1875 @@ -42,23 +42,20 @@ q 0.625,-0.15625 1.140625,-0.15625 1.3593752,0.0 2.1718752,0.6875 q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625 q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375 - q -0.78125024,0.8125 -2.2187502,2.265625Z" - android:fill="#ffff00" - android:fillOpacity="0" - /> + q -0.78125024,0.8125 -2.2187502,2.265625Z" /> </group> <group> <path - android:name="one" - android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125 + android:name="one" + android:fill="#ffff00" + android:fillOpacity="0" + android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125 l 2.09375,-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0 -l -5.046875,0.0 0.0,-1.0Z" - android:fill="#ffff00" - android:fillOpacity="0" - /> +l -5.046875,0.0 0.0,-1.0Z" /> <path - android:name="two" - android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375 + android:name="two" + android:fill="#ffff00" + android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375 q 1.1718752,-1.1875 1.4687502,-1.53125 0.578125,-0.625 0.796875,-1.0625 q 0.234375,-0.453125 0.234375,-0.875 0.0,-0.703125 -0.5,-1.140625 q -0.484375,-0.4375 -1.2656252,-0.4375 -0.5625,0.0 -1.1875,0.1875 @@ -66,14 +63,13 @@ l -5.046875,0.0 0.0,-1.0Z" q 0.625,-0.15625 1.140625,-0.15625 1.3593752,0.0 2.1718752,0.6875 q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625 q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375 - q -0.78125024,0.8125 -2.2187502,2.265625Z" - android:fill="#ffff00" - /> + q -0.78125024,0.8125 -2.2187502,2.265625Z" /> </group> <group> <path - android:name="two" - android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375 + android:name="two" + android:fill="#ffff00" + android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375 q 1.1718752,-1.1875 1.4687502,-1.53125 0.578125,-0.625 0.796875,-1.0625 q 0.234375,-0.453125 0.234375,-0.875 0.0,-0.703125 -0.5,-1.140625 q -0.484375,-0.4375 -1.2656252,-0.4375 -0.5625,0.0 -1.1875,0.1875 @@ -81,12 +77,12 @@ l -5.046875,0.0 0.0,-1.0Z" q 0.625,-0.15625 1.140625,-0.15625 1.3593752,0.0 2.1718752,0.6875 q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625 q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375 - q -0.78125024,0.8125 -2.2187502,2.265625Z" - android:fill="#ffff00" - /> + q -0.78125024,0.8125 -2.2187502,2.265625Z" /> <path - android:name="three" - android:pathData="M 5.103125,6.003125q 0.84375,0.1875 1.3125,0.765625 0.484375,0.5625 0.484375,1.40625 + android:name="three" + android:fill="#ffff00" + android:fillOpacity="0" + android:pathData="M 5.103125,6.003125q 0.84375,0.1875 1.3125,0.765625 0.484375,0.5625 0.484375,1.40625 q 0.0,1.296875 -0.890625,2.015625 -0.890625,0.703125 -2.53125,0.703125 q -0.546875,0.0 -1.140625,-0.109375 -0.5781251,-0.109375 -1.1875001,-0.328125 l 0.0,-1.140625q 0.484375,0.28125 1.0625001,0.4375 0.59375,0.140625 1.234375,0.140625 @@ -98,15 +94,14 @@ l -5.046875,0.0 0.0,-1.0Z" q -0.546875,0.09375 -1.2187501,0.3125l 0.0,-1.046875q 0.6875001,-0.1875 1.2656251,-0.28125 q 0.59375,-0.09375 1.109375,-0.09375 1.359375,0.0 2.140625,0.609375 q 0.78125,0.609375 0.78125,1.65625 0.0,0.734375 -0.421875,1.234375 - q -0.40625,0.5 -1.171875,0.6875Z" - android:fill="#ffff00" - android:fillOpacity="0" - /> + q -0.40625,0.5 -1.171875,0.6875Z" /> </group> <group> <path - android:name="two" - android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375 + android:name="two" + android:fill="#ffff00" + android:fillOpacity="0" + android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375 q 1.1718752,-1.1875 1.4687502,-1.53125 0.578125,-0.625 0.796875,-1.0625 q 0.234375,-0.453125 0.234375,-0.875 0.0,-0.703125 -0.5,-1.140625 q -0.484375,-0.4375 -1.2656252,-0.4375 -0.5625,0.0 -1.1875,0.1875 @@ -114,13 +109,11 @@ l -5.046875,0.0 0.0,-1.0Z" q 0.625,-0.15625 1.140625,-0.15625 1.3593752,0.0 2.1718752,0.6875 q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625 q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375 - q -0.78125024,0.8125 -2.2187502,2.265625Z" - android:fill="#ffff00" - android:fillOpacity="0" - /> + q -0.78125024,0.8125 -2.2187502,2.265625Z" /> <path - android:name="three" - android:pathData="M 5.103125,6.003125q 0.84375,0.1875 1.3125,0.765625 0.484375,0.5625 0.484375,1.40625 + android:name="three" + android:fill="#ffff00" + android:pathData="M 5.103125,6.003125q 0.84375,0.1875 1.3125,0.765625 0.484375,0.5625 0.484375,1.40625 q 0.0,1.296875 -0.890625,2.015625 -0.890625,0.703125 -2.53125,0.703125 q -0.546875,0.0 -1.140625,-0.109375 -0.5781251,-0.109375 -1.1875001,-0.328125 l 0.0,-1.140625q 0.484375,0.28125 1.0625001,0.4375 0.59375,0.140625 1.234375,0.140625 @@ -132,16 +125,14 @@ l -5.046875,0.0 0.0,-1.0Z" q -0.546875,0.09375 -1.2187501,0.3125l 0.0,-1.046875q 0.6875001,-0.1875 1.2656251,-0.28125 q 0.59375,-0.09375 1.109375,-0.09375 1.359375,0.0 2.140625,0.609375 q 0.78125,0.609375 0.78125,1.65625 0.0,0.734375 -0.421875,1.234375 - q -0.40625,0.5 -1.171875,0.6875Z" - android:fill="#ffff00" - /> + q -0.40625,0.5 -1.171875,0.6875Z" /> </group> - <animation - android:sequence="one,one,three,three" - android:durations="2000,0,2000"/> + android:durations="2000,0,2000" + android:sequence="one,one,three,three" /> <animation - android:sequence="two,two,two,two" - android:durations="2000,0,2000"/> -</vector> + android:durations="2000,0,2000" + android:sequence="two,two,two,two" /> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable09.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable09.xml index 09934de707f7..b3c91a88bced 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable09.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable09.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,27 +16,29 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - - <viewport android:viewportWidth="200" - android:viewportHeight="200"/> + <viewport + android:viewportHeight="200" + android:viewportWidth="200" /> <group> <path - android:name="arrow" - android:pathData="M 20,20 l 60,0 0,140 -60,0 z M 120,20 l 60,0 0,140 -60,0 z" - android:fill="#ffffffff"/> + android:name="arrow" + android:fill="#ffffffff" + android:pathData="M 20,20 l 60,0 0,140 -60,0 z M 120,20 l 60,0 0,140 -60,0 z" /> </group> <group> <path - android:name="house" - android:pathData="M 100,20 l 0,0 0,140 -80,0 z M 100,20 l 0,0 80,140 -80,0 z" - android:fill="#ffffffff" - android:rotation="90" - android:pivotX="100" - android:pivotY="100"/> + android:name="house" + android:fill="#ffffffff" + android:pathData="M 100,20 l 0,0 0,140 -80,0 z M 100,20 l 0,0 80,140 -80,0 z" + android:pivotX="100" + android:pivotY="100" + android:rotation="90" /> </group> - <animation android:sequence="arrow,house"/> -</vector> + + <animation android:sequence="arrow,house" /> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable10.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable10.xml index f17f67ac6acc..7aca16960f7e 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable10.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable10.xml @@ -27,88 +27,87 @@ <group> <path android:name="bar3" - android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" /> <path android:name="bar2" - android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" - android:fill="#FF555555" /> + android:fill="#FF555555" + android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" /> <path android:name="bar1" - android:pathData="M14.001,34.645 L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" - android:fill="#FF555555" /> + android:fill="#FF555555" + android:pathData="M14.001,34.645 L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" /> <path android:name="bar0" - android:pathData="M0,20.502l6.999,7.071 c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" - android:fill="#FF555555" /> + android:fill="#FF555555" + android:pathData="M0,20.502l6.999,7.071 c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" /> </group> - <group> + <group> <path android:name="bar3" - android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" /> <path android:name="bar2" - android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" /> <path android:name="bar1" - android:pathData="M14.001,34.645 L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" - android:fill="#FF555555" /> + android:fill="#FF555555" + android:pathData="M14.001,34.645 L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" /> <path android:name="bar0" - android:pathData="M0,20.502l6.999,7.071 c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" - android:fill="#FF555555" /> + android:fill="#FF555555" + android:pathData="M0,20.502l6.999,7.071 c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" /> </group> - - <group> + <group> <path android:name="bar3" - android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" /> <path android:name="bar2" - android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" /> <path android:name="bar1" - android:pathData="M14.001,34.645 L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M14.001,34.645 L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" /> <path android:name="bar0" - android:pathData="M0,20.502l6.999,7.071 c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" - android:fill="#FF555555" /> + android:fill="#FF555555" + android:pathData="M0,20.502l6.999,7.071 c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" /> </group> - - <group> + <group> <path android:name="bar3" - android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" /> <path android:name="bar2" - android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" /> <path android:name="bar1" - android:pathData="M14.001,34.645 L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M14.001,34.645 L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" /> <path android:name="bar0" - android:pathData="M0,20.502l6.999,7.071 c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" - android:fill="#FFFFFFFF" /> + android:fill="#FFFFFFFF" + android:pathData="M0,20.502l6.999,7.071 c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" /> </group> - <animation - android:sequence="bar0,bar0,bar0,bar0" - android:durations="500,500,500"/> - <animation - android:sequence="bar1,bar1,bar1,bar1" - android:durations="500,500,500"/> - <animation - android:sequence="bar2,bar2,bar2,bar2" - android:durations="500,500,500"/> - <animation - android:sequence="bar3,bar3,bar3,bar3" - android:durations="500,500,500"/> -</vector> + <animation + android:durations="500,500,500" + android:sequence="bar0,bar0,bar0,bar0" /> + <animation + android:durations="500,500,500" + android:sequence="bar1,bar1,bar1,bar1" /> + <animation + android:durations="500,500,500" + android:sequence="bar2,bar2,bar2,bar2" /> + <animation + android:durations="500,500,500" + android:sequence="bar3,bar3,bar3,bar3" /> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable11.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable11.xml index 8787b342b225..a4403c5a9c50 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable11.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable11.xml @@ -26,29 +26,28 @@ <group> <path android:name="battery" - android:pathData="M 20.28125,2.0000002 C 17.352748,2.0000002 15,4.3527485 15,7.2812502 L 15,8.0000002 L 13.15625,8.0000002 C 9.7507553,8.0000002 7,10.750759 7,14.15625 L 7,39.84375 C 7,43.24924 9.7507558,46 13.15625,46 L 33.84375,46 C 37.249245,46 39.999999,43.24924 40,39.84375 L 40,14.15625 C 40,10.75076 37.249243,8.0000002 33.84375,8.0000002 L 32,8.0000002 L 32,7.2812502 C 32,4.3527485 29.647252,2.0000002 26.71875,2.0000002 L 20.28125,2.0000002 z" android:fill="#3388ff" + android:pathData="M 20.28125,2.0000002 C 17.352748,2.0000002 15,4.3527485 15,7.2812502 L 15,8.0000002 L 13.15625,8.0000002 C 9.7507553,8.0000002 7,10.750759 7,14.15625 L 7,39.84375 C 7,43.24924 9.7507558,46 13.15625,46 L 33.84375,46 C 37.249245,46 39.999999,43.24924 40,39.84375 L 40,14.15625 C 40,10.75076 37.249243,8.0000002 33.84375,8.0000002 L 32,8.0000002 L 32,7.2812502 C 32,4.3527485 29.647252,2.0000002 26.71875,2.0000002 L 20.28125,2.0000002 z" + android:rotation="0" android:stroke="#ff8833" - android:strokeWidth="1" - android:rotation="0"/> - <path + android:strokeWidth="1" /> + <path android:name="spark" - android:pathData="M 30,18.031528 L 25.579581,23.421071 L 29.370621,26.765348 L 20.096792,37 L 21.156922,28.014053 L 17,24.902844 L 20.880632,18 L 30,18.031528 z" - android:fill="#FFFF0000" /> - + android:fill="#FFFF0000" + android:pathData="M 30,18.031528 L 25.579581,23.421071 L 29.370621,26.765348 L 20.096792,37 L 21.156922,28.014053 L 17,24.902844 L 20.880632,18 L 30,18.031528 z" /> </group> <group> <path android:name="battery" - android:pathData="M 20.28125,2.0000002 C 17.352748,2.0000002 15,4.3527485 15,7.2812502 L 15,8.0000002 L 13.15625,8.0000002 C 9.7507553,8.0000002 7,10.750759 7,14.15625 L 7,39.84375 C 7,43.24924 9.7507558,46 13.15625,46 L 33.84375,46 C 37.249245,46 39.999999,43.24924 40,39.84375 L 40,14.15625 C 40,10.75076 37.249243,8.0000002 33.84375,8.0000002 L 32,8.0000002 L 32,7.2812502 C 32,4.3527485 29.647252,2.0000002 26.71875,2.0000002 L 20.28125,2.0000002 z" android:fill="#ff8833" + android:pathData="M 20.28125,2.0000002 C 17.352748,2.0000002 15,4.3527485 15,7.2812502 L 15,8.0000002 L 13.15625,8.0000002 C 9.7507553,8.0000002 7,10.750759 7,14.15625 L 7,39.84375 C 7,43.24924 9.7507558,46 13.15625,46 L 33.84375,46 C 37.249245,46 39.999999,43.24924 40,39.84375 L 40,14.15625 C 40,10.75076 37.249243,8.0000002 33.84375,8.0000002 L 32,8.0000002 L 32,7.2812502 C 32,4.3527485 29.647252,2.0000002 26.71875,2.0000002 L 20.28125,2.0000002 z" + android:rotation="0" android:stroke="#3388ff" - android:strokeWidth="1" - android:rotation="0" /> + android:strokeWidth="1" /> <path android:name="spark" - android:pathData="M 30,18.031528 L 25.579581,23.421071 L 29.370621,26.765348 L 20.096792,37 L 21.156922,28.014053 L 17,24.902844 L 20.880632,18 L 30,18.031528 z" - android:fill="#FFFF0000" /> + android:fill="#FFFF0000" + android:pathData="M 30,18.031528 L 25.579581,23.421071 L 29.370621,26.765348 L 20.096792,37 L 21.156922,28.014053 L 17,24.902844 L 20.880632,18 L 30,18.031528 z" /> </group> <animation @@ -58,5 +57,4 @@ android:durations="2000" android:sequence="battery,battery" /> - -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml index 89748d53f44a..207879d6ab35 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,76 +16,72 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - - <viewport android:viewportWidth="600" - android:viewportHeight="600"/> + <viewport + android:viewportHeight="600" + android:viewportWidth="600" /> <group> <path - android:name="pie1" - android:pathData="M300,70 a230,230 0 1,0 1,0 z" - android:stroke="#FF00FF00" - android:strokeWidth="70" - android:trimPathStart="0" - android:trimPathEnd=".75" - android:trimPathOffset="0"/> - + android:name="pie1" + android:pathData="M300,70 a230,230 0 1,0 1,0 z" + android:stroke="#FF00FF00" + android:strokeWidth="70" + android:trimPathEnd=".75" + android:trimPathOffset="0" + android:trimPathStart="0" /> <path - android:name="v" - android:pathData="M300,70 l 0,-70 70,70 -70,70z" - android:fill="#FF00FF00" - android:pivotX="300" - android:pivotY="300" - android:rotation="0" - /> + android:name="v" + android:fill="#FF00FF00" + android:pathData="M300,70 l 0,-70 70,70 -70,70z" + android:pivotX="300" + android:pivotY="300" + android:rotation="0" /> </group> - <group> <path - android:name="v" - android:pathData="M300,70 l 0,-70 70,70 -70,70z" - android:pivotX="300" - android:pivotY="300" - android:rotation="360"/> + android:name="v" + android:pathData="M300,70 l 0,-70 70,70 -70,70z" + android:pivotX="300" + android:pivotY="300" + android:rotation="360" /> <path - android:name="pie2" - android:pathData="M300,70 a230,230 0 1,0 1,0 z" - android:stroke="#FF00FF00" - android:strokeWidth="70" - android:rotation="360" - android:pivotX="300" - android:pivotY="300" - android:trimPathStart="0" - android:trimPathEnd=".5" - android:trimPathOffset="0" - android:strokeLineCap="round" - /> + android:name="pie2" + android:pathData="M300,70 a230,230 0 1,0 1,0 z" + android:pivotX="300" + android:pivotY="300" + android:rotation="360" + android:stroke="#FF00FF00" + android:strokeLineCap="round" + android:strokeWidth="70" + android:trimPathEnd=".5" + android:trimPathOffset="0" + android:trimPathStart="0" /> </group> - <animation android:sequence="pie1,pie2" - android:durations="2000" - android:startOffset="500" - android:repeatCount="-1" - android:repeatStyle="forward" - android:animate="easeInOut" - /> - <animation android:sequence="v,v" - android:durations="2000" - android:startOffset="500" - android:repeatCount="-1" - android:repeatStyle="forward" - android:animate="easeInOut" - /> - <animation android:sequence="pie1,pie2" - android:durations="2800" - android:startOffset="500" - android:limitTo="trimPathEnd" - android:repeatCount="-1" - android:repeatStyle="reverse" - android:animate="easeInOut" - /> + <animation + android:animate="easeInOut" + android:durations="2000" + android:repeatCount="-1" + android:repeatStyle="forward" + android:sequence="pie1,pie2" + android:startOffset="500" /> + <animation + android:animate="easeInOut" + android:durations="2000" + android:repeatCount="-1" + android:repeatStyle="forward" + android:sequence="v,v" + android:startOffset="500" /> + <animation + android:animate="easeInOut" + android:durations="2800" + android:limitTo="trimPathEnd" + android:repeatCount="-1" + android:repeatStyle="reverse" + android:sequence="pie1,pie2" + android:startOffset="500" /> -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable13.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable13.xml index 43dda52c5889..4a2ed90c6f5f 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable13.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable13.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,68 +16,64 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - - <viewport android:viewportWidth="600" - android:viewportHeight="400"/> + <viewport + android:viewportHeight="400" + android:viewportWidth="600" /> <group> <path - android:name="pie1" - android:pathData="M300,200 h-150 a150,150 0 1,0 150,-150 z" - android:fill="#ffffffff" - android:stroke="#FF00FF00" - android:strokeWidth="1"/> - + android:name="pie1" + android:fill="#ffffffff" + android:pathData="M300,200 h-150 a150,150 0 1,0 150,-150 z" + android:stroke="#FF00FF00" + android:strokeWidth="1" /> <path - android:name="half" - android:pathData="M275,175 v-150 a150,150 0 0,0 -150,150 z" - android:fill="#FFFF0000" - android:stroke="#FF0000FF" - android:strokeWidth="5" - android:rotation="0" - android:pivotX="300" - android:pivotY="200"/> + android:name="half" + android:fill="#FFFF0000" + android:pathData="M275,175 v-150 a150,150 0 0,0 -150,150 z" + android:pivotX="300" + android:pivotY="200" + android:rotation="0" + android:stroke="#FF0000FF" + android:strokeWidth="5" /> </group> - <group> <path - android:name="pie2" - android:pathData="M300,200 h-150 a150,150 0 1,0 150,-150 z" - android:fill="#ffff0000" - android:stroke="#FF00FF00" - android:strokeWidth="10" - android:rotation="360" - android:pivotX="300" - android:pivotY="200"/> - + android:name="pie2" + android:fill="#ffff0000" + android:pathData="M300,200 h-150 a150,150 0 1,0 150,-150 z" + android:pivotX="300" + android:pivotY="200" + android:rotation="360" + android:stroke="#FF00FF00" + android:strokeWidth="10" /> <path - android:name="half" - android:pathData="M275,175 v-150 a150,150 0 0,0 -150,150 z" - android:fill="#FFFFFF00" - android:stroke="#FF0000FF" - android:strokeWidth="5" - android:rotation="-360" - android:pivotX="300" - android:pivotY="200"/> + android:name="half" + android:fill="#FFFFFF00" + android:pathData="M275,175 v-150 a150,150 0 0,0 -150,150 z" + android:pivotX="300" + android:pivotY="200" + android:rotation="-360" + android:stroke="#FF0000FF" + android:strokeWidth="5" /> </group> - <animation android:sequence="pie1,pie2" - android:durations="1000" - android:startOffset="500" - android:repeatCount="2" - android:repeatStyle="forward" - android:animate="easeInOut" - /> - <animation android:sequence="half,half" - android:durations="1000" - android:startOffset="500" - android:repeatCount="5" - android:repeatStyle="forward" - android:animate="easeInOut" - /> - + <animation + android:animate="easeInOut" + android:durations="1000" + android:repeatCount="2" + android:repeatStyle="forward" + android:sequence="pie1,pie2" + android:startOffset="500" /> + <animation + android:animate="easeInOut" + android:durations="1000" + android:repeatCount="5" + android:repeatStyle="forward" + android:sequence="half,half" + android:startOffset="500" /> -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable14.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable14.xml index 0f1f14993f97..6ebd56b41bd8 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable14.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable14.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,40 +16,39 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - <viewport android:viewportWidth="800" - android:viewportHeight="500"/> + <viewport + android:viewportHeight="500" + android:viewportWidth="800" /> <group> <path - android:name="pie1" - android:pathData="M200,450 l 50,-25 + android:name="pie1" + android:pathData="M200,450 l 50,-25 a25,25 -30 0,1 100,-50 l 50,-25 a25,50 -30 0,1 100,-50 l 50,-25 a25,75 -30 0,1 100,-50 l 50,-25 a25,100 -30 0,1 100,-50 l 50,-25" - android:stroke="#FF00FF00" - android:strokeWidth="10"/> + android:stroke="#FF00FF00" + android:strokeWidth="10" /> </group> - <group> <path - android:name="pie2" - android:pathData="M200,350 l 50,-25 + android:name="pie2" + android:pathData="M200,350 l 50,-25 a25,12 -30 0,1 100,-50 l 50,-25 a25,25 -30 0,1 100,-50 l 50,-25 a25,37 -30 0,1 100,-50 l 50,-25 a25,50 -30 0,1 100,-50 l 50,-25" - android:stroke="#FF00FF00" - android:strokeWidth="10" - android:rotation="20" - android:pivotX="90" - android:pivotY="100"/> - + android:pivotX="90" + android:pivotY="100" + android:rotation="20" + android:stroke="#FF00FF00" + android:strokeWidth="10" /> </group> - <animation android:sequence="pie1,pie2"/> + <animation android:sequence="pie1,pie2" /> -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable15.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable15.xml index 6bc946fe7f91..3c92d25cc0b7 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable15.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable15.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,34 +16,33 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - <viewport android:viewportWidth="500" - android:viewportHeight="400"/> + <viewport + android:viewportHeight="400" + android:viewportWidth="500" /> <group> <path - android:name="arrow" - android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200" - android:fill="#ffffffff" - android:stroke="#FFFF0000" - android:strokeWidth="1"/> + android:name="arrow" + android:fill="#ffffffff" + android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200" + android:stroke="#FFFF0000" + android:strokeWidth="1" /> </group> - <group> <path - android:name="house" - android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200" - android:fill="#ff440000" - android:stroke="#FFFF0000" - android:strokeWidth="10" - android:rotation="180" - android:pivotX="250" - android:pivotY="200"/> + android:name="house" + android:fill="#ff440000" + android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200" + android:pivotX="250" + android:pivotY="200" + android:rotation="180" + android:stroke="#FFFF0000" + android:strokeWidth="10" /> </group> - <animation android:sequence="arrow,house"/> - + <animation android:sequence="arrow,house" /> -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable16.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable16.xml index c9c8e8a8d4f2..7e757a5f142c 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable16.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable16.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,32 +16,31 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - <viewport android:viewportWidth="200" - android:viewportHeight="200"/> + <viewport + android:viewportHeight="200" + android:viewportWidth="200" /> <group> <path - android:name="arrow" - android:pathData="M 100,10 v 180 M 10,100 h 180" - android:stroke="#FF00FF00" - android:strokeWidth="1"/> + android:name="arrow" + android:pathData="M 100,10 v 180 M 10,100 h 180" + android:stroke="#FF00FF00" + android:strokeWidth="1" /> </group> - <group> <path - android:name="house" - android:pathData="M 100,10 v 90 M 10,100 h 90" - android:stroke="#FF00FF00" - android:strokeWidth="10" - android:rotation="360" - android:pivotX="100" - android:pivotY="100"/> + android:name="house" + android:pathData="M 100,10 v 90 M 10,100 h 90" + android:pivotX="100" + android:pivotY="100" + android:rotation="360" + android:stroke="#FF00FF00" + android:strokeWidth="10" /> </group> - <animation android:sequence="arrow,house"/> - + <animation android:sequence="arrow,house" /> -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable18.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable18.xml index 83dfbd20a1d3..69212f5dc01d 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable18.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable18.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,31 +16,30 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - <viewport android:viewportWidth="500" - android:viewportHeight="400"/> + <viewport + android:viewportHeight="400" + android:viewportWidth="500" /> <group> <path - android:name="arrow" - android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200" - android:stroke="#FFFFFF00" - android:strokeWidth="10"/> + android:name="arrow" + android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200" + android:stroke="#FFFFFF00" + android:strokeWidth="10" /> </group> - <group> <path - android:name="house" - android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200" - android:strokeWidth="10" - android:rotation="360" - android:pivotX="250" - android:pivotY="200"/> + android:name="house" + android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200" + android:pivotX="250" + android:pivotY="200" + android:rotation="360" + android:strokeWidth="10" /> </group> - <animation android:sequence="arrow,house"/> - + <animation android:sequence="arrow,house" /> -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable19.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable19.xml index 013254f00b7a..2dca48d9c87d 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable19.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable19.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,31 +16,30 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - <viewport android:viewportWidth="1000" - android:viewportHeight="800"/> + <viewport + android:viewportHeight="800" + android:viewportWidth="1000" /> <group> <path - android:name="arrow" - android:pathData="M10,300 Q400,50 600,300 T1000,300" - android:stroke="#FF00FFFF" - android:strokeWidth="40"/> + android:name="arrow" + android:pathData="M10,300 Q400,50 600,300 T1000,300" + android:stroke="#FF00FFFF" + android:strokeWidth="40" /> </group> - <group> <path - android:name="house" - android:pathData="M10,300 Q400,550 600,300 T1000,300" - android:stroke="#FFFF0000" - android:strokeWidth="60" - android:pivotX="90" - android:pivotY="100"/> + android:name="house" + android:pathData="M10,300 Q400,550 600,300 T1000,300" + android:pivotX="90" + android:pivotY="100" + android:stroke="#FFFF0000" + android:strokeWidth="60" /> </group> - <animation android:sequence="arrow,house"/> - + <animation android:sequence="arrow,house" /> -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable20.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable20.xml index aba7e5f23bcb..b8af7e2d076c 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable20.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable20.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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. @@ -15,23 +16,23 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size - android:width="64dp" - android:height="64dp"/> + android:height="64dp" + android:width="64dp" /> - <viewport android:viewportWidth="480" - android:viewportHeight="480"/> + <viewport + android:viewportHeight="480" + android:viewportWidth="480" /> <group> <path - android:name="edit" - android:pathData="M406.667,180c0,0 -100 -100 -113.334 -113.333 + android:name="edit" + android:fill="#FF00FFFF" + android:pathData="M406.667,180c0,0 -100 -100 -113.334 -113.333 c-13.333 -13.334 -33.333,0 -33.333,0l-160,160c0,0 -40,153.333 -40,173.333c0,13.333,13.333,13.333,13.333,13.333l173.334 -40 c0,0,146.666 -146.666,160 -160C420,200,406.667,180,406.667,180z M226.399,356.823L131.95,378.62l-38.516 -38.522 c7.848 -34.675,20.152 -82.52,23.538 -95.593l3.027,2.162l106.667,106.666L226.399,356.823z" - android:stroke="#FF000000" - android:fill="#FF00FFFF" - android:strokeWidth="10"/> + android:stroke="#FF000000" + android:strokeWidth="10" /> </group> - -</vector> +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_create.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_create.xml index 88971817fc3f..22ce795564fc 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_icon_create.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_create.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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"+ @@ -13,17 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > -<size -android:width="64dp" -android:height="64dp"/> - <viewport android:viewportWidth="24" - android:viewportHeight="24"/> + <size + android:height="64dp" + android:width="64dp" /> -<group> -<path - android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.813995,9.936001l-3.75,-3.75L3.0,17.25zM20.707,7.0429993c0.391,-0.391 0.391,-1.023 0.0,-1.414l-2.336,-2.336c-0.391,-0.391 -1.023,-0.391 -1.414,0.0l-1.832,1.832l3.75,3.75L20.707,7.0429993z" - android:fill="#FF000000" - /> -</group> -</vector> + <viewport + android:viewportHeight="24" + android:viewportWidth="24" /> + + <group> + <path + android:fill="#FF000000" + android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.813995,9.936001l-3.75,-3.75L3.0,17.25zM20.707,7.0429993c0.391,-0.391 0.391,-1.023 0.0,-1.414l-2.336,-2.336c-0.391,-0.391 -1.023,-0.391 -1.414,0.0l-1.832,1.832l3.75,3.75L20.707,7.0429993z" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_delete.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_delete.xml index 2c7ebbd2660c..042173ca9f9c 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_icon_delete.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_delete.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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"+ @@ -13,17 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > -<size -android:width="64dp" -android:height="64dp"/> - <viewport android:viewportWidth="24" - android:viewportHeight="24"/> + <size + android:height="64dp" + android:width="64dp" /> -<group> -<path - android:pathData="M6.0,19.0c0.0,1.104 0.896,2.0 2.0,2.0l8.0,0.0c1.104,0.0 2.0,-0.896 2.0,-2.0l0.0,-12.0L6.0,7.0L6.0,19.0zM18.0,4.0l-2.5,0.0l-1.0,-1.0l-5.0,0.0l-1.0,1.0L6.0,4.0C5.4469986,4.0 5.0,4.4469986 5.0,5.0l0.0,1.0l14.0,0.0l0.0,-1.0C19.0,4.4469986 18.552002,4.0 18.0,4.0z" - android:fill="#FF000000" - /> -</group> -</vector> + <viewport + android:viewportHeight="24" + android:viewportWidth="24" /> + + <group> + <path + android:fill="#FF000000" + android:pathData="M6.0,19.0c0.0,1.104 0.896,2.0 2.0,2.0l8.0,0.0c1.104,0.0 2.0,-0.896 2.0,-2.0l0.0,-12.0L6.0,7.0L6.0,19.0zM18.0,4.0l-2.5,0.0l-1.0,-1.0l-5.0,0.0l-1.0,1.0L6.0,4.0C5.4469986,4.0 5.0,4.4469986 5.0,5.0l0.0,1.0l14.0,0.0l0.0,-1.0C19.0,4.4469986 18.552002,4.0 18.0,4.0z" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_heart.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_heart.xml index e4cf78c90edd..6b6f43de122b 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_icon_heart.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_heart.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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"+ @@ -13,17 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > -<size -android:width="64dp" -android:height="64dp"/> - <viewport android:viewportWidth="24" - android:viewportHeight="24"/> + <size + android:height="64dp" + android:width="64dp" /> -<group> -<path - android:pathData="M16.0,5.0c-1.955,0.0 -3.83,1.268 -4.5,3.0c-0.67,-1.732 -2.547,-3.0 -4.5,-3.0C4.4570007,5.0 2.5,6.931999 2.5,9.5c0.0,3.529 3.793,6.258 9.0,11.5c5.207,-5.242 9.0,-7.971 9.0,-11.5C20.5,6.931999 18.543,5.0 16.0,5.0z" - android:fill="#FF000000" - /> -</group> -</vector> + <viewport + android:viewportHeight="24" + android:viewportWidth="24" /> + + <group> + <path + android:fill="#FF000000" + android:pathData="M16.0,5.0c-1.955,0.0 -3.83,1.268 -4.5,3.0c-0.67,-1.732 -2.547,-3.0 -4.5,-3.0C4.4570007,5.0 2.5,6.931999 2.5,9.5c0.0,3.529 3.793,6.258 9.0,11.5c5.207,-5.242 9.0,-7.971 9.0,-11.5C20.5,6.931999 18.543,5.0 16.0,5.0z" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml index cec12bac095d..ba8ebcad170c 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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"+ @@ -13,21 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > -<size -android:width="64dp" -android:height="64dp"/> - <viewport android:viewportWidth="24" - android:viewportHeight="24"/> + <size + android:height="64dp" + android:width="64dp" /> -<group> -<path - android:pathData="M11.994999,2.0C6.4679985,2.0 2.0,6.4780006 2.0,12.0s4.468,10.0 9.995,10.0S22.0,17.522 22.0,12.0S17.521,2.0 11.994999,2.0zM12.0,20.0c-4.42,0.0 -8.0,-3.582 -8.0,-8.0s3.58,-8.0 8.0,-8.0s8.0,3.582 8.0,8.0S16.419998,20.0 12.0,20.0z" - android:fillOpacity="0.9" - /> -<path - android:pathData="M12.5,6.0l-1.5,0.0 0.0,7.0 5.3029995,3.1819992 0.75,-1.249999 -4.5529995,-2.7320004z" - android:fillOpacity="0.9" - /> -</group> -</vector> + <viewport + android:viewportHeight="24" + android:viewportWidth="24" /> + + <group> + <path + android:fillOpacity="0.9" + android:pathData="M11.994999,2.0C6.4679985,2.0 2.0,6.4780006 2.0,12.0s4.468,10.0 9.995,10.0S22.0,17.522 22.0,12.0S17.521,2.0 11.994999,2.0zM12.0,20.0c-4.42,0.0 -8.0,-3.582 -8.0,-8.0s3.58,-8.0 8.0,-8.0s8.0,3.582 8.0,8.0S16.419998,20.0 12.0,20.0z" /> + <path + android:fillOpacity="0.9" + android:pathData="M12.5,6.0l-1.5,0.0 0.0,7.0 5.3029995,3.1819992 0.75,-1.249999 -4.5529995,-2.7320004z" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_settings.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_settings.xml index 5fe1fb6d635f..896a9387afe6 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_icon_settings.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_settings.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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"+ @@ -13,17 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > -<size -android:width="64dp" -android:height="64dp"/> - <viewport android:viewportWidth="24" - android:viewportHeight="24"/> + <size + android:height="64dp" + android:width="64dp" /> -<group> -<path - android:pathData="M19.429,12.975998c0.042,-0.32 0.07,-0.645 0.07,-0.976s-0.029,-0.655 -0.07,-0.976l2.113,-1.654c0.188,-0.151 0.243,-0.422 0.118,-0.639l-2.0,-3.463c-0.125,-0.217 -0.386,-0.304 -0.612,-0.218l-2.49,1.004c-0.516,-0.396 -1.081,-0.731 -1.69,-0.984l-0.375,-2.648C14.456,2.1829987 14.25,2.0 14.0,2.0l-4.0,0.0C9.75,2.0 9.544,2.1829987 9.506,2.422001L9.131,5.0699997C8.521,5.322998 7.957,5.6570015 7.44,6.054001L4.952,5.0509987C4.726,4.965 4.464,5.052002 4.34,5.269001l-2.0,3.463C2.2150002,8.947998 2.27,9.219002 2.4580002,9.369999l2.112,1.653C4.528,11.344002 4.5,11.668999 4.5,12.0s0.029,0.656 0.071,0.977L2.4580002,14.630001c-0.188,0.151 -0.243,0.422 -0.118,0.639l2.0,3.463c0.125,0.217 0.386,0.304 0.612,0.218l2.489,-1.004c0.516,0.396 1.081,0.731 1.69,0.984l0.375,2.648C9.544,21.817001 9.75,22.0 10.0,22.0l4.0,0.0c0.25,0.0 0.456,-0.183 0.494,-0.422l0.375,-2.648c0.609,-0.253 1.174,-0.588 1.689,-0.984l2.49,1.004c0.226,0.086 0.487,-0.001 0.612,-0.218l2.0,-3.463c0.125,-0.217 0.07,-0.487 -0.118,-0.639L19.429,12.975998zM12.0,16.0c-2.21,0.0 -4.0,-1.791 -4.0,-4.0c0.0,-2.21 1.79,-4.0 4.0,-4.0c2.208,0.0 4.0,1.79 4.0,4.0C16.0,14.209 14.208,16.0 12.0,16.0z" - android:fill="#FF000000" - /> -</group> -</vector> + <viewport + android:viewportHeight="24" + android:viewportWidth="24" /> + + <group> + <path + android:fill="#FF000000" + android:pathData="M19.429,12.975998c0.042,-0.32 0.07,-0.645 0.07,-0.976s-0.029,-0.655 -0.07,-0.976l2.113,-1.654c0.188,-0.151 0.243,-0.422 0.118,-0.639l-2.0,-3.463c-0.125,-0.217 -0.386,-0.304 -0.612,-0.218l-2.49,1.004c-0.516,-0.396 -1.081,-0.731 -1.69,-0.984l-0.375,-2.648C14.456,2.1829987 14.25,2.0 14.0,2.0l-4.0,0.0C9.75,2.0 9.544,2.1829987 9.506,2.422001L9.131,5.0699997C8.521,5.322998 7.957,5.6570015 7.44,6.054001L4.952,5.0509987C4.726,4.965 4.464,5.052002 4.34,5.269001l-2.0,3.463C2.2150002,8.947998 2.27,9.219002 2.4580002,9.369999l2.112,1.653C4.528,11.344002 4.5,11.668999 4.5,12.0s0.029,0.656 0.071,0.977L2.4580002,14.630001c-0.188,0.151 -0.243,0.422 -0.118,0.639l2.0,3.463c0.125,0.217 0.386,0.304 0.612,0.218l2.489,-1.004c0.516,0.396 1.081,0.731 1.69,0.984l0.375,2.648C9.544,21.817001 9.75,22.0 10.0,22.0l4.0,0.0c0.25,0.0 0.456,-0.183 0.494,-0.422l0.375,-2.648c0.609,-0.253 1.174,-0.588 1.689,-0.984l2.49,1.004c0.226,0.086 0.487,-0.001 0.612,-0.218l2.0,-3.463c0.125,-0.217 0.07,-0.487 -0.118,-0.639L19.429,12.975998zM12.0,16.0c-2.21,0.0 -4.0,-1.791 -4.0,-4.0c0.0,-2.21 1.79,-4.0 4.0,-4.0c2.208,0.0 4.0,1.79 4.0,4.0C16.0,14.209 14.208,16.0 12.0,16.0z" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_test01.xml b/tests/VectorDrawableTest/res/drawable/vector_test01.xml index 6beb9d847ea9..a9091aba3827 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_test01.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_test01.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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"+ @@ -13,19 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > -<size -android:width="128dp" -android:height="128dp"/> - <viewport android:viewportWidth="512" - android:viewportHeight="512"/> + <size + android:height="128dp" + android:width="128dp" /> -<group> -<path - android:name="002b" - android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0t-200,299" - android:stroke="#FF0000FF" - android:strokeWidth="4" - /> -</group> -</vector> + <viewport + android:viewportHeight="512" + android:viewportWidth="512" /> + + <group> + <path + android:name="002b" + android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0t-200,299" + android:stroke="#FF0000FF" + android:strokeWidth="4" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_test02.xml b/tests/VectorDrawableTest/res/drawable/vector_test02.xml index 2c1a28e2bcdb..ab58c0674eab 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_test02.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_test02.xml @@ -1,4 +1,5 @@ -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- + 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"+ @@ -13,19 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > -<size -android:width="128dp" -android:height="128dp"/> - <viewport android:viewportWidth="512" - android:viewportHeight="512"/> + <size + android:height="128dp" + android:width="128dp" /> -<group> -<path - android:name="002b" - android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0T-200,299" - android:stroke="#FF0000FF" - android:strokeWidth="4" - /> -</group> -</vector> + <viewport + android:viewportHeight="512" + android:viewportWidth="512" /> + + <group> + <path + android:name="002b" + android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0T-200,299" + android:stroke="#FF0000FF" + android:strokeWidth="4" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VoiceInteraction/Android.mk b/tests/VoiceInteraction/Android.mk new file mode 100644 index 000000000000..8decca7d163f --- /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 000000000000..9c5acf9d2e7d --- /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 000000000000..3d7a41893d6f --- /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 000000000000..2abf6519428b --- /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 000000000000..12edb31c783f --- /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 000000000000..35702f1e001b --- /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 000000000000..adc0df4a5b2e --- /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 000000000000..016a80e999d2 --- /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 000000000000..5d212a4afd75 --- /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)); + } + }; +} |