Merge "Add special system view cookie to OverflowButton."
diff --git a/Android.mk b/Android.mk
index be7e055..232f5bf 100644
--- a/Android.mk
+++ b/Android.mk
@@ -180,6 +180,8 @@
core/java/android/os/IUserManager.aidl \
core/java/android/os/IVibratorService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
+ core/java/android/service/notification/IConditionListener.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 +199,9 @@
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/voice/IVoiceInteractionSessionService.aidl \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \
core/java/android/service/wallpaper/IWallpaperEngine.aidl \
core/java/android/service/wallpaper/IWallpaperService.aidl \
@@ -230,6 +235,10 @@
core/java/com/android/internal/app/IBatteryStats.aidl \
core/java/com/android/internal/app/IProcessStats.aidl \
core/java/com/android/internal/app/IUsageStats.aidl \
+ core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
+ core/java/com/android/internal/app/IVoiceInteractor.aidl \
+ core/java/com/android/internal/app/IVoiceInteractorCallback.aidl \
+ core/java/com/android/internal/app/IVoiceInteractorRequest.aidl \
core/java/com/android/internal/app/IMediaContainerService.aidl \
core/java/com/android/internal/appwidget/IAppWidgetService.aidl \
core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
diff --git a/api/current.txt b/api/current.txt
index aa1b6f0..4779124 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31,6 +31,7 @@
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
field public static final java.lang.String BIND_TRUST_AGENT_SERVICE = "android.permission.BIND_TRUST_AGENT_SERVICE";
field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
+ field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION";
field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER";
field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH";
@@ -490,6 +491,7 @@
field public static final int editTextStyle = 16842862; // 0x101006e
field public static final deprecated int editable = 16843115; // 0x101016b
field public static final int editorExtras = 16843300; // 0x1010224
+ field public static final int elevation = 16843852; // 0x101044c
field public static final int ellipsize = 16842923; // 0x10100ab
field public static final int ems = 16843096; // 0x1010158
field public static final int enabled = 16842766; // 0x101000e
@@ -1008,6 +1010,7 @@
field public static final int selectedDateVerticalBar = 16843591; // 0x1010347
field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342
field public static final int sequence = 16843815; // 0x1010427
+ field public static final int sessionService = 16843850; // 0x101044a
field public static final int settingsActivity = 16843301; // 0x1010225
field public static final int shadowColor = 16843105; // 0x1010161
field public static final int shadowDx = 16843106; // 0x1010162
@@ -1103,6 +1106,7 @@
field public static final int switchMinWidth = 16843632; // 0x1010370
field public static final int switchPadding = 16843633; // 0x1010371
field public static final int switchPreferenceStyle = 16843629; // 0x101036d
+ field public static final int switchStyle = 16843851; // 0x101044b
field public static final int switchTextAppearance = 16843630; // 0x101036e
field public static final int switchTextOff = 16843628; // 0x101036c
field public static final int switchTextOn = 16843627; // 0x101036b
@@ -1662,10 +1666,14 @@
field public static final int decelerate_cubic = 17563651; // 0x10c0003
field public static final int decelerate_quad = 17563649; // 0x10c0001
field public static final int decelerate_quint = 17563653; // 0x10c0005
- field public static final int fast_out_linear_in = 17563663; // 0x10c000f
- field public static final int fast_out_slow_in = 17563661; // 0x10c000d
+ field public static final int fast_out_linear_in = 17563667; // 0x10c0013
+ field public static final int fast_out_slow_in = 17563665; // 0x10c0011
+ field public static final int l_resource_pad1 = 17563664; // 0x10c0010
+ field public static final int l_resource_pad2 = 17563663; // 0x10c000f
+ field public static final int l_resource_pad3 = 17563662; // 0x10c000e
+ field public static final int l_resource_pad4 = 17563661; // 0x10c000d
field public static final int linear = 17563659; // 0x10c000b
- field public static final int linear_out_slow_in = 17563662; // 0x10c000e
+ field public static final int linear_out_slow_in = 17563666; // 0x10c0012
field public static final int overshoot = 17563656; // 0x10c0008
}
@@ -3202,6 +3210,7 @@
method public int getTaskId();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
+ method public android.app.VoiceInteractor getVoiceInteractor();
method public final int getVolumeControlStream();
method public android.view.Window getWindow();
method public android.view.WindowManager getWindowManager();
@@ -3213,6 +3222,7 @@
method public boolean isFinishing();
method public boolean isImmersive();
method public boolean isTaskRoot();
+ method public boolean isVoiceInteraction();
method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public boolean moveTaskToBack(boolean);
method public boolean navigateUpTo(android.content.Intent);
@@ -4838,6 +4848,29 @@
field public static final int MODE_NIGHT_YES = 2; // 0x2
}
+ public class VoiceInteractor {
+ method public boolean submitRequest(android.app.VoiceInteractor.Request);
+ method public boolean[] supportsCommands(java.lang.String[]);
+ }
+
+ public static class VoiceInteractor.CommandRequest extends android.app.VoiceInteractor.Request {
+ ctor public VoiceInteractor.CommandRequest(java.lang.String, android.os.Bundle);
+ method public void onCommandResult(android.os.Bundle);
+ }
+
+ public static class VoiceInteractor.ConfirmationRequest extends android.app.VoiceInteractor.Request {
+ ctor public VoiceInteractor.ConfirmationRequest(java.lang.CharSequence, android.os.Bundle);
+ method public void onConfirmationResult(boolean, android.os.Bundle);
+ }
+
+ public static abstract class VoiceInteractor.Request {
+ ctor public VoiceInteractor.Request();
+ method public void cancel();
+ method public android.app.Activity getActivity();
+ method public android.content.Context getContext();
+ method public void onCancel();
+ }
+
public final class WallpaperInfo implements android.os.Parcelable {
ctor public WallpaperInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public int describeContents();
@@ -4947,7 +4980,9 @@
public class DevicePolicyManager {
method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
+ method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
+ method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
method public java.util.List<android.content.ComponentName> getActiveAdmins();
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
method public boolean getCameraDisabled(android.content.ComponentName);
@@ -5010,6 +5045,9 @@
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
+ field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
+ field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
+ field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
@@ -6997,6 +7035,7 @@
field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB";
field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST";
field public static final java.lang.String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
+ field public static final java.lang.String CATEGORY_VOICE = "android.intent.category.VOICE";
field public static final android.os.Parcelable.Creator CREATOR;
field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
@@ -7712,6 +7751,7 @@
method public long getFirstInstallTime();
method public android.graphics.drawable.Drawable getIcon(int);
method public java.lang.CharSequence getLabel();
+ method public java.lang.String getName();
method public android.os.UserHandle getUser();
}
@@ -9710,6 +9750,7 @@
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);
@@ -10022,11 +10063,18 @@
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 setOval(int, int, int, int);
+ method public void setOval(android.graphics.Rect);
+ 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 {
@@ -10195,6 +10243,7 @@
method public void addArc(android.graphics.RectF, float, float);
method public void addCircle(float, float, float, android.graphics.Path.Direction);
method public void addOval(android.graphics.RectF, android.graphics.Path.Direction);
+ method public void addOval(float, float, float, float, android.graphics.Path.Direction);
method public void addPath(android.graphics.Path, float, float);
method public void addPath(android.graphics.Path);
method public void addPath(android.graphics.Path, android.graphics.Matrix);
@@ -10749,7 +10798,7 @@
method public int getMinimumHeight();
method public int getMinimumWidth();
method public abstract int getOpacity();
- method public android.graphics.Outline getOutline();
+ method public boolean getOutline(android.graphics.Outline);
method public boolean getPadding(android.graphics.Rect);
method public int[] getState();
method public android.graphics.Region getTransparentRegion();
@@ -11122,6 +11171,7 @@
method public android.graphics.drawable.shapes.Shape clone() throws java.lang.CloneNotSupportedException;
method public abstract void draw(android.graphics.Canvas, android.graphics.Paint);
method public final float getHeight();
+ method public boolean getOutline(android.graphics.Outline);
method public final float getWidth();
method public boolean hasAlpha();
method protected void onResize(float, float);
@@ -13376,6 +13426,18 @@
ctor public DeniedByServerException(java.lang.String);
}
+ public final class DngCreator {
+ ctor public DngCreator(android.hardware.camera2.CameraCharacteristics, android.hardware.camera2.CaptureResult);
+ method public android.media.DngCreator setDescription(java.lang.String);
+ method public android.media.DngCreator setLocation(android.location.Location);
+ method public android.media.DngCreator setOrientation(int);
+ method public android.media.DngCreator setThumbnail(android.graphics.Bitmap);
+ method public android.media.DngCreator setThumbnail(android.media.Image);
+ method public void writeByteBuffer(java.io.OutputStream, java.nio.ByteBuffer, int, long) throws java.io.IOException;
+ method public void writeImage(java.io.OutputStream, android.media.Image) throws java.io.IOException;
+ method public void writeInputStream(java.io.OutputStream, java.io.InputStream, int, long) throws java.io.IOException;
+ }
+
public class ExifInterface {
ctor public ExifInterface(java.lang.String) throws java.io.IOException;
method public double getAltitude(double);
@@ -24865,6 +24927,42 @@
}
+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.os.Bundle);
+ 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(boolean, android.os.Bundle);
+ method public void sendConfirmResult(boolean, android.os.Bundle);
+ }
+
+ public abstract class VoiceInteractionSessionService extends android.app.Service {
+ ctor public VoiceInteractionSessionService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract android.service.voice.VoiceInteractionSession onNewSession(android.os.Bundle);
+ }
+
+}
+
package android.service.wallpaper {
public abstract class WallpaperService extends android.app.Service {
@@ -28065,12 +28163,19 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.tv.TvInputService";
}
- public abstract class TvInputService.TvInputSessionImpl {
+ public abstract class TvInputService.TvInputSessionImpl implements android.view.KeyEvent.Callback {
ctor public TvInputService.TvInputSessionImpl();
method public android.view.View onCreateOverlayView();
+ method public boolean onGenericMotionEvent(android.view.MotionEvent);
+ method public boolean onKeyDown(int, android.view.KeyEvent);
+ method public boolean onKeyLongPress(int, android.view.KeyEvent);
+ method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
+ method public boolean onKeyUp(int, android.view.KeyEvent);
method public abstract void onRelease();
method public abstract boolean onSetSurface(android.view.Surface);
method public abstract void onSetVolume(float);
+ method public boolean onTouchEvent(android.view.MotionEvent);
+ method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
method public void setOverlayViewEnabled(boolean);
}
@@ -28080,9 +28185,16 @@
ctor public TvView(android.content.Context, android.util.AttributeSet);
ctor public TvView(android.content.Context, android.util.AttributeSet, int);
method public void bindTvInput(android.content.ComponentName, android.tv.TvInputManager.SessionCreateCallback);
+ method public boolean dispatchUnhandledInputEvent(android.view.InputEvent);
+ method public boolean onUnhandledInputEvent(android.view.InputEvent);
+ method public void setOnUnhandledInputEventListener(android.tv.TvView.OnUnhandledInputEventListener);
method public void unbindTvInput();
}
+ public static abstract interface TvView.OnUnhandledInputEventListener {
+ method public abstract boolean onUnhandledInputEvent(android.view.InputEvent);
+ }
+
}
package android.util {
@@ -30024,6 +30136,7 @@
method public int getDrawingCacheQuality();
method public void getDrawingRect(android.graphics.Rect);
method public long getDrawingTime();
+ method public float getElevation();
method public boolean getFilterTouchesWhenObscured();
method public boolean getFitsSystemWindows();
method public java.util.ArrayList<android.view.View> getFocusables(int);
@@ -30122,6 +30235,7 @@
method public void getWindowVisibleDisplayFrame(android.graphics.Rect);
method public float getX();
method public float getY();
+ method public float getZ();
method public boolean hasFocus();
method public boolean hasFocusable();
method public boolean hasNestedScrollingParent();
@@ -30288,6 +30402,7 @@
method public void setDrawingCacheEnabled(boolean);
method public void setDrawingCacheQuality(int);
method public void setDuplicateParentStateEnabled(boolean);
+ method public void setElevation(float);
method public void setEnabled(boolean);
method public void setFadingEdgeLength(int);
method public void setFilterTouchesWhenObscured(boolean);
@@ -30373,6 +30488,7 @@
method public void setWillNotDraw(boolean);
method public void setX(float);
method public void setY(float);
+ method public void setZ(float);
method public boolean showContextMenu();
method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
method public void startAnimation(android.view.animation.Animation);
@@ -30506,6 +30622,7 @@
field protected static final int[] WINDOW_FOCUSED_STATE_SET;
field public static final android.util.Property X;
field public static final android.util.Property Y;
+ field public static final android.util.Property Z;
}
public static class View.AccessibilityDelegate {
@@ -30952,6 +31069,8 @@
method public android.view.ViewPropertyAnimator xBy(float);
method public android.view.ViewPropertyAnimator y(float);
method public android.view.ViewPropertyAnimator yBy(float);
+ method public android.view.ViewPropertyAnimator z(float);
+ method public android.view.ViewPropertyAnimator zBy(float);
}
public final class ViewStub extends android.view.View {
@@ -34433,6 +34552,7 @@
method public void setExcludeMimes(java.lang.String[]);
method public void setImageToDefault();
method public void setMode(int);
+ method public void setOverlay(android.graphics.drawable.Drawable);
field protected java.lang.String[] mExcludeMimes;
}
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 7df55a5..6b55b7b 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -749,6 +749,11 @@
"Error: Activity not started, you do not "
+ "have permission to access it.");
break;
+ case ActivityManager.START_NOT_VOICE_COMPATIBLE:
+ out.println(
+ "Error: Activity not started, voice control not allowed for: "
+ + intent);
+ break;
default:
out.println(
"Error: Activity not started, unknown error code " + res);
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 41afa39..1780e03 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -51,6 +51,7 @@
#include "BootAnimation.h"
+#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"
#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip"
#define EXIT_PROP_NAME "service.bootanim.exit"
@@ -283,6 +284,9 @@
(access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||
+ ((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) &&
+ ((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) ||
+
((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {
mZip = zipFile;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 599a608..8981c88 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -22,6 +22,7 @@
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
import android.widget.Toolbar;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.policy.PolicyManager;
@@ -726,6 +727,8 @@
/*package*/ ActionBar mActionBar = null;
private boolean mEnableDefaultActionBarUp;
+ private VoiceInteractor mVoiceInteractor;
+
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -1134,6 +1137,23 @@
}
/**
+ * Check whether this activity is running as part of a voice interaction with the user.
+ * If true, it should perform its interaction with the user through the
+ * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
+ */
+ public boolean isVoiceInteraction() {
+ return mVoiceInteractor != null;
+ }
+
+ /**
+ * Retrieve the active {@link VoiceInteractor} that the user is going through to
+ * interact with this activity.
+ */
+ public VoiceInteractor getVoiceInteractor() {
+ return mVoiceInteractor;
+ }
+
+ /**
* This is called for activities that set launchMode to "singleTop" in
* their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
* flag when calling {@link #startActivity}. In either case, when the
@@ -5397,7 +5417,7 @@
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config) {
attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
- lastNonConfigurationInstances, config, null);
+ lastNonConfigurationInstances, config, null, null);
}
final void attach(Context context, ActivityThread aThread,
@@ -5405,7 +5425,7 @@
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
- Configuration config, Bundle options) {
+ Configuration config, Bundle options, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5433,6 +5453,8 @@
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
+ mVoiceInteractor = voiceInteractor != null
+ ? new VoiceInteractor(this, this, voiceInteractor, Looper.myLooper()) : null;
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c027e99..018e949 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -76,6 +76,13 @@
public static final String META_HOME_ALTERNATE = "android.app.home.alternate";
/**
+ * Result for IActivityManager.startActivity: trying to start an activity under voice
+ * control when that activity does not support the VOICE category.
+ * @hide
+ */
+ public static final int START_NOT_VOICE_COMPATIBLE = -7;
+
+ /**
* Result for IActivityManager.startActivity: an error where the
* start had to be canceled.
* @hide
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 10831f2..b1c37de 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -43,9 +43,11 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
+import android.service.voice.IVoiceInteractionSession;
import android.text.TextUtils;
import android.util.Log;
import android.util.Singleton;
+import com.android.internal.app.IVoiceInteractor;
import java.util.ArrayList;
import java.util.List;
@@ -242,6 +244,33 @@
return true;
}
+ case START_VOICE_ACTIVITY_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ String callingPackage = data.readString();
+ int callingPid = data.readInt();
+ int callingUid = data.readInt();
+ Intent intent = Intent.CREATOR.createFromParcel(data);
+ String resolvedType = data.readString();
+ IVoiceInteractionSession session = IVoiceInteractionSession.Stub.asInterface(
+ data.readStrongBinder());
+ IVoiceInteractor interactor = IVoiceInteractor.Stub.asInterface(
+ data.readStrongBinder());
+ int startFlags = data.readInt();
+ String profileFile = data.readString();
+ ParcelFileDescriptor profileFd = data.readInt() != 0
+ ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
+ int userId = data.readInt();
+ int result = startVoiceActivity(callingPackage, callingPid, callingUid,
+ intent, resolvedType, session, interactor, startFlags,
+ profileFile, profileFd, options, userId);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
+
case START_NEXT_MATCHING_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -2323,6 +2352,42 @@
data.recycle();
return result;
}
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(callingPackage);
+ data.writeInt(callingPid);
+ data.writeInt(callingUid);
+ intent.writeToParcel(data, 0);
+ data.writeString(resolvedType);
+ data.writeStrongBinder(session.asBinder());
+ data.writeStrongBinder(interactor.asBinder());
+ data.writeInt(startFlags);
+ data.writeString(profileFile);
+ if (profileFd != null) {
+ data.writeInt(1);
+ profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ data.writeInt(0);
+ }
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ data.writeInt(userId);
+ mRemote.transact(START_VOICE_ACTIVITY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int result = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 88eae7f..7dc21b4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -94,6 +94,7 @@
import android.renderscript.RenderScript;
import android.security.AndroidKeyStoreProvider;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
@@ -265,6 +266,7 @@
IBinder token;
int ident;
Intent intent;
+ IVoiceInteractor voiceInteractor;
Bundle state;
Activity activity;
Window window;
@@ -603,6 +605,7 @@
// activity itself back to the activity manager. (matters more with ipc)
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
+ IVoiceInteractor voiceInteractor,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
@@ -615,6 +618,7 @@
r.token = token;
r.ident = ident;
r.intent = intent;
+ r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
@@ -2197,7 +2201,8 @@
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstances, config, options);
+ r.embeddedID, r.lastNonConfigurationInstances, config, options,
+ r.voiceInteractor);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -3003,7 +3008,9 @@
int h;
if (w < 0) {
Resources res = r.activity.getResources();
- if (SystemProperties.getBoolean("persist.recents.use_alternate", false)) {
+ Configuration config = res.getConfiguration();
+ boolean useAlternateRecents = (config.smallestScreenWidthDp < 600);
+ if (useAlternateRecents) {
int wId = com.android.internal.R.dimen.recents_thumbnail_width;
int hId = com.android.internal.R.dimen.recents_thumbnail_height;
mThumbnailWidth = w = res.getDimensionPixelSize(wId);
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index edf21dd..a810134 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -147,6 +147,7 @@
if (mSurface != null) {
mActivityContainer.startActivity(intent);
} else {
+ mActivityContainer.checkEmbeddedAllowed(intent);
mQueuedIntent = intent;
mQueuedPendingIntent = null;
}
@@ -162,6 +163,7 @@
if (mSurface != null) {
mActivityContainer.startActivityIntentSender(iIntentSender);
} else {
+ mActivityContainer.checkEmbeddedAllowedIntentSender(iIntentSender);
mQueuedPendingIntent = iIntentSender;
mQueuedIntent = null;
}
@@ -177,6 +179,7 @@
if (mSurface != null) {
mActivityContainer.startActivityIntentSender(iIntentSender);
} else {
+ mActivityContainer.checkEmbeddedAllowedIntentSender(iIntentSender);
mQueuedPendingIntent = iIntentSender;
mQueuedIntent = null;
}
@@ -326,6 +329,24 @@
}
}
+ void checkEmbeddedAllowed(Intent intent) {
+ try {
+ mIActivityContainer.checkEmbeddedAllowed(intent);
+ } catch (RemoteException e) {
+ throw new RuntimeException(
+ "ActivityView: Unable to startActivity from Intent. " + e);
+ }
+ }
+
+ void checkEmbeddedAllowedIntentSender(IIntentSender intentSender) {
+ try {
+ mIActivityContainer.checkEmbeddedAllowedIntentSender(intentSender);
+ } catch (RemoteException e) {
+ throw new RuntimeException(
+ "ActivityView: Unable to startActivity from IntentSender. " + e);
+ }
+ }
+
int getDisplayId() {
try {
return mIActivityContainer.getDisplayId();
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index f1c632e..fcc7f8e 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -33,6 +33,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import com.android.internal.app.IVoiceInteractor;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -136,6 +137,8 @@
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
+ IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
+ data.readStrongBinder());
int procState = data.readInt();
Bundle state = data.readBundle();
List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
@@ -147,7 +150,8 @@
? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
boolean autoStopProfiler = data.readInt() != 0;
Bundle resumeArgs = data.readBundle();
- scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state,
+ scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo,
+ voiceInteractor, procState, state,
ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
resumeArgs);
return true;
@@ -735,6 +739,7 @@
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
+ IVoiceInteractor voiceInteractor,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
@@ -748,6 +753,7 @@
info.writeToParcel(data, 0);
curConfig.writeToParcel(data, 0);
compatInfo.writeToParcel(data, 0);
+ data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
data.writeInt(procState);
data.writeBundle(state);
data.writeTypedList(pendingResults);
diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl
index cc3b10c..52884f7 100644
--- a/core/java/android/app/IActivityContainer.aidl
+++ b/core/java/android/app/IActivityContainer.aidl
@@ -29,6 +29,8 @@
void setSurface(in Surface surface, int width, int height, int density);
int startActivity(in Intent intent);
int startActivityIntentSender(in IIntentSender intentSender);
+ void checkEmbeddedAllowed(in Intent intent);
+ void checkEmbeddedAllowedIntentSender(in IIntentSender intentSender);
int getDisplayId();
boolean injectEvent(in InputEvent event);
void release();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 52003f1..6b94c4e 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -47,6 +47,8 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import java.util.List;
@@ -77,6 +79,10 @@
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
int flagsMask, int flagsValues, Bundle options) throws RemoteException;
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int flags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) throws RemoteException;
public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask)
@@ -733,4 +739,5 @@
int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215;
int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216;
int SET_ACTIVITY_LABEL_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
+ int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index ac8ac8f..f290e94 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -31,6 +31,8 @@
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import java.io.FileDescriptor;
import java.util.List;
@@ -55,8 +57,9 @@
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- int procState, Bundle state, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed,
+ boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle resumeArgs)
throws RemoteException;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 8681f5c..ad4027d 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -18,11 +18,15 @@
package android.app;
import android.app.ITransientNotification;
-import android.service.notification.StatusBarNotification;
import android.app.Notification;
import android.content.ComponentName;
import android.content.Intent;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
+import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
/** {@hide} */
@@ -53,4 +57,7 @@
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
+ oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
+ oneway void requestZenModeConditions(in IConditionListener callback, boolean requested);
+ oneway void setZenModeCondition(in Uri conditionId);
}
\ No newline at end of file
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 028fa68..e58ccb8 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1428,7 +1428,7 @@
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * Like {@link #execStartActivity},
* but accepts an array of activities to be started. Note that active
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
@@ -1442,7 +1442,7 @@
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * Like {@link #execStartActivity},
* but accepts an array of activities to be started. Note that active
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
@@ -1545,8 +1545,7 @@
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
- * but for starting as a particular user.
+ * Like {@link #execStartActivity}, but for starting as a particular user.
*
* @param who The Context from which the activity is being started.
* @param contextThread The main thread of the Context from which the activity
@@ -1616,7 +1615,8 @@
mUiAutomationConnection = uiAutomationConnection;
}
- /*package*/ static void checkStartActivityResult(int res, Object intent) {
+ /** @hide */
+ public static void checkStartActivityResult(int res, Object intent) {
if (res >= ActivityManager.START_SUCCESS) {
return;
}
@@ -1640,6 +1640,9 @@
case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
+ case ActivityManager.START_NOT_VOICE_COMPATIBLE:
+ throw new SecurityException(
+ "Starting under voice control not allowed for: " + intent);
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index fe629f6..25a1493 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -328,8 +328,8 @@
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
- * set if you want the sound and/or vibration play each time the
- * notification is sent, even if it has not been canceled before that.
+ * set if you would only like the sound, vibrate and ticker to be played
+ * if the notification was not already showing.
*/
public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008;
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 1b838fb..4427ce1 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -19,6 +19,9 @@
import android.content.SharedPreferences;
import android.os.FileUtils;
import android.os.Looper;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
import android.util.Log;
import com.google.android.collect.Maps;
@@ -43,10 +46,7 @@
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
-import libcore.io.StructStat;
final class SharedPreferencesImpl implements SharedPreferences {
private static final String TAG = "SharedPreferencesImpl";
@@ -110,7 +110,7 @@
Map map = null;
StructStat stat = null;
try {
- stat = Libcore.os.stat(mFile.getPath());
+ stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
@@ -172,7 +172,7 @@
* violation, but we explicitly want this one.
*/
BlockGuard.getThreadPolicy().onReadFromDisk();
- stat = Libcore.os.stat(mFile.getPath());
+ stat = Os.stat(mFile.getPath());
} catch (ErrnoException e) {
return true;
}
@@ -599,7 +599,7 @@
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
- final StructStat stat = Libcore.os.stat(mFile.getPath());
+ final StructStat stat = Os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
new file mode 100644
index 0000000..6dc48b0
--- /dev/null
+++ b/core/java/android/app/VoiceInteractor.java
@@ -0,0 +1,275 @@
+/*
+ * 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.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;
+
+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 Activity mActivity;
+ final IVoiceInteractor mInteractor;
+ final HandlerCaller mHandlerCaller;
+ final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
+ @Override
+ public void executeMessage(Message msg) {
+ SomeArgs args = (SomeArgs)msg.obj;
+ Request request;
+ switch (msg.what) {
+ case MSG_CONFIRMATION_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+ if (DEBUG) Log.d(TAG, "onConfirmResult: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " confirmed=" + msg.arg1 + " result=" + args.arg2);
+ if (request != null) {
+ ((ConfirmationRequest)request).onConfirmationResult(msg.arg1 != 0,
+ (Bundle) args.arg2);
+ request.clear();
+ }
+ break;
+ case MSG_COMMAND_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
+ if (DEBUG) Log.d(TAG, "onCommandResult: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " result=" + args.arg2);
+ if (request != null) {
+ ((CommandRequest)request).onCommandResult((Bundle) args.arg2);
+ if (msg.arg1 != 0) {
+ request.clear();
+ }
+ }
+ break;
+ case MSG_CANCEL_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+ if (DEBUG) Log.d(TAG, "onCancelResult: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request);
+ if (request != null) {
+ request.onCancel();
+ request.clear();
+ }
+ break;
+ }
+ }
+ };
+
+ final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() {
+ @Override
+ public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+ Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
+ MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, request, result));
+ }
+
+ @Override
+ public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
+ Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
+ MSG_COMMAND_RESULT, complete ? 1 : 0, request, result));
+ }
+
+ @Override
+ public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(
+ MSG_CANCEL_RESULT, request));
+ }
+ };
+
+ final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<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 abstract class Request {
+ IVoiceInteractorRequest mRequestInterface;
+ Context mContext;
+ Activity mActivity;
+
+ public Request() {
+ }
+
+ public void cancel() {
+ try {
+ mRequestInterface.cancel();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Voice interactor has died", e);
+ }
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Activity getActivity() {
+ return mActivity;
+ }
+
+ public void onCancel() {
+ }
+
+ void clear() {
+ mRequestInterface = null;
+ mContext = null;
+ mActivity = null;
+ }
+
+ abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor,
+ String packageName, IVoiceInteractorCallback callback) throws RemoteException;
+ }
+
+ public static class ConfirmationRequest extends Request {
+ final CharSequence mPrompt;
+ final Bundle mExtras;
+
+ /**
+ * 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 through an asynchronous call to
+ * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or
+ * {@link #onCancel()}.
+ *
+ * <p>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 a confirmation.
+ * @param prompt Optional confirmation text to read to the user as the action being
+ * confirmed.
+ * @param extras Additional optional information.
+ */
+ public ConfirmationRequest(CharSequence prompt, Bundle extras) {
+ mPrompt = prompt;
+ mExtras = extras;
+ }
+
+ public void onConfirmationResult(boolean confirmed, Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras);
+ }
+ }
+
+ public static class CommandRequest extends Request {
+ final String mCommand;
+ final Bundle mArgs;
+
+ /**
+ * Execute 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 result of the confirmation will be returned through an asynchronous call to
+ * either {@link #onCommandResult(android.os.Bundle)} or
+ * {@link #onCancel()}.
+ *
+ * <p>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.)
+ *
+ * @param command The desired command to perform.
+ * @param args Additional arguments to control execution of the command.
+ */
+ public CommandRequest(String command, Bundle args) {
+ mCommand = command;
+ mArgs = args;
+ }
+
+ public void onCommandResult(Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startConfirmation(packageName, callback, mCommand, mArgs);
+ }
+ }
+
+ VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor,
+ Looper looper) {
+ mContext = context;
+ mActivity = activity;
+ mInteractor = interactor;
+ mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
+ }
+
+ Request pullRequest(IVoiceInteractorRequest request, boolean complete) {
+ synchronized (mActiveRequests) {
+ Request req = mActiveRequests.get(request.asBinder());
+ if (req != null && complete) {
+ mActiveRequests.remove(request.asBinder());
+ }
+ return req;
+ }
+ }
+
+ public boolean submitRequest(Request request) {
+ try {
+ IVoiceInteractorRequest ireq = request.submit(mInteractor,
+ mContext.getOpPackageName(), mCallback);
+ request.mRequestInterface = ireq;
+ request.mContext = mContext;
+ request.mActivity = mActivity;
+ synchronized (mActiveRequests) {
+ mActiveRequests.put(ireq.asBinder(), request);
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Remove voice interactor service died", e);
+ return false;
+ }
+ }
+
+ /**
+ * Queries the supported commands available from the VoiceinteractionService.
+ * The command is a string that describes the generic operation to be performed.
+ * An example might be "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number
+ * of bags as part of airline check-in. (This is not an actual working example.)
+ *
+ * @param commands
+ */
+ public boolean[] supportsCommands(String[] commands) {
+ try {
+ boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands);
+ if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res);
+ return res;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Voice interactor has died", e);
+ }
+ }
+}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e6e0f35..5f8ebbe 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,6 +16,7 @@
package android.app;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -44,10 +45,14 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.Log;
import android.view.WindowManagerGlobal;
import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -65,6 +70,11 @@
private float mWallpaperXStep = -1;
private float mWallpaperYStep = -1;
+ /** {@hide} */
+ private static final String PROP_WALLPAPER = "ro.config.wallpaper";
+ /** {@hide} */
+ private static final String PROP_WALLPAPER_COMPONENT = "ro.config.wallpaper_component";
+
/**
* Activity Action: Show settings for choosing wallpaper. Do not use directly to construct
* an intent; instead, use {@link #getCropAndSetWallpaperIntent}.
@@ -269,13 +279,15 @@
}
private Bitmap getCurrentWallpaperLocked(Context context) {
+ if (mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return null;
+ }
+
try {
Bundle params = new Bundle();
ParcelFileDescriptor fd = mService.getWallpaper(this, params);
if (fd != null) {
- int width = params.getInt("width", 0);
- int height = params.getInt("height", 0);
-
try {
BitmapFactory.Options options = new BitmapFactory.Options();
return BitmapFactory.decodeFileDescriptor(
@@ -297,28 +309,20 @@
}
private Bitmap getDefaultWallpaperLocked(Context context) {
- try {
- InputStream is = context.getResources().openRawResource(
- com.android.internal.R.drawable.default_wallpaper);
- if (is != null) {
- int width = mService.getWidthHint();
- int height = mService.getHeightHint();
-
+ InputStream is = openDefaultWallpaper(context);
+ if (is != null) {
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ return BitmapFactory.decodeStream(is, null, options);
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "Can't decode stream", e);
+ } finally {
try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- return BitmapFactory.decodeStream(is, null, options);
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "Can't decode stream", e);
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- // Ignore
- }
+ is.close();
+ } catch (IOException e) {
+ // Ignore
}
}
- } catch (RemoteException e) {
- // Ignore
}
return null;
}
@@ -403,8 +407,7 @@
horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment));
verticalAlignment = Math.max(0, Math.min(1, verticalAlignment));
- InputStream is = new BufferedInputStream(
- resources.openRawResource(com.android.internal.R.drawable.default_wallpaper));
+ InputStream is = new BufferedInputStream(openDefaultWallpaper(mContext));
if (is == null) {
Log.e(TAG, "default wallpaper input stream is null");
@@ -429,8 +432,7 @@
}
}
- is = new BufferedInputStream(resources.openRawResource(
- com.android.internal.R.drawable.default_wallpaper));
+ is = new BufferedInputStream(openDefaultWallpaper(mContext));
RectF cropRectF;
@@ -479,8 +481,7 @@
if (crop == null) {
// BitmapRegionDecoder has failed, try to crop in-memory
- is = new BufferedInputStream(resources.openRawResource(
- com.android.internal.R.drawable.default_wallpaper));
+ is = new BufferedInputStream(openDefaultWallpaper(mContext));
Bitmap fullSize = null;
if (is != null) {
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -1009,6 +1010,53 @@
* wallpaper.
*/
public void clear() throws IOException {
- setResource(com.android.internal.R.drawable.default_wallpaper);
+ setStream(openDefaultWallpaper(mContext));
+ }
+
+ /**
+ * Open stream representing the default static image wallpaper.
+ *
+ * @hide
+ */
+ public static InputStream openDefaultWallpaper(Context context) {
+ final String path = SystemProperties.get(PROP_WALLPAPER);
+ if (!TextUtils.isEmpty(path)) {
+ final File file = new File(path);
+ if (file.exists()) {
+ try {
+ return new FileInputStream(file);
+ } catch (IOException e) {
+ // Ignored, fall back to platform default below
+ }
+ }
+ }
+ return context.getResources().openRawResource(
+ com.android.internal.R.drawable.default_wallpaper);
+ }
+
+ /**
+ * Return {@link ComponentName} of the default live wallpaper, or
+ * {@code null} if none is defined.
+ *
+ * @hide
+ */
+ public static ComponentName getDefaultWallpaperComponent(Context context) {
+ String flat = SystemProperties.get(PROP_WALLPAPER_COMPONENT);
+ if (!TextUtils.isEmpty(flat)) {
+ final ComponentName cn = ComponentName.unflattenFromString(flat);
+ if (cn != null) {
+ return cn;
+ }
+ }
+
+ flat = context.getString(com.android.internal.R.string.default_wallpaper_component);
+ if (!TextUtils.isEmpty(flat)) {
+ final ComponentName cn = ComponentName.unflattenFromString(flat);
+ if (cn != null) {
+ return cn;
+ }
+ }
+
+ return null;
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d7170e8..68ab611 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -33,6 +33,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.service.trust.TrustAgentService;
import android.util.Log;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -1267,7 +1268,7 @@
public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
/**
- * Disable all keyguard widgets
+ * Disable all keyguard widgets. Has no effect.
*/
public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1 << 0;
@@ -1277,6 +1278,22 @@
public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 1 << 1;
/**
+ * Disable showing all notifications on secure keyguard screens (e.g. PIN/Pattern/Password)
+ */
+ public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 1 << 2;
+
+ /**
+ * Only allow redacted notifications on secure keyguard screens (e.g. PIN/Pattern/Password)
+ */
+ public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 1 << 3;
+
+ /**
+ * Ignore {@link TrustAgentService} state on secure keyguard screens
+ * (e.g. PIN/Pattern/Password).
+ */
+ public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4;
+
+ /**
* Disable all current and future keyguard customizations.
*/
public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
@@ -1496,7 +1513,8 @@
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param which {@link #KEYGUARD_DISABLE_FEATURES_NONE} (default),
* {@link #KEYGUARD_DISABLE_WIDGETS_ALL}, {@link #KEYGUARD_DISABLE_SECURE_CAMERA},
- * {@link #KEYGUARD_DISABLE_FEATURES_ALL}
+ * {@link #KEYGUARD_DISABLE_SECURE_NOTIFICATIONS}, {@link #KEYGUARD_DISABLE_TRUST_AGENTS},
+ * {@link #KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS}, {@link #KEYGUARD_DISABLE_FEATURES_ALL}
*/
public void setKeyguardDisabledFeatures(ComponentName admin, int which) {
if (mService != null) {
@@ -1957,4 +1975,48 @@
}
return null;
}
+
+ /**
+ * Called by a profile or device owner to set a user restriction specified
+ * by the key.
+ * <p>
+ * The calling device admin must be a profile or device owner; if it is not,
+ * a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated
+ * with.
+ * @param key The key of the restriction. See the constants in
+ * {@link android.os.UserManager} for the list of keys.
+ */
+ public void addUserRestriction(ComponentName admin, String key) {
+ if (mService != null) {
+ try {
+ mService.setUserRestriction(admin, key, true);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile or device owner to clear a user restriction specified
+ * by the key.
+ * <p>
+ * The calling device admin must be a profile or device owner; if it is not,
+ * a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated
+ * with.
+ * @param key The key of the restriction. See the constants in
+ * {@link android.os.UserManager} for the list of keys.
+ */
+ public void clearUserRestriction(ComponentName admin, String key) {
+ if (mService != null) {
+ try {
+ mService.setUserRestriction(admin, key, false);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 85ba58b..72b3c20 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -119,4 +119,6 @@
void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+
+ void setUserRestriction(in ComponentName who, in String key, boolean enable);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 3c31f8d..886f1a6 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -29,6 +29,10 @@
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
import android.util.Log;
import java.io.File;
@@ -38,11 +42,6 @@
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-import libcore.io.OsConstants;
-import libcore.io.StructStat;
-
/**
* Provides the central interface between an
* application and Android's data backup infrastructure. An application that wishes
@@ -195,7 +194,7 @@
* the key supplied as part of the entity. Writing an entity with a negative
* data size instructs the transport to delete whatever entity currently exists
* under that key from the remote data set.
- *
+ *
* @param oldState An open, read-only ParcelFileDescriptor pointing to the
* last backup state provided by the application. May be
* <code>null</code>, in which case no prior state is being
@@ -226,7 +225,7 @@
* onRestore() throws an exception, the OS will assume that the
* application's data may now be in an incoherent state, and will clear it
* before proceeding.
- *
+ *
* @param data A structured wrapper around an open, read-only
* file descriptor pointing to a full snapshot of the
* application's data. The application should consume every
@@ -416,7 +415,7 @@
}
// If it's a directory, enqueue its contents for scanning.
- StructStat stat = Libcore.os.lstat(filePath);
+ StructStat stat = Os.lstat(filePath);
if (OsConstants.S_ISLNK(stat.st_mode)) {
if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
continue;
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index cfd0a65..6ebb6c4 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -17,6 +17,8 @@
package android.app.backup;
import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import java.io.File;
@@ -24,9 +26,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-
/**
* Global constant definitions et cetera related to the full-backup-to-fd
* binary format. Nothing in this namespace is part of any API; it's all
@@ -147,7 +146,7 @@
try {
// explicitly prevent emplacement of files accessible by outside apps
mode &= 0700;
- Libcore.os.chmod(outFile.getPath(), (int)mode);
+ Os.chmod(outFile.getPath(), (int)mode);
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cbb6cf5..de223a3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2442,6 +2442,14 @@
public static final String APPWIDGET_SERVICE = "appwidget";
/**
+ * Official published name of the (internal) voice interaction manager service.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
+
+ /**
* Use with {@link #getSystemService} to retrieve an
* {@link android.app.backup.IBackupManager IBackupManager} for communicating
* with the backup mechanism.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c0f04af..ae5437b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2804,6 +2804,14 @@
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
/**
+ * Categories for activities that can participate in voice interaction.
+ * An activity that supports this category must be prepared to run with
+ * no UI shown at all (though in some case it may have a UI shown), and
+ * rely on {@link android.app.VoiceInteractor} to interact with the user.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
+ /**
* Set if the activity should be considered as an alternative action to
* the data the user is currently viewing. See also
* {@link #CATEGORY_SELECTED_ALTERNATIVE} for an alternative action that
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ae0899f..488e25f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -74,6 +74,9 @@
ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
+ boolean activitySupportsIntent(in ComponentName className, in Intent intent,
+ String resolvedType);
+
ActivityInfo getReceiverInfo(in ComponentName className, int flags, int userId);
ServiceInfo getServiceInfo(in ComponentName className, int flags, int userId);
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 92b9146..9087338 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -19,6 +19,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Bitmap.Config;
@@ -37,6 +39,8 @@
*/
public class LauncherActivityInfo {
private static final boolean DEBUG = false;
+ private static final String TAG = "LauncherActivityInfo";
+
private final PackageManager mPm;
private final UserManager mUm;
@@ -121,25 +125,42 @@
}
/**
+ * Returns the name for the acitivty from android:name in the manifest.
+ * @return the name from android:name for the acitivity.
+ */
+ public String getName() {
+ return mActivityInfo.name;
+ }
+
+ /**
* Returns the activity icon with badging appropriate for the profile.
* @param density Optional density for the icon, or 0 to use the default density.
* @return A badged icon for the activity.
*/
public Drawable getBadgedIcon(int density) {
- // TODO: Handle density
- if (mUser.equals(android.os.Process.myUserHandle())) {
- return mActivityInfo.loadIcon(mPm);
- }
- Drawable originalIcon = mActivityInfo.loadIcon(mPm);
- if (originalIcon == null) {
- if (DEBUG) {
- Log.w("LauncherActivityInfo", "Couldn't find icon for activity");
+ int iconRes = mActivityInfo.getIconResource();
+ Resources resources = null;
+ Drawable originalIcon = null;
+ try {
+ resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
+ try {
+ if (density != 0) {
+ originalIcon = resources.getDrawableForDensity(iconRes, density);
+ }
+ } catch (Resources.NotFoundException e) {
}
- originalIcon = mPm.getDefaultActivityIcon();
+ } catch (NameNotFoundException nnfe) {
}
+
+ if (originalIcon == null) {
+ originalIcon = mActivityInfo.loadIcon(mPm);
+ }
+
if (originalIcon instanceof BitmapDrawable) {
return mUm.getBadgedDrawableForUser(
originalIcon, mUser);
+ } else {
+ Log.e(TAG, "Unable to create badged icon for " + mActivityInfo);
}
return originalIcon;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d981cc1..484a2a1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1235,7 +1235,6 @@
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
-
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports app widgets.
@@ -1244,6 +1243,17 @@
public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets";
/**
+ * @hide
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports
+ * {@link android.service.voice.VoiceInteractionService} and
+ * {@link android.app.VoiceInteractor}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports a home screen that is replaceable
* by third party applications.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8b6ca83..080b37b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -50,6 +50,7 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
@@ -147,8 +148,7 @@
private String[] mSeparateProcesses;
private boolean mOnlyCoreApps;
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
- private static final String SDK_CODENAME = "REL".equals(Build.VERSION.CODENAME)
- ? null : Build.VERSION.CODENAME;
+ private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
private int mParseError = PackageManager.INSTALL_SUCCEEDED;
@@ -459,7 +459,7 @@
return pi;
}
- private Certificate[] loadCertificates(StrictJarFile jarFile, ZipEntry je,
+ private Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
byte[] readBuffer) {
try {
// We must read the stream for the JarEntry to retrieve
@@ -469,7 +469,7 @@
// not using
}
is.close();
- return je != null ? jarFile.getCertificates(je) : null;
+ return je != null ? jarFile.getCertificateChains(je) : null;
} catch (IOException e) {
Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e);
} catch (RuntimeException e) {
@@ -632,7 +632,7 @@
try {
StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath);
- Certificate[] certs = null;
+ Certificate[][] certs = null;
if ((flags&PARSE_IS_SYSTEM) != 0) {
// If this package comes from the system image, then we
@@ -656,8 +656,8 @@
final int N = certs.length;
for (int i=0; i<N; i++) {
Slog.i(TAG, " Public key: "
- + certs[i].getPublicKey().getEncoded()
- + " " + certs[i].getPublicKey());
+ + certs[i][0].getPublicKey().getEncoded()
+ + " " + certs[i][0].getPublicKey());
}
}
}
@@ -677,7 +677,7 @@
ManifestDigest.fromInputStream(jarFile.getInputStream(je));
}
- final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
+ final Certificate[][] localCerts = loadCertificates(jarFile, je, readBuffer);
if (DEBUG_JAR) {
Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
+ ": certs=" + certs + " ("
@@ -726,8 +726,7 @@
final int N = certs.length;
pkg.mSignatures = new Signature[certs.length];
for (int i=0; i<N; i++) {
- pkg.mSignatures[i] = new Signature(
- certs[i].getEncoded());
+ pkg.mSignatures[i] = new Signature(certs[i]);
}
} else {
Slog.e(TAG, "Package " + pkg.packageName
@@ -739,7 +738,7 @@
// Add the signing KeySet to the system
pkg.mSigningKeys = new HashSet<PublicKey>();
for (int i=0; i < certs.length; i++) {
- pkg.mSigningKeys.add(certs[i].getPublicKey());
+ pkg.mSigningKeys.add(certs[i][0].getPublicKey());
}
} catch (CertificateEncodingException e) {
@@ -1200,10 +1199,18 @@
sa.recycle();
if (minCode != null) {
- if (!minCode.equals(SDK_CODENAME)) {
- if (SDK_CODENAME != null) {
+ boolean allowedCodename = false;
+ for (String codename : SDK_CODENAMES) {
+ if (minCode.equals(codename)) {
+ allowedCodename = true;
+ break;
+ }
+ }
+ if (!allowedCodename) {
+ if (SDK_CODENAMES.length > 0) {
outError[0] = "Requires development platform " + minCode
- + " (current platform is " + SDK_CODENAME + ")";
+ + " (current platform is any of "
+ + Arrays.toString(SDK_CODENAMES) + ")";
} else {
outError[0] = "Requires development platform " + minCode
+ " but this is a release platform.";
@@ -1219,10 +1226,18 @@
}
if (targetCode != null) {
- if (!targetCode.equals(SDK_CODENAME)) {
- if (SDK_CODENAME != null) {
+ boolean allowedCodename = false;
+ for (String codename : SDK_CODENAMES) {
+ if (targetCode.equals(codename)) {
+ allowedCodename = true;
+ break;
+ }
+ }
+ if (!allowedCodename) {
+ if (SDK_CODENAMES.length > 0) {
outError[0] = "Requires development platform " + targetCode
- + " (current platform is " + SDK_CODENAME + ")";
+ + " (current platform is any of "
+ + Arrays.toString(SDK_CODENAMES) + ")";
} else {
outError[0] = "Requires development platform " + targetCode
+ " but this is a release platform.";
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 752bf8b..f4e7dc3 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -25,6 +25,7 @@
import java.lang.ref.SoftReference;
import java.security.PublicKey;
import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
@@ -38,12 +39,28 @@
private int mHashCode;
private boolean mHaveHashCode;
private SoftReference<String> mStringRef;
+ private Certificate[] mCertificateChain;
/**
* Create Signature from an existing raw byte array.
*/
public Signature(byte[] signature) {
mSignature = signature.clone();
+ mCertificateChain = null;
+ }
+
+ /**
+ * Create signature from a certificate chain. Used for backward
+ * compatibility.
+ *
+ * @throws CertificateEncodingException
+ * @hide
+ */
+ public Signature(Certificate[] certificateChain) throws CertificateEncodingException {
+ mSignature = certificateChain[0].getEncoded();
+ if (certificateChain.length > 1) {
+ mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length);
+ }
}
private static final int parseHexDigit(int nibble) {
@@ -156,6 +173,29 @@
return cert.getPublicKey();
}
+ /**
+ * Used for compatibility code that needs to check the certificate chain
+ * during upgrades.
+ *
+ * @throws CertificateEncodingException
+ * @hide
+ */
+ public Signature[] getChainSignatures() throws CertificateEncodingException {
+ if (mCertificateChain == null) {
+ return new Signature[] { this };
+ }
+
+ Signature[] chain = new Signature[1 + mCertificateChain.length];
+ chain[0] = this;
+
+ int i = 1;
+ for (Certificate c : mCertificateChain) {
+ chain[i++] = new Signature(c.getEncoded());
+ }
+
+ return chain;
+ }
+
@Override
public boolean equals(Object obj) {
try {
diff --git a/core/java/android/ddm/DdmHandleHeap.java b/core/java/android/ddm/DdmHandleHeap.java
index cece556..e24aeb2 100644
--- a/core/java/android/ddm/DdmHandleHeap.java
+++ b/core/java/android/ddm/DdmHandleHeap.java
@@ -219,7 +219,7 @@
if (false)
Log.d("ddm-heap", "Heap GC request");
- System.gc();
+ Runtime.getRuntime().gc();
return null; // empty response
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index ee2adac..40a7905 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -157,7 +157,14 @@
mCameraId = cameraId;
mDeviceListener = listener;
mDeviceHandler = handler;
- TAG = String.format("CameraDevice-%s-JV", mCameraId);
+
+ final int MAX_TAG_LEN = 23;
+ String tag = String.format("CameraDevice-JV-%s", mCameraId);
+ if (tag.length() > MAX_TAG_LEN) {
+ tag = tag.substring(0, MAX_TAG_LEN);
+ }
+ TAG = tag;
+
DEBUG = Log.isLoggable(TAG, Log.DEBUG);
}
diff --git a/core/java/android/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java
index 21de9f5..4c074e9 100644
--- a/core/java/android/hardware/location/GeofenceHardware.java
+++ b/core/java/android/hardware/location/GeofenceHardware.java
@@ -79,7 +79,7 @@
*/
public static final int MONITOR_UNSUPPORTED = 2;
- // The following constants need to match geofence flags in gps.h
+ // The following constants need to match geofence flags in gps.h and fused_location.h
/**
* The constant to indicate that the user has entered the geofence.
*/
@@ -92,7 +92,7 @@
/**
* The constant to indicate that the user is uncertain with respect to a
- * geofence. nn
+ * geofence.
*/
public static final int GEOFENCE_UNCERTAIN = 1<<2L;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c51d1a7..505ef9c 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -255,7 +255,9 @@
public static final int CURSOR_ANCHOR_MONITOR_MODE_NONE = 0x0;
/**
- * The IME expects that {@link #onUpdateCursor(Rect)} is called back.
+ * Passing this flag into a call to {@link #setCursorAnchorMonitorMode(int)} will result in
+ * the cursor rectangle being provided in screen coordinates to subsequent
+ * {@link #onUpdateCursor(Rect)} callbacks.
*/
public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 0x1;
@@ -1703,9 +1705,11 @@
}
/**
- * Called when the application has reported a new location of its text
- * cursor. This is only called if explicitly requested by the input method.
- * The default implementation does nothing.
+ * Called when the application has reported a new location of its text cursor. This is only
+ * called if explicitly requested by the input method. The default implementation does nothing.
+ * @param newCursor The new cursor position, in screen coordinates if the input method calls
+ * {@link #setCursorAnchorMonitorMode} with {@link #CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT}.
+ * Otherwise, this is in local coordinates.
*/
public void onUpdateCursor(Rect newCursor) {
// Intentionally empty
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index 22543e3..a725bec 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -24,13 +24,13 @@
import java.net.InterfaceAddress;
import java.net.UnknownHostException;
-import static libcore.io.OsConstants.IFA_F_DADFAILED;
-import static libcore.io.OsConstants.IFA_F_DEPRECATED;
-import static libcore.io.OsConstants.IFA_F_TENTATIVE;
-import static libcore.io.OsConstants.RT_SCOPE_HOST;
-import static libcore.io.OsConstants.RT_SCOPE_LINK;
-import static libcore.io.OsConstants.RT_SCOPE_SITE;
-import static libcore.io.OsConstants.RT_SCOPE_UNIVERSE;
+import static android.system.OsConstants.IFA_F_DADFAILED;
+import static android.system.OsConstants.IFA_F_DEPRECATED;
+import static android.system.OsConstants.IFA_F_TENTATIVE;
+import static android.system.OsConstants.RT_SCOPE_HOST;
+import static android.system.OsConstants.RT_SCOPE_LINK;
+import static android.system.OsConstants.RT_SCOPE_SITE;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
/**
* Identifies an IP address on a network link.
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index 119e533..643e8c2 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -22,9 +22,9 @@
import java.io.FileDescriptor;
import java.net.SocketOptions;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-import libcore.io.OsConstants;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
/**
* Socket implementation used for android.net.LocalSocket and
@@ -248,7 +248,7 @@
throw new IllegalStateException("unknown sockType");
}
try {
- fd = Libcore.os.socket(OsConstants.AF_UNIX, osType, 0);
+ fd = Os.socket(OsConstants.AF_UNIX, osType, 0);
mFdCreatedInternally = true;
} catch (ErrnoException e) {
e.rethrowAsIOException();
@@ -268,7 +268,7 @@
return;
}
try {
- Libcore.os.close(fd);
+ Os.close(fd);
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 19be2c8..0336dd6 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -16,12 +16,17 @@
package android.os;
+import android.text.TextUtils;
+import android.util.Slog;
+
import com.android.internal.telephony.TelephonyProperties;
/**
* Information about the current build, extracted from system properties.
*/
public class Build {
+ private static final String TAG = "Build";
+
/** Value used for when a build property is unknown. */
public static final String UNKNOWN = "unknown";
@@ -118,14 +123,22 @@
*/
public static final String CODENAME = getString("ro.build.version.codename");
+ private static final String[] ALL_CODENAMES
+ = getString("ro.build.version.all_codenames").split(",");
+
/**
- * The SDK version to use when accessing resources.
- * Use the current SDK version code. If we are a development build,
- * also allow the previous SDK version + 1.
* @hide
*/
- public static final int RESOURCES_SDK_INT = SDK_INT
- + ("REL".equals(CODENAME) ? 0 : 1);
+ public static final String[] ACTIVE_CODENAMES = "REL".equals(ALL_CODENAMES[0])
+ ? new String[0] : ALL_CODENAMES;
+
+ /**
+ * The SDK version to use when accessing resources.
+ * Use the current SDK version code. For every active development codename
+ * we are operating under, we bump the assumed resource platform version by 1.
+ * @hide
+ */
+ public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
}
/**
@@ -501,7 +514,43 @@
public static final String TAGS = getString("ro.build.tags");
/** A string that uniquely identifies this build. Do not attempt to parse this value. */
- public static final String FINGERPRINT = getString("ro.build.fingerprint");
+ public static final String FINGERPRINT = deriveFingerprint();
+
+ /**
+ * Some devices split the fingerprint components between multiple
+ * partitions, so we might derive the fingerprint at runtime.
+ */
+ private static String deriveFingerprint() {
+ String finger = SystemProperties.get("ro.build.fingerprint");
+ if (TextUtils.isEmpty(finger)) {
+ finger = getString("ro.product.brand") + '/' +
+ getString("ro.product.name") + '/' +
+ getString("ro.product.device") + ':' +
+ getString("ro.build.version.release") + '/' +
+ getString("ro.build.id") + '/' +
+ getString("ro.build.version.incremental") + ':' +
+ getString("ro.build.type") + '/' +
+ getString("ro.build.tags");
+ }
+ return finger;
+ }
+
+ /**
+ * Ensure that raw fingerprint system property is defined. If it was derived
+ * dynamically by {@link #deriveFingerprint()} this is where we push the
+ * derived value into the property service.
+ *
+ * @hide
+ */
+ public static void ensureFingerprintProperty() {
+ if (TextUtils.isEmpty(SystemProperties.get("ro.build.fingerprint"))) {
+ try {
+ SystemProperties.set("ro.build.fingerprint", FINGERPRINT);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Failed to set fingerprint property", e);
+ }
+ }
+ }
// The following properties only make sense for internal engineering builds.
public static final long TIME = getLong("ro.build.date.utc") * 1000;
diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java
index 2ecf317..f83a90b 100644
--- a/core/java/android/os/CommonClock.java
+++ b/core/java/android/os/CommonClock.java
@@ -23,6 +23,7 @@
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
+import static android.system.OsConstants.*;
/**
* Used for accessing the android common time service's common clock and receiving notifications
diff --git a/core/java/android/os/CommonTimeUtils.java b/core/java/android/os/CommonTimeUtils.java
index 20755d9..ba060b8 100644
--- a/core/java/android/os/CommonTimeUtils.java
+++ b/core/java/android/os/CommonTimeUtils.java
@@ -20,7 +20,7 @@
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.Locale;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
class CommonTimeUtils {
/**
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 1089f27..d71c3e6 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -16,12 +16,12 @@
package android.os;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.Log;
import android.util.Slog;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -85,7 +85,7 @@
*/
public static int setPermissions(String path, int mode, int uid, int gid) {
try {
- Libcore.os.chmod(path, mode);
+ Os.chmod(path, mode);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
return e.errno;
@@ -93,7 +93,7 @@
if (uid >= 0 || gid >= 0) {
try {
- Libcore.os.chown(path, uid, gid);
+ Os.chown(path, uid, gid);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to chown(" + path + "): " + e);
return e.errno;
@@ -113,7 +113,7 @@
*/
public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
try {
- Libcore.os.fchmod(fd, mode);
+ Os.fchmod(fd, mode);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to fchmod(): " + e);
return e.errno;
@@ -121,7 +121,7 @@
if (uid >= 0 || gid >= 0) {
try {
- Libcore.os.fchown(fd, uid, gid);
+ Os.fchown(fd, uid, gid);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to fchown(): " + e);
return e.errno;
@@ -136,7 +136,7 @@
*/
public static int getUid(String path) {
try {
- return Libcore.os.stat(path).st_uid;
+ return Os.stat(path).st_uid;
} catch (ErrnoException e) {
return -1;
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 24bf05e..c6b2151 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -16,24 +16,24 @@
package android.os;
-import static libcore.io.OsConstants.AF_UNIX;
-import static libcore.io.OsConstants.SEEK_SET;
-import static libcore.io.OsConstants.SOCK_STREAM;
-import static libcore.io.OsConstants.S_ISLNK;
-import static libcore.io.OsConstants.S_ISREG;
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SEEK_SET;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.S_ISLNK;
+import static android.system.OsConstants.S_ISREG;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
import android.util.Log;
import dalvik.system.CloseGuard;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
import libcore.io.Memory;
-import libcore.io.OsConstants;
-import libcore.io.StructStat;
import java.io.Closeable;
import java.io.File;
@@ -261,7 +261,7 @@
*/
public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException {
try {
- final FileDescriptor fd = Libcore.os.dup(orig);
+ final FileDescriptor fd = Os.dup(orig);
return new ParcelFileDescriptor(fd);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
@@ -297,7 +297,7 @@
original.setInt$(fd);
try {
- final FileDescriptor dup = Libcore.os.dup(original);
+ final FileDescriptor dup = Os.dup(original);
return new ParcelFileDescriptor(dup);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
@@ -359,7 +359,7 @@
*/
public static ParcelFileDescriptor[] createPipe() throws IOException {
try {
- final FileDescriptor[] fds = Libcore.os.pipe();
+ final FileDescriptor[] fds = Os.pipe();
return new ParcelFileDescriptor[] {
new ParcelFileDescriptor(fds[0]),
new ParcelFileDescriptor(fds[1]) };
@@ -381,7 +381,7 @@
public static ParcelFileDescriptor[] createReliablePipe() throws IOException {
try {
final FileDescriptor[] comm = createCommSocketPair();
- final FileDescriptor[] fds = Libcore.os.pipe();
+ final FileDescriptor[] fds = Os.pipe();
return new ParcelFileDescriptor[] {
new ParcelFileDescriptor(fds[0], comm[0]),
new ParcelFileDescriptor(fds[1], comm[1]) };
@@ -398,7 +398,7 @@
try {
final FileDescriptor fd0 = new FileDescriptor();
final FileDescriptor fd1 = new FileDescriptor();
- Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1);
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1);
return new ParcelFileDescriptor[] {
new ParcelFileDescriptor(fd0),
new ParcelFileDescriptor(fd1) };
@@ -421,7 +421,7 @@
final FileDescriptor[] comm = createCommSocketPair();
final FileDescriptor fd0 = new FileDescriptor();
final FileDescriptor fd1 = new FileDescriptor();
- Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1);
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1);
return new ParcelFileDescriptor[] {
new ParcelFileDescriptor(fd0, comm[0]),
new ParcelFileDescriptor(fd1, comm[1]) };
@@ -434,7 +434,7 @@
try {
final FileDescriptor comm1 = new FileDescriptor();
final FileDescriptor comm2 = new FileDescriptor();
- Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, comm1, comm2);
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, comm1, comm2);
IoUtils.setBlocking(comm1, false);
IoUtils.setBlocking(comm2, false);
return new FileDescriptor[] { comm1, comm2 };
@@ -520,7 +520,7 @@
return mWrapped.getStatSize();
} else {
try {
- final StructStat st = Libcore.os.fstat(mFd);
+ final StructStat st = Os.fstat(mFd);
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
return st.st_size;
} else {
@@ -543,7 +543,7 @@
return mWrapped.seekTo(pos);
} else {
try {
- return Libcore.os.lseek(mFd, pos, SEEK_SET);
+ return Os.lseek(mFd, pos, SEEK_SET);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
@@ -695,7 +695,7 @@
writePtr += len;
}
- Libcore.os.write(mCommFd, buf, 0, writePtr);
+ Os.write(mCommFd, buf, 0, writePtr);
} catch (ErrnoException e) {
// Reporting status is best-effort
Log.w(TAG, "Failed to report status: " + e);
@@ -712,7 +712,7 @@
private static Status readCommStatus(FileDescriptor comm, byte[] buf) {
try {
- final int n = Libcore.os.read(comm, buf, 0, buf.length);
+ final int n = Os.read(comm, buf, 0, buf.length);
if (n == 0) {
// EOF means they're dead
return new Status(Status.DEAD);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 995e396..b4ed68c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -18,6 +18,7 @@
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
+import android.system.Os;
import android.util.Log;
import com.android.internal.os.Zygote;
import java.io.BufferedWriter;
@@ -28,7 +29,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import libcore.io.Libcore;
/*package*/ class ZygoteStartFailedEx extends Exception {
/**
@@ -741,7 +741,7 @@
* {@link #killProcess} and {@link #sendSignal}.
*/
public static final int myPid() {
- return Libcore.os.getpid();
+ return Os.getpid();
}
/**
@@ -749,7 +749,7 @@
* @hide
*/
public static final int myPpid() {
- return Libcore.os.getppid();
+ return Os.getppid();
}
/**
@@ -757,7 +757,7 @@
* {@link #setThreadPriority(int, int)}.
*/
public static final int myTid() {
- return Libcore.os.gettid();
+ return Os.gettid();
}
/**
@@ -767,7 +767,7 @@
* a uid identifies a specific app sandbox in a specific user.
*/
public static final int myUid() {
- return Libcore.os.getuid();
+ return Os.getuid();
}
/**
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index f671ed9..cdde4c7 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -375,6 +375,7 @@
final ConditionVariable condition = new ConditionVariable();
Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER,
android.Manifest.permission.MASTER_CLEAR,
new BroadcastReceiver() {
diff --git a/core/java/android/os/StatFs.java b/core/java/android/os/StatFs.java
index 9e9521a..13e9a15 100644
--- a/core/java/android/os/StatFs.java
+++ b/core/java/android/os/StatFs.java
@@ -16,9 +16,9 @@
package android.os;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-import libcore.io.StructStatVfs;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStatVfs;
/**
* Retrieve overall information about the space on a filesystem. This is a
@@ -41,7 +41,7 @@
private static StructStatVfs doStat(String path) {
try {
- return Libcore.os.statvfs(path);
+ return Os.statvfs(path);
} catch (ErrnoException e) {
throw new IllegalArgumentException("Invalid path: " + path, e);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 63de9a0..1fe9337 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -300,8 +300,7 @@
/**
* Sets all the user-wide restrictions for this user.
- * Requires the MANAGE_USERS permission or profile/device owner
- * privileges.
+ * Requires the MANAGE_USERS permission.
* @param restrictions the Bundle containing all the restrictions.
*/
public void setUserRestrictions(Bundle restrictions) {
@@ -310,8 +309,7 @@
/**
* Sets all the user-wide restrictions for the specified user.
- * Requires the MANAGE_USERS permission or profile/device owner
- * privileges.
+ * Requires the MANAGE_USERS permission.
* @param restrictions the Bundle containing all the restrictions.
* @param userHandle the UserHandle of the user for whom to set the restrictions.
*/
@@ -325,8 +323,7 @@
/**
* Sets the value of a specific restriction.
- * Requires the MANAGE_USERS permission or profile/device owner
- * privileges.
+ * Requires the MANAGE_USERS permission.
* @param key the key of the restriction
* @param value the value for the restriction
*/
@@ -339,8 +336,7 @@
/**
* @hide
* Sets the value of a specific restriction on a specific user.
- * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission or profile/device owner
- * privileges.
+ * Requires the MANAGE_USERS permission.
* @param key the key of the restriction
* @param value the value for the restriction
* @param userHandle the user whose restriction is to be changed.
@@ -492,7 +488,8 @@
ArrayList<UserHandle> profiles = new ArrayList<UserHandle>();
List<UserInfo> users = new ArrayList<UserInfo>();
try {
- users = mService.getProfiles(UserHandle.myUserId(), true /* enabledOnly */);
+ // TODO: Switch enabledOnly to true once client apps are updated
+ users = mService.getProfiles(UserHandle.myUserId(), false /* enabledOnly */);
} catch (RemoteException re) {
Log.w(TAG, "Could not get user list", re);
return null;
@@ -560,7 +557,7 @@
/**
* Returns information for all users on this device. Requires
* {@link android.Manifest.permission#MANAGE_USERS} permission.
- *
+ *
* @param excludeDying specify if the list should exclude users being
* removed.
* @return the list of users that were created.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 9a768e0..b907375 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -17,7 +17,7 @@
package android.provider;
import static android.net.TrafficStats.KB_IN_BYTES;
-import static libcore.io.OsConstants.SEEK_SET;
+import static android.system.OsConstants.SEEK_SET;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
@@ -38,11 +38,11 @@
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
import java.io.BufferedInputStream;
import java.io.File;
@@ -809,7 +809,7 @@
// optimal decode path; otherwise fall back to buffering.
BufferedInputStream is = null;
try {
- Libcore.os.lseek(fd, offset, SEEK_SET);
+ Os.lseek(fd, offset, SEEK_SET);
} catch (ErrnoException e) {
is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
is.mark(THUMBNAIL_BUFFER_SIZE);
@@ -835,7 +835,7 @@
bitmap = BitmapFactory.decodeStream(is, null, opts);
} else {
try {
- Libcore.os.lseek(fd, offset, SEEK_SET);
+ Os.lseek(fd, offset, SEEK_SET);
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7f9f862..ab06230 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -723,6 +723,13 @@
= "android.settings.NOTIFICATION_LISTENER_SETTINGS";
/**
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CONDITION_PROVIDER_SETTINGS
+ = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
+
+ /**
* Activity Action: Show settings for video captioning.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -757,6 +764,11 @@
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS";
+ /** {@hide} */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String
+ ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
+
// End of Intent actions for Settings
/**
@@ -3311,6 +3323,12 @@
"input_method_selector_visibility";
/**
+ * The currently selected voice interaction service flattened ComponentName.
+ * @hide
+ */
+ public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
+
+ /**
* bluetooth HCI snoop log configuration
* @hide
*/
@@ -4510,6 +4528,11 @@
*/
public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+ /**
+ * @hide
+ */
+ public static final String ENABLED_CONDITION_PROVIDERS = "enabled_condition_providers";
+
/** @hide */
public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
diff --git a/core/java/android/service/notification/Condition.aidl b/core/java/android/service/notification/Condition.aidl
new file mode 100644
index 0000000..432852c
--- /dev/null
+++ b/core/java/android/service/notification/Condition.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+parcelable Condition;
+
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
new file mode 100644
index 0000000..71e3166
--- /dev/null
+++ b/core/java/android/service/notification/Condition.java
@@ -0,0 +1,147 @@
+/**
+ * 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.content.Context;
+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 String SCHEME = "condition";
+
+ public static final int STATE_FALSE = 0;
+ public static final int STATE_TRUE = 1;
+ public static final int STATE_UNKNOWN = 2;
+ public static final int STATE_ERROR = 3;
+
+ 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 int state;
+ public int flags;
+
+ public Condition(Uri id, String caption, int state, int flags) {
+ if (id == null) throw new IllegalArgumentException("id is required");
+ if (caption == null) throw new IllegalArgumentException("caption is required");
+ if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state);
+ this.id = id;
+ this.caption = caption;
+ this.state = state;
+ this.flags = flags;
+ }
+
+ private Condition(Parcel source) {
+ this((Uri)source.readParcelable(Condition.class.getClassLoader()),
+ source.readString(),
+ source.readInt(),
+ source.readInt());
+ }
+
+ private static boolean isValidState(int state) {
+ return state >= STATE_FALSE && state <= STATE_ERROR;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(id, 0);
+ dest.writeString(caption);
+ dest.writeInt(state);
+ 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(stateToString(state))
+ .append(",flags=").append(flags)
+ .append(']').toString();
+ }
+
+ public static String stateToString(int state) {
+ if (state == STATE_FALSE) return "STATE_FALSE";
+ if (state == STATE_TRUE) return "STATE_TRUE";
+ if (state == STATE_UNKNOWN) return "STATE_UNKNOWN";
+ if (state == STATE_ERROR) return "STATE_ERROR";
+ throw new IllegalArgumentException("state is invalid: " + state);
+ }
+
+ @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 Uri.Builder newId(Context context) {
+ return new Uri.Builder().scheme(SCHEME).authority(context.getPackageName());
+ }
+
+ public static boolean isValidId(Uri id, String pkg) {
+ return id != null && id.getScheme().equals(SCHEME) && id.getAuthority().equals(pkg);
+ }
+
+ public static final Parcelable.Creator<Condition> CREATOR
+ = new Parcelable.Creator<Condition>() {
+ @Override
+ public Condition createFromParcel(Parcel source) {
+ return new Condition(source);
+ }
+
+ @Override
+ public Condition[] newArray(int size) {
+ return new Condition[size];
+ }
+ };
+}
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
new file mode 100644
index 0000000..d6ef8f5
--- /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 void onConnected();
+ abstract public void onRequestConditions(int relevance);
+ abstract public void onSubscribe(Uri conditionId);
+ abstract public void onUnsubscribe(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 (condition == null) return;
+ notifyConditions(new Condition[]{ condition });
+ }
+
+ public final void notifyConditions(Condition... conditions) {
+ if (!isBound() || conditions == null) return;
+ try {
+ getNotificationInterface().notifyConditions(getPackageName(), mProvider, conditions);
+ } 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 void onConnected() {
+ try {
+ mService.onConnected();
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onConnected", t);
+ }
+ }
+
+ @Override
+ public void onRequestConditions(int relevance) {
+ try {
+ mService.onRequestConditions(relevance);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onRequestConditions", t);
+ }
+ }
+
+ @Override
+ public void onSubscribe(Uri conditionId) {
+ try {
+ mService.onSubscribe(conditionId);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onSubscribe", t);
+ }
+ }
+
+ @Override
+ public void onUnsubscribe(Uri conditionId) {
+ try {
+ mService.onUnsubscribe(conditionId);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onUnsubscribe", t);
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/notification/IConditionListener.aidl b/core/java/android/service/notification/IConditionListener.aidl
new file mode 100644
index 0000000..01f874f
--- /dev/null
+++ b/core/java/android/service/notification/IConditionListener.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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 */
+oneway interface IConditionListener {
+ void onConditionsReceived(in Condition[] conditions);
+}
\ No newline at end of file
diff --git a/core/java/android/service/notification/IConditionProvider.aidl b/core/java/android/service/notification/IConditionProvider.aidl
new file mode 100644
index 0000000..ada8939
--- /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 */
+oneway interface IConditionProvider {
+ void onConnected();
+ void onRequestConditions(int relevance);
+ void onSubscribe(in Uri conditionId);
+ void onUnsubscribe(in Uri conditionId);
+}
\ No newline at end of file
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 2c0b76d..72720d1 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -87,7 +87,7 @@
}
private String key() {
- return pkg + '|' + opPkg + '|' + id + '|' + tag + '|' + uid;
+ return pkg + '|' + id + '|' + tag + '|' + uid;
}
public void writeToParcel(Parcel out, int flags) {
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
new file mode 100644
index 0000000..e9e2f4c
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+/**
+ * @hide
+ */
+oneway interface IVoiceInteractionService {
+}
diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl
new file mode 100644
index 0000000..7dbf66b
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * @hide
+ */
+interface IVoiceInteractionSession {
+}
diff --git a/core/java/android/service/voice/IVoiceInteractionSessionService.aidl b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
new file mode 100644
index 0000000..2519442
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionSessionService.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 android.service.voice.IVoiceInteractionSession;
+
+/**
+ * @hide
+ */
+oneway interface IVoiceInteractionSessionService {
+ void newSession(IBinder token, in Bundle args);
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
new file mode 100644
index 0000000..d005890
--- /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.Bundle;
+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, Bundle sessionArgs) {
+ try {
+ mSystemService.startVoiceActivity(intent,
+ intent.resolveType(getContentResolver()),
+ mInterface, sessionArgs);
+ } 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/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
new file mode 100644
index 0000000..a909ead
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.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 android.service.voice;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.speech.RecognitionService;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class VoiceInteractionServiceInfo {
+ static final String TAG = "VoiceInteractionServiceInfo";
+
+ private String mParseError;
+
+ private ServiceInfo mServiceInfo;
+ private String mSessionService;
+ private String mSettingsActivity;
+
+ public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp)
+ throws PackageManager.NameNotFoundException {
+ this(pm, pm.getServiceInfo(comp, PackageManager.GET_META_DATA));
+ }
+
+ public VoiceInteractionServiceInfo(PackageManager pm, ServiceInfo si) {
+ if (!Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
+ mParseError = "Service does not require permission "
+ + Manifest.permission.BIND_VOICE_INTERACTION;
+ return;
+ }
+
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, VoiceInteractionService.SERVICE_META_DATA);
+ if (parser == null) {
+ mParseError = "No " + VoiceInteractionService.SERVICE_META_DATA
+ + " meta-data for " + si.packageName;
+ return;
+ }
+
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"voice-interaction-service".equals(nodeName)) {
+ mParseError = "Meta-data does not start with voice-interaction-service tag";
+ return;
+ }
+
+ TypedArray array = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.VoiceInteractionService);
+ mSessionService = array.getString(
+ com.android.internal.R.styleable.VoiceInteractionService_sessionService);
+ mSettingsActivity = array.getString(
+ com.android.internal.R.styleable.VoiceInteractionService_settingsActivity);
+ array.recycle();
+ if (mSessionService == null) {
+ mParseError = "No sessionService specified";
+ return;
+ }
+ } catch (XmlPullParserException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } catch (IOException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } catch (PackageManager.NameNotFoundException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ mServiceInfo = si;
+ }
+
+ public String getParseError() {
+ return mParseError;
+ }
+
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ public String getSessionService() {
+ return mSessionService;
+ }
+
+ public String getSettingsActivity() {
+ return mSettingsActivity;
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
new file mode 100644
index 0000000..963b6b4
--- /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(boolean complete, Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+ + " result=" + result);
+ mCallback.deliverCommandResult(mInterface, complete, 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/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java
new file mode 100644
index 0000000..40e5bba
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionSessionService.java
@@ -0,0 +1,82 @@
+/**
+ * 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.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+public abstract class VoiceInteractionSessionService extends Service {
+
+ static final int MSG_NEW_SESSION = 1;
+
+ IVoiceInteractionManagerService mSystemService;
+
+ IVoiceInteractionSessionService mInterface = new IVoiceInteractionSessionService.Stub() {
+ public void newSession(IBinder token, Bundle args) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(MSG_NEW_SESSION,
+ token, args));
+
+ }
+ };
+
+ 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_NEW_SESSION:
+ doNewSession((IBinder)args.arg1, (Bundle)args.arg2);
+ break;
+ }
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ mHandlerCaller = new HandlerCaller(this, Looper.myLooper(),
+ mHandlerCallerCallback, true);
+ }
+
+ public abstract VoiceInteractionSession onNewSession(Bundle args);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mInterface.asBinder();
+ }
+
+ void doNewSession(IBinder token, Bundle args) {
+ VoiceInteractionSession session = onNewSession(args);
+ try {
+ mSystemService.deliverNewSession(token, session.mSession, session.mInteractor);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 77cd71e..6f00707 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -188,10 +188,6 @@
spacing = metrics.descent - metrics.ascent;
}
- if (spacingmult != 1 || spacingadd != 0) {
- spacing = (int)(spacing * spacingmult + spacingadd + 0.5f);
- }
-
mBottom = spacing;
if (includepad) {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f7ac75a..0db00f0 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -632,7 +632,11 @@
bottom = fm.bottom;
}
- if (j == 0) {
+ boolean firstLine = (j == 0);
+ boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
+ boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
+
+ if (firstLine) {
if (trackPad) {
mTopPadding = top - above;
}
@@ -641,7 +645,10 @@
above = top;
}
}
- if (end == bufEnd) {
+
+ int extra;
+
+ if (lastLine) {
if (trackPad) {
mBottomPadding = bottom - below;
}
@@ -651,9 +658,8 @@
}
}
- int extra;
- if (needMultiply && end != bufEnd) {
+ if (needMultiply && !lastLine) {
double ex = (below - above) * (spacingmult - 1) + spacingadd;
if (ex >= 0) {
extra = (int)(ex + EXTRA_ROUNDING);
@@ -690,8 +696,6 @@
if (ellipsize != null) {
// If there is only one line, then do any type of ellipsis except when it is MARQUEE
// if there are multiple lines, just allow END ellipsis on the last line
- boolean firstLine = (j == 0);
- boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
boolean doEllipsis =
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 9c98b98..b0cbcd2 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -106,4 +106,69 @@
public static String formatIpAddress(int ipv4Address) {
return NetworkUtils.intToInetAddress(ipv4Address).getHostAddress();
}
+
+ private static final int SECONDS_PER_MINUTE = 60;
+ private static final int SECONDS_PER_HOUR = 60 * 60;
+ private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+
+ /**
+ * Returns elapsed time for the given millis, in the following format:
+ * 1 day 5 hrs; will include at most two units, can go down to seconds precision.
+ * @param context the application context
+ * @param millis the elapsed time in milli seconds
+ * @return the formatted elapsed time
+ * @hide
+ */
+ public static String formatShortElapsedTime(Context context, long millis) {
+ long secondsLong = millis / 1000;
+
+ int days = 0, hours = 0, minutes = 0;
+ if (secondsLong >= SECONDS_PER_DAY) {
+ days = (int)(secondsLong / SECONDS_PER_DAY);
+ secondsLong -= days * SECONDS_PER_DAY;
+ }
+ if (secondsLong >= SECONDS_PER_HOUR) {
+ hours = (int)(secondsLong / SECONDS_PER_HOUR);
+ secondsLong -= hours * SECONDS_PER_HOUR;
+ }
+ if (secondsLong >= SECONDS_PER_MINUTE) {
+ minutes = (int)(secondsLong / SECONDS_PER_MINUTE);
+ secondsLong -= minutes * SECONDS_PER_MINUTE;
+ }
+ int seconds = (int)secondsLong;
+
+ if (days >= 2) {
+ days += (hours+12)/24;
+ return context.getString(com.android.internal.R.string.durationDays, days);
+ } else if (days > 0) {
+ if (hours == 1) {
+ return context.getString(com.android.internal.R.string.durationDayHour, days, hours);
+ }
+ return context.getString(com.android.internal.R.string.durationDayHours, days, hours);
+ } else if (hours >= 2) {
+ hours += (minutes+30)/60;
+ return context.getString(com.android.internal.R.string.durationHours, hours);
+ } else if (hours > 0) {
+ if (minutes == 1) {
+ return context.getString(com.android.internal.R.string.durationHourMinute, hours,
+ minutes);
+ }
+ return context.getString(com.android.internal.R.string.durationHourMinutes, hours,
+ minutes);
+ } else if (minutes >= 2) {
+ minutes += (seconds+30)/60;
+ return context.getString(com.android.internal.R.string.durationMinutes, minutes);
+ } else if (minutes > 0) {
+ if (seconds == 1) {
+ return context.getString(com.android.internal.R.string.durationMinuteSecond, minutes,
+ seconds);
+ }
+ return context.getString(com.android.internal.R.string.durationMinuteSeconds, minutes,
+ seconds);
+ } else if (seconds == 1) {
+ return context.getString(com.android.internal.R.string.durationSecond, seconds);
+ } else {
+ return context.getString(com.android.internal.R.string.durationSeconds, seconds);
+ }
+ }
}
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index 08e27d3..e70dc0c 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -105,7 +105,7 @@
if (DBG) {
Log.d(LOG_TAG, "Created animator " + anim);
}
- FadeAnimatorListener listener = new FadeAnimatorListener(view, endAlpha);
+ FadeAnimatorListener listener = new FadeAnimatorListener(view);
anim.addListener(listener);
anim.addPauseListener(listener);
return anim;
@@ -138,13 +138,11 @@
private static class FadeAnimatorListener extends AnimatorListenerAdapter {
private final View mView;
- private final float mEndAlpha;
private boolean mCanceled = false;
- private float mPausedAlpha;
+ private float mPausedAlpha = -1;
- public FadeAnimatorListener(View view, float endAlpha) {
+ public FadeAnimatorListener(View view) {
mView = view;
- mEndAlpha = endAlpha;
}
@Override
@@ -158,14 +156,14 @@
@Override
public void onAnimationEnd(Animator animator) {
if (!mCanceled) {
- mView.setTransitionAlpha(mEndAlpha);
+ mView.setTransitionAlpha(1);
}
}
@Override
public void onAnimationPause(Animator animator) {
mPausedAlpha = mView.getTransitionAlpha();
- mView.setTransitionAlpha(mEndAlpha);
+ mView.setTransitionAlpha(1);
}
@Override
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 526803a..6e6496c 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -347,10 +347,11 @@
}
if (viewToKeep != null) {
+ int originalVisibility = viewToKeep.getVisibility();
viewToKeep.setVisibility(View.VISIBLE);
Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);
if (animator == null) {
- viewToKeep.setVisibility(finalVisibility);
+ viewToKeep.setVisibility(originalVisibility);
} else {
final View finalViewToKeep = viewToKeep;
animator.addListener(new AnimatorListenerAdapter() {
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl
index 43be6f0..538f8a1 100644
--- a/core/java/android/tv/ITvInputClient.aidl
+++ b/core/java/android/tv/ITvInputClient.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.tv.ITvInputSession;
+import android.view.InputChannel;
/**
* Interface a client of the ITvInputManager implements, to identify itself and receive information
@@ -25,6 +26,6 @@
* @hide
*/
oneway interface ITvInputClient {
- void onSessionCreated(in ComponentName name, IBinder token, int seq);
+ void onSessionCreated(in ComponentName name, IBinder token, in InputChannel channel, int seq);
void onAvailabilityChanged(in ComponentName name, boolean isAvailable);
}
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl
index 672784f..4f1bc2b 100644
--- a/core/java/android/tv/ITvInputService.aidl
+++ b/core/java/android/tv/ITvInputService.aidl
@@ -18,6 +18,7 @@
import android.tv.ITvInputServiceCallback;
import android.tv.ITvInputSessionCallback;
+import android.view.InputChannel;
/**
* Top-level interface to a TV input component (implemented in a Service).
@@ -26,5 +27,5 @@
oneway interface ITvInputService {
void registerCallback(ITvInputServiceCallback callback);
void unregisterCallback(in ITvInputServiceCallback callback);
- void createSession(ITvInputSessionCallback callback);
+ void createSession(in InputChannel channel, ITvInputSessionCallback callback);
}
diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java
index a6e0877..3ccccf3 100644
--- a/core/java/android/tv/ITvInputSessionWrapper.java
+++ b/core/java/android/tv/ITvInputSessionWrapper.java
@@ -20,9 +20,16 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
+import android.tv.TvInputManager.Session;
import android.tv.TvInputService.TvInputSessionImpl;
import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.Surface;
import com.android.internal.os.HandlerCaller;
@@ -45,50 +52,66 @@
private static final int DO_RELAYOUT_OVERLAY_VIEW = 6;
private static final int DO_REMOVE_OVERLAY_VIEW = 7;
- private TvInputSessionImpl mTvInputSession;
private final HandlerCaller mCaller;
- public ITvInputSessionWrapper(Context context, TvInputSessionImpl session) {
+ private TvInputSessionImpl mTvInputSessionImpl;
+ private InputChannel mChannel;
+ private TvInputEventReceiver mReceiver;
+
+ public ITvInputSessionWrapper(Context context, TvInputSessionImpl sessionImpl,
+ InputChannel channel) {
mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
- mTvInputSession = session;
+ mTvInputSessionImpl = sessionImpl;
+ mChannel = channel;
+ if (channel != null) {
+ mReceiver = new TvInputEventReceiver(channel, context.getMainLooper());
+ }
}
@Override
public void executeMessage(Message msg) {
- if (mTvInputSession == null) {
+ if (mTvInputSessionImpl == null) {
return;
}
switch (msg.what) {
case DO_RELEASE: {
- mTvInputSession.release();
- mTvInputSession = null;
+ mTvInputSessionImpl.release();
+ mTvInputSessionImpl = null;
+ if (mReceiver != null) {
+ mReceiver.dispose();
+ mReceiver = null;
+ }
+ if (mChannel != null) {
+ mChannel.dispose();
+ mChannel = null;
+ }
return;
}
case DO_SET_SURFACE: {
- mTvInputSession.setSurface((Surface) msg.obj);
+ mTvInputSessionImpl.setSurface((Surface) msg.obj);
return;
}
case DO_SET_VOLUME: {
- mTvInputSession.setVolume((Float) msg.obj);
+ mTvInputSessionImpl.setVolume((Float) msg.obj);
return;
}
case DO_TUNE: {
- mTvInputSession.tune((Uri) msg.obj);
+ mTvInputSessionImpl.tune((Uri) msg.obj);
return;
}
case DO_CREATE_OVERLAY_VIEW: {
SomeArgs args = (SomeArgs) msg.obj;
- mTvInputSession.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
+ mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
args.recycle();
return;
}
case DO_RELAYOUT_OVERLAY_VIEW: {
- mTvInputSession.relayoutOverlayView((Rect) msg.obj);
+ mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj);
return;
}
case DO_REMOVE_OVERLAY_VIEW: {
- mTvInputSession.removeOverlayView(true);
+ mTvInputSessionImpl.removeOverlayView(true);
return;
}
default: {
@@ -133,4 +156,24 @@
public void removeOverlayView() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
}
+
+ private final class TvInputEventReceiver extends InputEventReceiver {
+ public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (mTvInputSessionImpl == null) {
+ // The session has been finished.
+ finishInputEvent(event, false);
+ return;
+ }
+
+ int handled = mTvInputSessionImpl.dispatchInputEvent(event, this);
+ if (handled != Session.DISPATCH_IN_PROGRESS) {
+ finishInputEvent(event, handled == Session.DISPATCH_HANDLED);
+ }
+ }
+ }
}
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
index 05f0b9c..7b9b1fb 100644
--- a/core/java/android/tv/TvInputManager.java
+++ b/core/java/android/tv/TvInputManager.java
@@ -21,9 +21,16 @@
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
import android.view.Surface;
import android.view.View;
@@ -138,7 +145,8 @@
mUserId = userId;
mClient = new ITvInputClient.Stub() {
@Override
- public void onSessionCreated(ComponentName name, IBinder token, int seq) {
+ public void onSessionCreated(ComponentName name, IBinder token, InputChannel channel,
+ int seq) {
synchronized (mSessionCreateCallbackRecordMap) {
SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq);
mSessionCreateCallbackRecordMap.delete(seq);
@@ -148,7 +156,7 @@
}
Session session = null;
if (token != null) {
- session = new Session(name, token, mService, mUserId);
+ session = new Session(token, channel, mService, mUserId);
}
record.postSessionCreated(session);
}
@@ -321,13 +329,30 @@
/** The Session provides the per-session functionality of TV inputs. */
public static final class Session {
+ static final int DISPATCH_IN_PROGRESS = -1;
+ static final int DISPATCH_NOT_HANDLED = 0;
+ static final int DISPATCH_HANDLED = 1;
+
+ private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
+
private final ITvInputManager mService;
private final int mUserId;
+
+ // For scheduling input event handling on the main thread. This also serves as a lock to
+ // protect pending input events and the input channel.
+ private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
+
+ private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
+ private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
+
private IBinder mToken;
+ private TvInputEventSender mSender;
+ private InputChannel mChannel;
/** @hide */
- private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) {
+ private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId) {
mToken = token;
+ mChannel = channel;
mService = service;
mUserId = userId;
}
@@ -347,6 +372,18 @@
} catch (RemoteException e) {
throw new RuntimeException(e);
}
+
+ synchronized (mHandler) {
+ if (mChannel != null) {
+ if (mSender != null) {
+ flushPendingEventsLocked();
+ mSender.dispose();
+ mSender = null;
+ }
+ mChannel.dispose();
+ mChannel = null;
+ }
+ }
}
/**
@@ -478,5 +515,228 @@
throw new RuntimeException(e);
}
}
+
+ /**
+ * Dispatches an input event to this session.
+ *
+ * @param event {@link InputEvent} to dispatch.
+ * @param token A token used to identify the input event later in the callback.
+ * @param callback A callback used to receive the dispatch result.
+ * @param handler {@link Handler} that the dispatch result will be delivered to.
+ * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
+ * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
+ * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
+ * be invoked later.
+ * @throws IllegalArgumentException if any of the necessary arguments is {@code null}.
+ * @hide
+ */
+ public int dispatchInputEvent(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ if (event == null) {
+ throw new IllegalArgumentException("event cannot be null");
+ }
+ if (callback != null && handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ synchronized (mHandler) {
+ if (mChannel == null) {
+ return DISPATCH_NOT_HANDLED;
+ }
+ PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // Already running on the main thread so we can send the event immediately.
+ return sendInputEventOnMainLooperLocked(p);
+ }
+
+ // Post the event to the main thread.
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ return DISPATCH_IN_PROGRESS;
+ }
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to this session has been
+ * finished.
+ *
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ /**
+ * Called when the dispatched input event is finished.
+ *
+ * @param token a token passed to {@link #dispatchInputEvent}.
+ * @param handled {@code true} if the dispatched input event was handled properly.
+ * {@code false} otherwise.
+ */
+ public void onFinishedInputEvent(Object token, boolean handled);
+ }
+
+ // Must be called on the main looper
+ private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+ synchronized (mHandler) {
+ int result = sendInputEventOnMainLooperLocked(p);
+ if (result == DISPATCH_IN_PROGRESS) {
+ return;
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, false);
+ }
+
+ private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+ if (mChannel != null) {
+ if (mSender == null) {
+ mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
+ }
+
+ final InputEvent event = p.mEvent;
+ final int seq = event.getSequenceNumber();
+ if (mSender.sendInputEvent(seq, event)) {
+ mPendingEvents.put(seq, p);
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+ return DISPATCH_IN_PROGRESS;
+ }
+
+ Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+ + event);
+ }
+ return DISPATCH_NOT_HANDLED;
+ }
+
+ void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+ final PendingEvent p;
+ synchronized (mHandler) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index < 0) {
+ return; // spurious, event already finished or timed out
+ }
+
+ p = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+
+ if (timeout) {
+ Log.w(TAG, "Timeout waiting for seesion to handle input event after "
+ + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+ } else {
+ mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, handled);
+ }
+
+ // Assumes the event has already been removed from the queue.
+ void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+ p.mHandled = handled;
+ if (p.mHandler.getLooper().isCurrentThread()) {
+ // Already running on the callback handler thread so we can send the callback
+ // immediately.
+ p.run();
+ } else {
+ // Post the event to the callback handler thread.
+ // In this case, the callback will be responsible for recycling the event.
+ Message msg = Message.obtain(p.mHandler, p);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private void flushPendingEventsLocked() {
+ mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+ final int count = mPendingEvents.size();
+ for (int i = 0; i < count; i++) {
+ int seq = mPendingEvents.keyAt(i);
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ PendingEvent p = mPendingEventPool.acquire();
+ if (p == null) {
+ p = new PendingEvent();
+ }
+ p.mEvent = event;
+ p.mToken = token;
+ p.mCallback = callback;
+ p.mHandler = handler;
+ return p;
+ }
+
+ private void recyclePendingEventLocked(PendingEvent p) {
+ p.recycle();
+ mPendingEventPool.release(p);
+ }
+
+ private final class InputEventHandler extends Handler {
+ public static final int MSG_SEND_INPUT_EVENT = 1;
+ public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+ public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+ InputEventHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SEND_INPUT_EVENT: {
+ sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+ return;
+ }
+ case MSG_TIMEOUT_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, true);
+ return;
+ }
+ case MSG_FLUSH_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, false);
+ return;
+ }
+ }
+ }
+ }
+
+ private final class TvInputEventSender extends InputEventSender {
+ public TvInputEventSender(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEventFinished(int seq, boolean handled) {
+ finishedInputEvent(seq, handled, false);
+ }
+ }
+
+ private final class PendingEvent implements Runnable {
+ public InputEvent mEvent;
+ public Object mToken;
+ public FinishedInputEventCallback mCallback;
+ public Handler mHandler;
+ public boolean mHandled;
+
+ public void recycle() {
+ mEvent = null;
+ mToken = null;
+ mCallback = null;
+ mHandler = null;
+ mHandled = false;
+ }
+
+ @Override
+ public void run() {
+ mCallback.onFinishedInputEvent(mToken, mHandled);
+
+ synchronized (mHandler) {
+ recyclePendingEventLocked(this);
+ }
+ }
+ }
}
}
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
index 80eb407..636e3b4 100644
--- a/core/java/android/tv/TvInputService.java
+++ b/core/java/android/tv/TvInputService.java
@@ -28,13 +28,21 @@
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.tv.TvInputManager.Session;
import android.util.Log;
import android.view.Gravity;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
/**
* A base class for implementing television input service.
@@ -89,10 +97,17 @@
}
@Override
- public void createSession(ITvInputSessionCallback cb) {
- if (cb != null) {
- mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, cb).sendToTarget();
+ public void createSession(InputChannel channel, ITvInputSessionCallback cb) {
+ if (channel == null) {
+ Log.w(TAG, "Creating session without input channel");
}
+ if (cb == null) {
+ return;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = channel;
+ args.arg2 = cb;
+ mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
}
};
}
@@ -131,7 +146,8 @@
/**
* Base class for derived classes to implement to provide {@link TvInputManager.Session}.
*/
- public abstract class TvInputSessionImpl {
+ public abstract class TvInputSessionImpl implements KeyEvent.Callback {
+ private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
private final WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowParams;
private View mOverlayView;
@@ -143,6 +159,13 @@
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
}
+ /**
+ * Enables or disables the overlay view. By default, the overlay view is disabled. Must be
+ * called explicitly after the session is created to enable the overlay view.
+ *
+ * @param enable {@code true} if you want to enable the overlay view. {@code false}
+ * otherwise.
+ */
public void setOverlayViewEnabled(final boolean enable) {
mHandler.post(new Runnable() {
@Override
@@ -203,6 +226,121 @@
}
/**
+ * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
+ * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key down events before they are processed by the application.
+ * If you return true, the application will not process the event itself. If you return
+ * false, the normal application processing will occur as if the TV input had not seen the
+ * event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+ * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key long press events before they are processed by the
+ * application. If you return true, the application will not process the event itself. If
+ * you return false, the normal application processing will occur as if the TV input had not
+ * seen the event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+ * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept special key multiple events before they are processed by the
+ * application. If you return true, the application will not itself process the event. If
+ * you return false, the normal application processing will occur as if the TV input had not
+ * seen the event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param count The number of times the action was made.
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
+ * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key up events before they are processed by the application. If
+ * you return true, the application will not itself process the event. If you return false,
+ * the normal application processing will occur as if the TV input had not seen the event at
+ * all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle touch screen motion events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTouchEvent
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle trackball events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTrackballEvent
+ */
+ public boolean onTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle generic motion events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onGenericMotionEvent
+ */
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
* This method is called when the application would like to stop using the current input
* session.
*/
@@ -212,7 +350,7 @@
}
/**
- * Calls {@link onSetSurface}.
+ * Calls {@link #onSetSurface}.
*/
void setSurface(Surface surface) {
onSetSurface(surface);
@@ -220,14 +358,14 @@
}
/**
- * Calls {@link onSetVolume}.
+ * Calls {@link #onSetVolume}.
*/
void setVolume(float volume) {
onSetVolume(volume);
}
/**
- * Calls {@link onTune}.
+ * Calls {@link #onTune}.
*/
void tune(Uri channelUri) {
onTune(channelUri);
@@ -235,8 +373,8 @@
}
/**
- * Creates an overlay view. This calls {@link onCreateOverlayView} to get
- * a view to attach to the overlay window.
+ * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
+ * to the overlay window.
*
* @param windowToken A window token of an application.
* @param frame A position of the overlay view.
@@ -314,6 +452,42 @@
mWindowParams = null;
}
}
+
+ /**
+ * Takes care of dispatching incoming input events and tells whether the event was handled.
+ */
+ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+ if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+ if (event instanceof KeyEvent) {
+ if (((KeyEvent) event).dispatch(this, mDispatcherState, this)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ final int source = motionEvent.getSource();
+ if (motionEvent.isTouchEvent()) {
+ if (onTouchEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (onTrackballEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else {
+ if (onGenericMotionEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ }
+ }
+ if (mOverlayView == null) {
+ return Session.DISPATCH_NOT_HANDLED;
+ }
+ if (!mOverlayView.hasWindowFocus()) {
+ mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
+ }
+ mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
+ return Session.DISPATCH_IN_PROGRESS;
+ }
}
private final class ServiceHandler extends Handler {
@@ -324,20 +498,23 @@
public final void handleMessage(Message msg) {
switch (msg.what) {
case DO_CREATE_SESSION: {
- ITvInputSessionCallback cb = (ITvInputSessionCallback) msg.obj;
+ SomeArgs args = (SomeArgs) msg.obj;
+ InputChannel channel = (InputChannel) args.arg1;
+ ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
try {
TvInputSessionImpl sessionImpl = onCreateSession();
if (sessionImpl == null) {
// Failed to create a session.
cb.onSessionCreated(null);
- return;
+ } else {
+ ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
+ sessionImpl, channel);
+ cb.onSessionCreated(stub);
}
- ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
- sessionImpl);
- cb.onSessionCreated(stub);
} catch (RemoteException e) {
Log.e(TAG, "error in onSessionCreated");
}
+ args.recycle();
return;
}
case DO_BROADCAST_AVAILABILITY_CHANGE: {
diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java
index 325950d..289823b 100644
--- a/core/java/android/tv/TvView.java
+++ b/core/java/android/tv/TvView.java
@@ -20,20 +20,24 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
-import android.tv.TvInputManager;
import android.tv.TvInputManager.Session;
+import android.tv.TvInputManager.Session.FinishedInputEventCallback;
import android.tv.TvInputManager.SessionCreateCallback;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import android.view.ViewTreeObserver;
/**
* View playing TV
*/
public class TvView extends SurfaceView {
+ // STOPSHIP: Turn debugging off.
+ private static final boolean DEBUG = true;
private static final String TAG = "TvView";
private final Handler mHandler = new Handler();
@@ -41,11 +45,11 @@
private Surface mSurface;
private boolean mOverlayViewCreated;
private Rect mOverlayViewFrame;
- private boolean mGlobalListenersAdded;
- private TvInputManager mTvInputManager;
+ private final TvInputManager mTvInputManager;
private SessionCreateCallback mSessionCreateCallback;
+ private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
- private SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+ private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width
@@ -70,6 +74,25 @@
}
};
+ private final FinishedInputEventCallback mFinishedInputEventCallback =
+ new FinishedInputEventCallback() {
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ if (DEBUG) {
+ Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")");
+ }
+ if (handled) {
+ return;
+ }
+ // TODO: Re-order unhandled events.
+ InputEvent event = (InputEvent) token;
+ if (dispatchUnhandledInputEvent(event)) {
+ return;
+ }
+ getViewRootImpl().dispatchUnhandledInputEvent(event);
+ }
+ };
+
public TvView(Context context) {
this(context, null, 0);
}
@@ -124,6 +147,98 @@
}
}
+ /**
+ * Dispatches an unhandled input event to the next receiver.
+ * <p>
+ * Except system keys, TvView always consumes input events in the normal flow. This is called
+ * asynchronously from where the event is dispatched. It gives the host application a chance to
+ * dispatch the unhandled input events.
+ *
+ * @param event The input event.
+ * @return {@code true} if the event was handled by the view, {@code false} otherwise.
+ */
+ public boolean dispatchUnhandledInputEvent(InputEvent event) {
+ if (mOnUnhandledInputEventListener != null) {
+ if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
+ return true;
+ }
+ }
+ return onUnhandledInputEvent(event);
+ }
+
+ /**
+ * Called when an unhandled input event was also not handled by the user provided callback. This
+ * is the last chance to handle the unhandled input event in the TvView.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to be
+ * handled by the next receiver, return {@code false}.
+ */
+ public boolean onUnhandledInputEvent(InputEvent event) {
+ return false;
+ }
+
+ /**
+ * Registers a callback to be invoked when an input event was not handled by the bound TV input.
+ *
+ * @param listener The callback to invoke when the unhandled input event was received.
+ */
+ public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
+ mOnUnhandledInputEventListener = listener;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ int ret = mSession.dispatchInputEvent(event, event, mFinishedInputEventCallback, mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (super.dispatchTouchEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ int ret = mSession.dispatchInputEvent(event, event, mFinishedInputEventCallback, mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (super.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ int ret = mSession.dispatchInputEvent(event, event, mFinishedInputEventCallback, mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (super.dispatchGenericMotionEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ int ret = mSession.dispatchInputEvent(event, event, mFinishedInputEventCallback, mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -196,6 +311,23 @@
location[0] + getWidth(), location[1] + getHeight());
}
+ /**
+ * Interface definition for a callback to be invoked when the unhandled input event is received.
+ */
+ public interface OnUnhandledInputEventListener {
+ /**
+ * Called when an input event was not handled by the bound TV input.
+ * <p>
+ * This is called asynchronously from where the event is dispatched. It gives the host
+ * application a chance to handle the unhandled input events.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ boolean onUnhandledInputEvent(InputEvent event);
+ }
+
private class MySessionCreateCallback implements SessionCreateCallback {
final SessionCreateCallback mExternalCallback;
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 29c0ba2..aefced8 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -56,16 +56,18 @@
public static final class Event {
private final ByteBuffer mBuffer;
- // Layout of event log entry received from kernel.
+ // Layout of event log entry received from Android logger.
+ // see system/core/include/log/logger.h
private static final int LENGTH_OFFSET = 0;
+ private static final int HEADER_SIZE_OFFSET = 2;
private static final int PROCESS_OFFSET = 4;
private static final int THREAD_OFFSET = 8;
private static final int SECONDS_OFFSET = 12;
private static final int NANOSECONDS_OFFSET = 16;
- private static final int PAYLOAD_START = 20;
- private static final int TAG_OFFSET = 20;
- private static final int DATA_START = 24;
+ // Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
+ private static final int V1_PAYLOAD_START = 20;
+ private static final int DATA_OFFSET = 4;
// Value types
private static final byte INT_TYPE = 0;
@@ -97,14 +99,22 @@
/** @return the type tag code of the entry */
public int getTag() {
- return mBuffer.getInt(TAG_OFFSET);
+ int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
+ if (offset == 0) {
+ offset = V1_PAYLOAD_START;
+ }
+ return mBuffer.getInt(offset);
}
/** @return one of Integer, Long, String, null, or Object[] of same. */
public synchronized Object getData() {
try {
- mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET));
- mBuffer.position(DATA_START); // Just after the tag.
+ int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
+ if (offset == 0) {
+ offset = V1_PAYLOAD_START;
+ }
+ mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
+ mBuffer.position(offset + DATA_OFFSET); // Just after the tag.
return decodeObject();
} catch (IllegalArgumentException e) {
Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index f0d8a61..34b85d9 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -1117,11 +1117,11 @@
}
@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/GLRenderer.java b/core/java/android/view/GLRenderer.java
index d8d11f7..97339cc 100644
--- a/core/java/android/view/GLRenderer.java
+++ b/core/java/android/view/GLRenderer.java
@@ -555,7 +555,7 @@
}
@Override
- public void invokeFunctor(long functor, boolean waitForCompletion) {
+ void invokeFunctor(long functor, boolean waitForCompletion) {
boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
boolean hasContext = !needsContext;
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 56d96e1..d31c79d 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -453,7 +453,7 @@
* has invoked. If false, the functor may be invoked
* asynchronously.
*/
- public abstract void invokeFunctor(long functor, boolean waitForCompletion);
+ abstract void invokeFunctor(long functor, boolean waitForCompletion);
/**
* Initializes the hardware renderer for the specified surface and setup the
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 30e4281..8b80c3e0 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -20,6 +20,9 @@
import android.graphics.Matrix;
import android.graphics.Outline;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* <p>A display list records a series of graphics related operations and can replay
* them later. Display lists are usually built by recording operations on a
@@ -176,11 +179,24 @@
private boolean mValid;
private final long mNativeRenderNode;
+ // We need to keep a strong reference to all running animators to ensure that
+ // they can call removeAnimator when they have finished, as the native-side
+ // object can only hold a WeakReference<> to avoid leaking memory due to
+ // cyclic references.
+ private List<RenderNodeAnimator> mActiveAnimators;
+
private RenderNode(String name) {
mNativeRenderNode = nCreate(name);
}
/**
+ * @see RenderNode#adopt(long)
+ */
+ private RenderNode(long nativePtr) {
+ mNativeRenderNode = nativePtr;
+ }
+
+ /**
* Creates a new display list that can be used to record batches of
* drawing operations.
*
@@ -195,6 +211,17 @@
}
/**
+ * Adopts an existing native render node.
+ *
+ * Note: This will *NOT* incRef() on the native object, however it will
+ * decRef() when it is destroyed. The caller should have already incRef'd it
+ */
+ public static RenderNode adopt(long nativePtr) {
+ return new RenderNode(nativePtr);
+ }
+
+
+ /**
* Starts recording a display list for the render node. All
* operations performed on the returned canvas are recorded and
* stored in this display list.
@@ -443,6 +470,14 @@
return nHasOverlappingRendering(mNativeRenderNode);
}
+ public void setElevation(float lift) {
+ nSetElevation(mNativeRenderNode, lift);
+ }
+
+ public float getElevation() {
+ return nGetElevation(mNativeRenderNode);
+ }
+
/**
* Sets the translation value for the display list on the X axis.
*
@@ -814,6 +849,23 @@
}
///////////////////////////////////////////////////////////////////////////
+ // Animations
+ ///////////////////////////////////////////////////////////////////////////
+
+ public void addAnimator(RenderNodeAnimator animator) {
+ if (mActiveAnimators == null) {
+ mActiveAnimators = new ArrayList<RenderNodeAnimator>();
+ }
+ mActiveAnimators.add(animator);
+ nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
+ }
+
+ public void removeAnimator(RenderNodeAnimator animator) {
+ nRemoveAnimator(mNativeRenderNode, animator.getNativeAnimator());
+ mActiveAnimators.remove(animator);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
// Native methods
///////////////////////////////////////////////////////////////////////////
@@ -854,6 +906,7 @@
private static native void nSetAlpha(long renderNode, float alpha);
private static native void nSetHasOverlappingRendering(long renderNode,
boolean hasOverlappingRendering);
+ private static native void nSetElevation(long renderNode, float lift);
private static native void nSetTranslationX(long renderNode, float translationX);
private static native void nSetTranslationY(long renderNode, float translationY);
private static native void nSetTranslationZ(long renderNode, float translationZ);
@@ -874,6 +927,7 @@
private static native float nGetCameraDistance(long renderNode);
private static native float nGetScaleX(long renderNode);
private static native float nGetScaleY(long renderNode);
+ private static native float nGetElevation(long renderNode);
private static native float nGetTranslationX(long renderNode);
private static native float nGetTranslationY(long renderNode);
private static native float nGetTranslationZ(long renderNode);
@@ -886,6 +940,13 @@
private static native void nOutput(long renderNode);
///////////////////////////////////////////////////////////////////////////
+ // Animations
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static native void nAddAnimator(long renderNode, long animatorPtr);
+ private static native void nRemoveAnimator(long renderNode, long animatorPtr);
+
+ ///////////////////////////////////////////////////////////////////////////
// Finalization
///////////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
new file mode 100644
index 0000000..b70ae3d
--- /dev/null
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -0,0 +1,122 @@
+/*
+ * 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.view;
+
+import android.util.SparseIntArray;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * @hide
+ */
+public final class RenderNodeAnimator {
+
+ // Keep in sync with enum RenderProperty in Animator.h
+ private static final int TRANSLATION_X = 0;
+ private static final int TRANSLATION_Y = 1;
+ private static final int TRANSLATION_Z = 2;
+ private static final int SCALE_X = 3;
+ private static final int SCALE_Y = 4;
+ private static final int ROTATION = 5;
+ private static final int ROTATION_X = 6;
+ private static final int ROTATION_Y = 7;
+ private static final int X = 8;
+ private static final int Y = 9;
+ private static final int Z = 10;
+ private static final int ALPHA = 11;
+
+ // ViewPropertyAnimator uses a mask for its values, we need to remap them
+ // to the enum values here. RenderPropertyAnimator can't use the mask values
+ // directly as internally it uses a lookup table so it needs the values to
+ // be sequential starting from 0
+ private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
+ put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
+ put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
+ put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
+ put(ViewPropertyAnimator.SCALE_X, SCALE_X);
+ put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
+ put(ViewPropertyAnimator.ROTATION, ROTATION);
+ put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
+ put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
+ put(ViewPropertyAnimator.X, X);
+ put(ViewPropertyAnimator.Y, Y);
+ put(ViewPropertyAnimator.Z, Z);
+ put(ViewPropertyAnimator.ALPHA, ALPHA);
+ }};
+
+ // Keep in sync DeltaValueType in Animator.h
+ private static final int DELTA_TYPE_ABSOLUTE = 0;
+ private static final int DELTA_TYPE_DELTA = 1;
+
+ private RenderNode mTarget;
+ private long mNativePtr;
+
+ public int mapViewPropertyToRenderProperty(int viewProperty) {
+ return sViewPropertyAnimatorMap.get(viewProperty);
+ }
+
+ public RenderNodeAnimator(int property, int deltaType, float deltaValue) {
+ mNativePtr = nCreateAnimator(new WeakReference<RenderNodeAnimator>(this),
+ property, deltaType, deltaValue);
+ }
+
+ public void start(View target) {
+ mTarget = target.mRenderNode;
+ mTarget.addAnimator(this);
+ // Kick off a frame to start the process
+ target.invalidateViewProperty(true, false);
+ }
+
+ public void cancel() {
+ mTarget.removeAnimator(this);
+ }
+
+ public void setDuration(int duration) {
+ nSetDuration(mNativePtr, duration);
+ }
+
+ long getNativeAnimator() {
+ return mNativePtr;
+ }
+
+ private void onFinished() {
+ mTarget.removeAnimator(this);
+ }
+
+ // Called by native
+ private static void callOnFinished(WeakReference<RenderNodeAnimator> weakThis) {
+ RenderNodeAnimator animator = weakThis.get();
+ if (animator != null) {
+ animator.onFinished();
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nUnref(mNativePtr);
+ mNativePtr = 0;
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis,
+ int property, int deltaValueType, float deltaValue);
+ private static native void nSetDuration(long nativePtr, int duration);
+ private static native void nUnref(long nativePtr);
+}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 924c331..eaec8ab 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -50,7 +50,7 @@
public class ThreadedRenderer extends HardwareRenderer {
private static final String LOGTAG = "ThreadedRenderer";
- private static final Rect NULL_RECT = new Rect(-1, -1, -1, -1);
+ private static final Rect NULL_RECT = new Rect();
private int mWidth, mHeight;
private long mNativeProxy;
@@ -58,9 +58,10 @@
private RenderNode mRootNode;
ThreadedRenderer(boolean translucent) {
- mNativeProxy = nCreateProxy(translucent);
- mRootNode = RenderNode.create("RootNode");
+ long rootNodePtr = nCreateRootRenderNode();
+ mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
+ mNativeProxy = nCreateProxy(translucent, rootNodePtr);
}
@Override
@@ -202,8 +203,7 @@
if (dirty == null) {
dirty = NULL_RECT;
}
- nDrawDisplayList(mNativeProxy, mRootNode.getNativeDisplayList(),
- dirty.left, dirty.top, dirty.right, dirty.bottom);
+ nSyncAndDrawFrame(mNativeProxy, dirty.left, dirty.top, dirty.right, dirty.bottom);
}
@Override
@@ -217,7 +217,7 @@
}
@Override
- public void invokeFunctor(long functor, boolean waitForCompletion) {
+ void invokeFunctor(long functor, boolean waitForCompletion) {
nInvokeFunctor(mNativeProxy, functor, waitForCompletion);
}
@@ -293,7 +293,8 @@
/** @hide */
public static native void postToRenderThread(Runnable runnable);
- private static native long nCreateProxy(boolean translucent);
+ private static native long nCreateRootRenderNode();
+ private static native long nCreateProxy(boolean translucent, long rootRenderNode);
private static native void nDeleteProxy(long nativeProxy);
private static native boolean nInitialize(long nativeProxy, Surface window);
@@ -302,7 +303,7 @@
private static native void nSetup(long nativeProxy, int width, int height);
private static native void nSetDisplayListData(long nativeProxy, long displayList,
long newData);
- private static native void nDrawDisplayList(long nativeProxy, long displayList,
+ private static native void nSyncAndDrawFrame(long nativeProxy,
int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
private static native void nRunWithGlContext(long nativeProxy, Runnable runnable);
private static native void nDestroyCanvasAndSurface(long nativeProxy);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 84d1328..85e3b3d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2375,24 +2375,19 @@
static final int PFLAG3_CALLED_SUPER = 0x10;
/**
- * Flag indicating that an view will be clipped to its outline.
- */
- static final int PFLAG3_CLIP_TO_OUTLINE = 0x20;
-
- /**
* Flag indicating that a view's outline has been specifically defined.
*/
- static final int PFLAG3_OUTLINE_DEFINED = 0x40;
+ static final int PFLAG3_OUTLINE_DEFINED = 0x20;
/**
* Flag indicating that we're in the process of applying window insets.
*/
- static final int PFLAG3_APPLYING_INSETS = 0x80;
+ static final int PFLAG3_APPLYING_INSETS = 0x40;
/**
* Flag indicating that we're in the process of fitting system windows using the old method.
*/
- static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100;
+ static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
/**
* Flag indicating that nested scrolling is enabled for this view.
@@ -3258,9 +3253,7 @@
/**
* Stores the outline of the view, passed down to the DisplayList level for
- * defining shadow shape and clipping.
- *
- * TODO: once RenderNode is long-lived, remove this and rely on native copy.
+ * defining shadow shape.
*/
private Outline mOutline;
@@ -3656,6 +3649,7 @@
float tx = 0;
float ty = 0;
float tz = 0;
+ float elevation = 0;
float rotation = 0;
float rotationX = 0;
float rotationY = 0;
@@ -3739,6 +3733,10 @@
tz = a.getDimensionPixelOffset(attr, 0);
transformSet = true;
break;
+ case com.android.internal.R.styleable.View_elevation:
+ elevation = a.getDimensionPixelOffset(attr, 0);
+ transformSet = true;
+ break;
case com.android.internal.R.styleable.View_rotation:
rotation = a.getFloat(attr, 0);
transformSet = true;
@@ -4087,6 +4085,7 @@
setTranslationX(tx);
setTranslationY(ty);
setTranslationZ(tz);
+ setElevation(elevation);
setRotation(rotation);
setRotationX(rotationX);
setRotationY(rotationY);
@@ -10442,6 +10441,48 @@
setTranslationY(y - mTop);
}
+ /**
+ * The visual z position of this view, in pixels. This is equivalent to the
+ * {@link #setTranslationZ(float) translationZ} property plus the current
+ * {@link #getElevation() elevation} property.
+ *
+ * @return The visual z position of this view, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getZ() {
+ return getElevation() + getTranslationZ();
+ }
+
+ /**
+ * Sets the visual z position of this view, in pixels. This is equivalent to setting the
+ * {@link #setTranslationZ(float) translationZ} property to be the difference between
+ * the x value passed in and the current {@link #getElevation() elevation} property.
+ *
+ * @param z The visual z position of this view, in pixels.
+ */
+ public void setZ(float z) {
+ setTranslationZ(z - getElevation());
+ }
+
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getElevation() {
+ return mRenderNode.getElevation();
+ }
+
+ /**
+ * Sets the base depth location of this view.
+ *
+ * @attr ref android.R.styleable#View_elevation
+ */
+ public void setElevation(float elevation) {
+ if (elevation != getElevation()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setElevation(elevation);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
/**
* The horizontal location of this view relative to its {@link #getLeft() left} position.
@@ -10509,9 +10550,9 @@
}
/**
- * The depth location of this view relative to its parent.
+ * The depth location of this view relative to its {@link #getElevation() elevation}.
*
- * @return The depth of this view relative to its parent.
+ * @return The depth of this view relative to its elevation.
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getTranslationZ() {
@@ -10519,7 +10560,7 @@
}
/**
- * Sets the depth location of this view relative to its parent.
+ * Sets the depth location of this view relative to its {@link #getElevation() elevation}.
*
* @attr ref android.R.styleable#View_translationZ
*/
@@ -10572,16 +10613,13 @@
/**
* Sets the outline of the view, which defines the shape of the shadow it
- * casts, and can used for clipping.
+ * casts.
* <p>
* If the outline is not set or is null, shadows will be cast from the
- * bounds of the View, and clipToOutline will be ignored.
+ * bounds of the View.
*
* @param outline The new outline of the view.
* Must be {@link android.graphics.Outline#isValid() valid.}
- *
- * @see #getClipToOutline()
- * @see #setClipToOutline(boolean)
*/
public void setOutline(@Nullable Outline outline) {
if (outline != null && !outline.isValid()) {
@@ -10602,39 +10640,27 @@
mRenderNode.setOutline(mOutline);
}
- /**
- * Returns whether the outline of the View will be used for clipping.
- *
- * @see #setOutline(Outline)
- */
- public final boolean getClipToOutline() {
- return ((mPrivateFlags3 & PFLAG3_CLIP_TO_OUTLINE) != 0);
- }
+ // TODO: remove
+ public final boolean getClipToOutline() { return false; }
+ public void setClipToOutline(boolean clipToOutline) {}
- /**
- * Sets whether the outline of the View will be used for clipping.
- * <p>
- * The current implementation of outline clipping uses
- * {@link Canvas#clipPath(Path) path clipping},
- * and thus does not support anti-aliasing, and is expensive in terms of
- * graphics performance. Therefore, it is strongly recommended that this
- * property only be set temporarily, as in an animation. For the same
- * reasons, there is no parallel XML attribute for this property.
- * <p>
- * If the outline of the view is not set or is empty, no clipping will be
- * performed.
- *
- * @see #setOutline(Outline)
- */
- public void setClipToOutline(boolean clipToOutline) {
- // TODO : Add a fast invalidation here.
- if (getClipToOutline() != clipToOutline) {
- if (clipToOutline) {
- mPrivateFlags3 |= PFLAG3_CLIP_TO_OUTLINE;
+ private void queryOutlineFromBackgroundIfUndefined() {
+ if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
+ // Outline not currently defined, query from background
+ if (mOutline == null) {
+ mOutline = new Outline();
} else {
- mPrivateFlags3 &= ~PFLAG3_CLIP_TO_OUTLINE;
+ //invalidate outline, to ensure background calculates it
+ mOutline.set(null);
}
- mRenderNode.setClipToOutline(clipToOutline);
+ if (mBackground.getOutline(mOutline)) {
+ if (!mOutline.isValid()) {
+ throw new IllegalStateException("Background drawable failed to build outline");
+ }
+ mRenderNode.setOutline(mOutline);
+ } else {
+ mRenderNode.setOutline(null);
+ }
}
}
@@ -11207,7 +11233,7 @@
}
// Damage the entire IsolatedZVolume recieving this view's shadow.
- if (isHardwareAccelerated() && getTranslationZ() != 0) {
+ if (isHardwareAccelerated() && getZ() != 0) {
damageShadowReceiver();
}
}
@@ -11283,7 +11309,7 @@
} else {
damageInParent();
}
- if (isHardwareAccelerated() && invalidateParent && getTranslationZ() != 0) {
+ if (isHardwareAccelerated() && invalidateParent && getZ() != 0) {
damageShadowReceiver();
}
}
@@ -12784,6 +12810,10 @@
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+ if (mBackground != null) {
+ mBackground.clearHotspots();
+ }
+
removeUnsetPressCallback();
removeLongPressCallback();
removePerformClickCallback();
@@ -14893,11 +14923,7 @@
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
- if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
- // Outline not currently define, query from background
- mOutline = background.getOutline();
- mRenderNode.setOutline(mOutline);
- }
+ queryOutlineFromBackgroundIfUndefined();
}
// Attempt to use a display list if requested.
@@ -15299,7 +15325,7 @@
* @param drawable the drawable to invalidate
*/
@Override
- public void invalidateDrawable(Drawable drawable) {
+ public void invalidateDrawable(@NonNull Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
@@ -15307,6 +15333,10 @@
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
+
+ if (drawable == mBackground) {
+ queryOutlineFromBackgroundIfUndefined();
+ }
}
}
@@ -18004,7 +18034,7 @@
*
* <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
* If it returns false the caller may ignore the rest of this contract until the next scroll.
- * </p>
+ * Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
*
* <p>At each incremental step of the scroll the caller should invoke
* {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll}
@@ -18028,6 +18058,10 @@
* @see #dispatchNestedScroll(int, int, int, int, int[])
*/
public boolean startNestedScroll(int axes) {
+ if (hasNestedScrollingParent()) {
+ // Already in progress
+ return true;
+ }
if (isNestedScrollingEnabled()) {
ViewParent p = getParent();
View child = this;
@@ -18830,6 +18864,22 @@
};
/**
+ * A Property wrapper around the <code>z</code> functionality handled by the
+ * {@link View#setZ(float)} and {@link View#getZ()} methods.
+ */
+ public static final Property<View, Float> Z = new FloatProperty<View>("z") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setZ(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getZ();
+ }
+ };
+
+ /**
* A Property wrapper around the <code>rotation</code> functionality handled by the
* {@link View#setRotation(float)} and {@link View#getRotation()} methods.
*/
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index ad76145..8865ab4 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2342,6 +2342,7 @@
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
+ stopNestedScroll();
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 6b21451..11d2622 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -133,21 +133,22 @@
* Constants used to associate a property being requested and the mechanism used to set
* the property (this class calls directly into View to set the properties in question).
*/
- private static final int NONE = 0x0000;
- private static final int TRANSLATION_X = 0x0001;
- private static final int TRANSLATION_Y = 0x0002;
- private static final int TRANSLATION_Z = 0x0004;
- private static final int SCALE_X = 0x0008;
- private static final int SCALE_Y = 0x0010;
- private static final int ROTATION = 0x0020;
- private static final int ROTATION_X = 0x0040;
- private static final int ROTATION_Y = 0x0080;
- private static final int X = 0x0100;
- private static final int Y = 0x0200;
- private static final int ALPHA = 0x0400;
+ static final int NONE = 0x0000;
+ static final int TRANSLATION_X = 0x0001;
+ static final int TRANSLATION_Y = 0x0002;
+ static final int TRANSLATION_Z = 0x0004;
+ static final int SCALE_X = 0x0008;
+ static final int SCALE_Y = 0x0010;
+ static final int ROTATION = 0x0020;
+ static final int ROTATION_X = 0x0040;
+ static final int ROTATION_Y = 0x0080;
+ static final int X = 0x0100;
+ static final int Y = 0x0200;
+ static final int Z = 0x0400;
+ static final int ALPHA = 0x0800;
private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z |
- SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y;
+ SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z;
/**
* The mechanism by which the user can request several properties that are then animated
@@ -470,6 +471,32 @@
}
/**
+ * This method will cause the View's <code>z</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator z(float value) {
+ animateProperty(Z, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>z</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator zBy(float value) {
+ animatePropertyBy(Z, value);
+ return this;
+ }
+
+ /**
* This method will cause the View's <code>rotation</code> property to be animated to the
* specified value. Animations already running on the property will be canceled.
*
@@ -957,6 +984,9 @@
case Y:
renderNode.setTranslationY(value - mView.mTop);
break;
+ case Z:
+ renderNode.setTranslationZ(value - renderNode.getElevation());
+ break;
case ALPHA:
info.mAlpha = value;
renderNode.setAlpha(value);
@@ -993,6 +1023,8 @@
return mView.mLeft + node.getTranslationX();
case Y:
return mView.mTop + node.getTranslationY();
+ case Z:
+ return node.getElevation() + node.getTranslationZ();
case ALPHA:
return mView.mTransformationInfo.mAlpha;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 246905d..14e422c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -678,6 +678,14 @@
}
}
+ public boolean invokeFunctor(long functor, boolean waitForCompletion) {
+ if (mAttachInfo.mHardwareRenderer == null || !mAttachInfo.mHardwareRenderer.isEnabled()) {
+ return false;
+ }
+ mAttachInfo.mHardwareRenderer.invokeFunctor(functor, waitForCompletion);
+ return true;
+ }
+
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
@@ -3204,8 +3212,11 @@
doDie();
break;
case MSG_DISPATCH_INPUT_EVENT: {
- InputEvent event = (InputEvent)msg.obj;
- enqueueInputEvent(event, null, 0, true);
+ SomeArgs args = (SomeArgs)msg.obj;
+ InputEvent event = (InputEvent)args.arg1;
+ InputEventReceiver receiver = (InputEventReceiver)args.arg2;
+ enqueueInputEvent(event, receiver, 0, true);
+ args.recycle();
} break;
case MSG_DISPATCH_KEY_FROM_IME: {
if (LOCAL_LOGV) Log.v(
@@ -5787,7 +5798,14 @@
}
public void dispatchInputEvent(InputEvent event) {
- Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, event);
+ dispatchInputEvent(event, null);
+ }
+
+ public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = event;
+ args.arg2 = receiver;
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index e3bd9fd..0227873 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -317,6 +317,10 @@
int mCursorSelEnd;
int mCursorCandStart;
int mCursorCandEnd;
+ /**
+ * The buffer to retrieve the view location in screen coordinates in {@link #updateCursor}.
+ */
+ private final int[] mViewTopLeft = new int[2];
// -----------------------------------------------------------
@@ -1487,12 +1491,26 @@
return false;
}
synchronized (mH) {
- return mCursorAnchorMonitorMode ==
- InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT;
+ return (mCursorAnchorMonitorMode &
+ InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0;
}
}
/**
+ * Returns true if the current input method wants to receive the cursor rectangle in
+ * screen coordinates rather than local coordinates in the attached view.
+ *
+ * @hide
+ */
+ public boolean usesScreenCoordinatesForCursorLocked() {
+ // {@link InputMethodService#CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT} also means
+ // that {@link InputMethodService#onUpdateCursor} should provide the cursor rectangle
+ // in screen coordinates rather than local coordinates.
+ return (mCursorAnchorMonitorMode &
+ InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0;
+ }
+
+ /**
* Set cursor/anchor monitor mode via {@link com.android.server.InputMethodManagerService}.
* This is an internal method for {@link android.inputmethodservice.InputMethodService} and
* should never be used from IMEs and applications.
@@ -1518,15 +1536,18 @@
|| mCurrentTextBoxAttribute == null || mCurMethod == null) {
return;
}
-
mTmpCursorRect.set(left, top, right, bottom);
if (!mCursorRect.equals(mTmpCursorRect)) {
if (DEBUG) Log.d(TAG, "updateCursor");
try {
if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
- mCurMethod.updateCursor(mTmpCursorRect);
mCursorRect.set(mTmpCursorRect);
+ if (usesScreenCoordinatesForCursorLocked()) {
+ view.getLocationOnScreen(mViewTopLeft);
+ mTmpCursorRect.offset(mViewTopLeft[0], mViewTopLeft[1]);
+ }
+ mCurMethod.updateCursor(mTmpCursorRect);
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
diff --git a/core/java/android/webkit/ClientCertRequest.java b/core/java/android/webkit/ClientCertRequest.java
new file mode 100644
index 0000000..8951786
--- /dev/null
+++ b/core/java/android/webkit/ClientCertRequest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+/**
+ * ClientCertRequest: The user receives an instance of this class as
+ * a parameter of {@link WebViewClient#onReceivedClientCertRequest}.
+ * The request includes the parameters to choose the client certificate,
+ * such as the host name and the port number requesting the cert, the acceptable
+ * key types and the principals.
+ *
+ * The user should call one of the interface methods to indicate how to deal
+ * with the client certificate request. All methods should be called on
+ * UI thread.
+ *
+ * WebView caches the {@link #proceed} and {@link #cancel} responses in memory
+ * and uses them to handle future client certificate requests for the same
+ * host/port pair. The user can clear the cached data using
+ * {@link WebView#clearClientCertPreferences}.
+ *
+ * TODO(sgurun) unhide
+ * @hide
+ */
+public interface ClientCertRequest {
+ /**
+ * Returns the acceptable types of asymmetric keys (can be null).
+ */
+ public String[] getKeyTypes();
+
+ /**
+ * Returns the acceptable certificate issuers for the certificate
+ * matching the private key (can be null).
+ */
+ public Principal[] getPrincipals();
+
+ /**
+ * Returns the host name of the server requesting the certificate.
+ */
+ public String getHost();
+
+ /**
+ * Returns the port number of the server requesting the certificate.
+ */
+ public int getPort();
+
+ /**
+ * Proceed with the specified private key and client certificate chain.
+ * Remember the user's positive choice and use it for future requests.
+ */
+ public void proceed(PrivateKey privateKey, X509Certificate[] chain);
+
+ /**
+ * Ignore the request for now. Do not remember user's choice.
+ */
+ public void ignore();
+
+ /**
+ * Cancel this request. Remember the user's choice and use it for
+ * future requests.
+ */
+ public void cancel();
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index d2e7324..c914e52 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -485,7 +485,7 @@
* @param privateBrowsing whether this WebView will be initialized in
* private mode
*
- * @deprecated Private browsing is no longer supported directly via
+ * @deprecated Private browsing is no longer supported directly via
* WebView and will be removed in a future release. Prefer using
* {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
* and {@link WebStorage} for fine-grained control of privacy data.
@@ -1476,6 +1476,24 @@
}
/**
+ * Clears the client certificate preferences table stored in response
+ * to proceeding/cancelling client cert requests. Note that webview
+ * automatically clears these preferences when it receives a
+ * {@link KeyChain.ACTION_STORAGE_CHANGED}
+ *
+ * @param resultCallback A callback to be invoked when client certs are cleared.
+ * The embedder can pass null if not interested in the callback.
+ *
+ * TODO(sgurun) unhide
+ * @hide
+ */
+ public void clearClientCertPreferences(ValueCallback<Void> resultCallback) {
+ checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearClientCertPreferences");
+ mProvider.clearClientCertPreferences(resultCallback);
+ }
+
+ /**
* Gets the WebBackForwardList for this WebView. This contains the
* back/forward list for use in querying each item in the history stack.
* This is a copy of the private WebBackForwardList so it contains only a
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index e8974c6..688c251 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -22,6 +22,8 @@
import android.view.KeyEvent;
import android.view.ViewRootImpl;
+import java.security.Principal;
+
public class WebViewClient {
/**
@@ -205,6 +207,30 @@
}
/**
+ * Notify the host application to handle a SSL client certificate
+ * request. The host application is responsible for showing the UI
+ * if desired and providing the keys. There are three ways to
+ * respond: proceed(), cancel() or ignore(). Webview remembers the
+ * response if proceed() or cancel() is called and does not
+ * call onReceivedClientCertRequest() again for the same host and port
+ * pair. Webview does not remember the response if ignore() is called.
+ *
+ * This method is called on the UI thread. During the callback, the
+ * connection is suspended.
+ *
+ * The default behavior is to cancel, returning no client certificate.
+ *
+ * @param view The WebView that is initiating the callback
+ * @param request An instance of a {@link ClientCertRequest}
+ *
+ * TODO(sgurun) unhide
+ * @hide
+ */
+ public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
+ request.cancel();
+ }
+
+ /**
* Notifies the host application that the WebView received an HTTP
* authentication request. The host application can use the supplied
* {@link HttpAuthHandler} to set the WebView's response to the request.
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 5081ff5..efa5497 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -198,6 +198,8 @@
public void clearSslPreferences();
+ public void clearClientCertPreferences(ValueCallback<Void> resultCallback);
+
public WebBackForwardList copyBackForwardList();
public void setFindListener(WebView.FindListener listener);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 301317e..becda67 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -543,7 +543,7 @@
/**
* The last CheckForTap runnable we posted, if any
*/
- private Runnable mPendingCheckForTap;
+ private CheckForTap mPendingCheckForTap;
/**
* The last CheckForKeyLongPress runnable we posted, if any
@@ -2126,7 +2126,9 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
+
mInLayout = true;
+
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
@@ -3185,7 +3187,10 @@
return INVALID_ROW_ID;
}
- final class CheckForTap implements Runnable {
+ private final class CheckForTap implements Runnable {
+ float x;
+ float y;
+
@Override
public void run() {
if (mTouchMode == TOUCH_MODE_DOWN) {
@@ -3205,7 +3210,7 @@
final boolean longClickable = isLongClickable();
if (mSelector != null) {
- Drawable d = mSelector.getCurrent();
+ final Drawable d = mSelector.getCurrent();
if (d != null && d instanceof TransitionDrawable) {
if (longClickable) {
((TransitionDrawable) d).startTransition(longPressTimeout);
@@ -3213,6 +3218,9 @@
((TransitionDrawable) d).resetTransition();
}
}
+ if (d.supportsHotspots()) {
+ d.setHotspot(R.attr.state_pressed, x, y);
+ }
}
if (longClickable) {
@@ -3596,6 +3604,8 @@
mPendingCheckForTap = new CheckForTap();
}
+ mPendingCheckForTap.x = ev.getX();
+ mPendingCheckForTap.y = ev.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
}
}
@@ -3705,6 +3715,9 @@
if (d != null && d instanceof TransitionDrawable) {
((TransitionDrawable) d).resetTransition();
}
+ if (mSelector.supportsHotspots()) {
+ mSelector.setHotspot(R.attr.state_pressed, x, ev.getY());
+ }
}
if (mTouchModeReset != null) {
removeCallbacks(mTouchModeReset);
@@ -3716,6 +3729,9 @@
mTouchMode = TOUCH_MODE_REST;
child.setPressed(false);
setPressed(false);
+ if (mSelector != null && mSelector.supportsHotspots()) {
+ mSelector.removeHotspot(R.attr.state_pressed);
+ }
if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
performClick.run();
}
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 74b41c9..0c31496 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -256,6 +256,16 @@
}
}
+ /**
+ * Assigns the drawable that is to be drawn on top of the assigned contact photo.
+ *
+ * @param overlay Drawable to be drawn over the assigned contact photo. Must have a non-zero
+ * instrinsic width and height.
+ */
+ public void setOverlay(Drawable overlay) {
+ mOverlay = overlay;
+ }
+
private void onContactUriChanged() {
setEnabled(isAssigned());
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 34a6a40..7e8f6b4 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -553,6 +553,7 @@
if (mIsBeingDragged && mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
+ startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}
@@ -565,6 +566,7 @@
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
postInvalidateOnAnimation();
}
+ stopNestedScroll();
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 7640749..4726da7 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -102,7 +102,7 @@
private ScrollView mScrollView;
- private int mIconId = -1;
+ private int mIconId = 0;
private Drawable mIcon;
@@ -337,25 +337,39 @@
}
/**
- * Set resId to 0 if you don't want an icon.
- * @param resId the resourceId of the drawable to use as the icon or 0
- * if you don't want an icon.
+ * Specifies the icon to display next to the alert title.
+ *
+ * @param resId the resource identifier of the drawable to use as the icon,
+ * or 0 for no icon
*/
public void setIcon(int resId) {
+ mIcon = null;
mIconId = resId;
+
if (mIconView != null) {
- if (resId > 0) {
+ if (resId != 0) {
mIconView.setImageResource(mIconId);
- } else if (resId == 0) {
+ } else {
mIconView.setVisibility(View.GONE);
}
}
}
-
+
+ /**
+ * Specifies the icon to display next to the alert title.
+ *
+ * @param icon the drawable to use as the icon or null for no icon
+ */
public void setIcon(Drawable icon) {
mIcon = icon;
- if ((mIconView != null) && (mIcon != null)) {
- mIconView.setImageDrawable(icon);
+ mIconId = 0;
+
+ if (mIconView != null) {
+ if (icon != null) {
+ mIconView.setImageDrawable(icon);
+ } else {
+ mIconView.setVisibility(View.GONE);
+ }
}
}
@@ -485,28 +499,24 @@
View titleTemplate = mWindow.findViewById(R.id.title_template);
titleTemplate.setVisibility(View.GONE);
} else {
- final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
-
mIconView = (ImageView) mWindow.findViewById(R.id.icon);
- if (hasTextTitle) {
- /* Display the title if a title is supplied, else hide it */
- mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
+ final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
+ if (hasTextTitle) {
+ // Display the title if a title is supplied, else hide it.
+ mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
mTitleView.setText(mTitle);
-
- /* Do this last so that if the user has supplied any
- * icons we use them instead of the default ones. If the
- * user has specified 0 then make it disappear.
- */
- if (mIconId > 0) {
+
+ // Do this last so that if the user has supplied any icons we
+ // use them instead of the default ones. If the user has
+ // specified 0 then make it disappear.
+ if (mIconId != 0) {
mIconView.setImageResource(mIconId);
} else if (mIcon != null) {
mIconView.setImageDrawable(mIcon);
- } else if (mIconId == 0) {
-
- /* Apply the padding from the icon to ensure the
- * title is aligned correctly.
- */
+ } else {
+ // Apply the padding from the icon to ensure the title is
+ // aligned correctly.
mTitleView.setPadding(mIconView.getPaddingLeft(),
mIconView.getPaddingTop(),
mIconView.getPaddingRight(),
@@ -514,9 +524,8 @@
mIconView.setVisibility(View.GONE);
}
} else {
-
// Hide the title template
- View titleTemplate = mWindow.findViewById(R.id.title_template);
+ final View titleTemplate = mWindow.findViewById(R.id.title_template);
titleTemplate.setVisibility(View.GONE);
mIconView.setVisibility(View.GONE);
topPanel.setVisibility(View.GONE);
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
new file mode 100644
index 0000000..3219ddd
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.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.content.Intent;
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractor;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+
+interface IVoiceInteractionManagerService {
+ void startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service,
+ in Bundle sessionArgs);
+ int deliverNewSession(IBinder token, IVoiceInteractionSession session,
+ IVoiceInteractor interactor);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
new file mode 100644
index 0000000..737906a
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * IPC interface for an application to perform calls through a VoiceInteractor.
+ */
+interface IVoiceInteractor {
+ IVoiceInteractorRequest startConfirmation(String callingPackage,
+ IVoiceInteractorCallback callback, String prompt, in Bundle extras);
+ IVoiceInteractorRequest startCommand(String callingPackage,
+ IVoiceInteractorCallback callback, String command, in Bundle extras);
+ boolean[] supportsCommands(String callingPackage, in String[] commands);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
new file mode 100644
index 0000000..c6f93e1
--- /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, boolean complete, in Bundle result);
+ void deliverCancel(IVoiceInteractorRequest request);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl
new file mode 100644
index 0000000..ce2902d
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+/**
+ * IPC interface identifying a request from an application calling through an IVoiceInteractor.
+ */
+interface IVoiceInteractorRequest {
+ void cancel();
+}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 1bfad05..446ef55 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -26,6 +26,9 @@
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.SELinux;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
import android.util.Log;
import com.android.org.bouncycastle.util.encoders.Base64;
@@ -37,10 +40,7 @@
import java.util.ArrayList;
import java.util.Collections;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-import libcore.io.StructStat;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* Backup transport for stashing stuff into a known location on disk, and
@@ -109,7 +109,7 @@
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
if (DEBUG) {
try {
- StructStat ss = Libcore.os.fstat(data.getFileDescriptor());
+ StructStat ss = Os.fstat(data.getFileDescriptor());
Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName
+ " size=" + ss.st_size);
} catch (ErrnoException e) {
@@ -152,7 +152,7 @@
changeSet.readEntityData(buf, 0, dataSize);
if (DEBUG) {
try {
- long cur = Libcore.os.lseek(data.getFileDescriptor(), 0, SEEK_CUR);
+ long cur = Os.lseek(data.getFileDescriptor(), 0, SEEK_CUR);
Log.v(TAG, " read entity data; new pos=" + cur);
}
catch (ErrnoException e) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 343c507..93d2297 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -88,7 +88,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 103 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 104 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -319,6 +319,18 @@
int mDischargeAmountScreenOff;
int mDischargeAmountScreenOffSinceCharge;
+ static final int MAX_LEVEL_STEPS = 100;
+
+ int mLastDischargeStepLevel;
+ long mLastDischargeStepTime;
+ int mNumDischargeStepDurations;
+ final long[] mDischargeStepDurations = new long[MAX_LEVEL_STEPS];
+
+ int mLastChargeStepLevel;
+ long mLastChargeStepTime;
+ int mNumChargeStepDurations;
+ final long[] mChargeStepDurations = new long[MAX_LEVEL_STEPS];
+
long mLastWriteTime = 0; // Milliseconds
private int mBluetoothPingCount;
@@ -5721,6 +5733,10 @@
mDischargeAmountScreenOnSinceCharge = 0;
mDischargeAmountScreenOff = 0;
mDischargeAmountScreenOffSinceCharge = 0;
+ mLastDischargeStepTime = -1;
+ mNumDischargeStepDurations = 0;
+ mLastChargeStepTime = -1;
+ mNumChargeStepDurations = 0;
}
public void resetAllStatsCmdLocked() {
@@ -5885,7 +5901,10 @@
resetAllStatsLocked();
mDischargeStartLevel = level;
reset = true;
+ mNumDischargeStepDurations = 0;
}
+ mLastDischargeStepLevel = level;
+ mLastDischargeStepTime = -1;
pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
@@ -5921,6 +5940,9 @@
}
updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn);
updateTimeBasesLocked(false, !mScreenOn, uptime, realtime);
+ mNumChargeStepDurations = 0;
+ mLastChargeStepLevel = level;
+ mLastChargeStepTime = -1;
}
if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
if (mFile != null) {
@@ -5944,6 +5966,24 @@
// This should probably be exposed in the API, though it's not critical
private static final int BATTERY_PLUGGED_NONE = 0;
+ private static int addLevelSteps(long[] steps, int stepCount, long lastStepTime,
+ int numStepLevels, long elapsedRealtime) {
+ if (lastStepTime >= 0 && numStepLevels > 0) {
+ long duration = elapsedRealtime - lastStepTime;
+ for (int i=0; i<numStepLevels; i++) {
+ System.arraycopy(steps, 0, steps, 1, steps.length-1);
+ long thisDuration = duration / (numStepLevels-i);
+ duration -= thisDuration;
+ steps[0] = thisDuration;
+ }
+ stepCount += numStepLevels;
+ if (stepCount > steps.length) {
+ stepCount = steps.length;
+ }
+ }
+ return stepCount;
+ }
+
public void setBatteryState(int status, int health, int plugType, int level,
int temp, int volt) {
synchronized(this) {
@@ -6021,6 +6061,23 @@
if (changed) {
addHistoryRecordLocked(elapsedRealtime, uptime);
}
+ if (onBattery) {
+ if (mLastDischargeStepLevel != level) {
+ mNumDischargeStepDurations = addLevelSteps(mDischargeStepDurations,
+ mNumDischargeStepDurations, mLastDischargeStepTime,
+ mLastDischargeStepLevel - level, elapsedRealtime);
+ mLastDischargeStepLevel = level;
+ mLastDischargeStepTime = elapsedRealtime;
+ }
+ } else {
+ if (mLastChargeStepLevel != level) {
+ mNumChargeStepDurations = addLevelSteps(mChargeStepDurations,
+ mNumChargeStepDurations, mLastChargeStepTime,
+ level - mLastChargeStepLevel, elapsedRealtime);
+ mLastChargeStepLevel = level;
+ mLastChargeStepTime = elapsedRealtime;
+ }
+ }
}
if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
// We don't record history while we are plugged in and fully charged.
@@ -6235,11 +6292,51 @@
return mOnBatteryScreenOffTimeBase.computeRealtime(curTime, which);
}
+ private long computeTimePerLevel(long[] steps, int numSteps) {
+ // For now we'll do a simple average across all steps.
+ if (numSteps <= 0) {
+ return -1;
+ }
+ long total = 0;
+ for (int i=0; i<numSteps; i++) {
+ total += steps[i];
+ }
+ return total / numSteps;
+ /*
+ long[] buckets = new long[numSteps];
+ int numBuckets = 0;
+ int numToAverage = 4;
+ int i = 0;
+ while (i < numSteps) {
+ long totalTime = 0;
+ int num = 0;
+ for (int j=0; j<numToAverage && (i+j)<numSteps; j++) {
+ totalTime += steps[i+j];
+ num++;
+ }
+ buckets[numBuckets] = totalTime / num;
+ numBuckets++;
+ numToAverage *= 2;
+ i += num;
+ }
+ if (numBuckets < 1) {
+ return -1;
+ }
+ long averageTime = buckets[numBuckets-1];
+ for (i=numBuckets-2; i>=0; i--) {
+ averageTime = (averageTime + buckets[i]) / 2;
+ }
+ return averageTime;
+ */
+ }
+
@Override
public long computeBatteryTimeRemaining(long curTime) {
if (!mOnBattery) {
return -1;
}
+ /* Simple implementation just looks at the average discharge per level across the
+ entire sample period.
int discharge = (getLowDischargeAmountSinceCharge()+getHighDischargeAmountSinceCharge())/2;
if (discharge < 2) {
return -1;
@@ -6250,14 +6347,24 @@
}
long usPerLevel = duration/discharge;
return usPerLevel * mCurrentBatteryLevel;
+ */
+ if (mNumDischargeStepDurations < 1) {
+ return -1;
+ }
+ long msPerLevel = computeTimePerLevel(mDischargeStepDurations, mNumDischargeStepDurations);
+ if (msPerLevel <= 0) {
+ return -1;
+ }
+ return (msPerLevel * mCurrentBatteryLevel) * 1000;
}
@Override
public long computeChargeTimeRemaining(long curTime) {
- if (true || mOnBattery) {
+ if (mOnBattery) {
// Not yet working.
return -1;
}
+ /* Broken
int curLevel = mCurrentBatteryLevel;
int plugLevel = mDischargePlugLevel;
if (plugLevel < 0 || curLevel < (plugLevel+1)) {
@@ -6269,6 +6376,15 @@
}
long usPerLevel = duration/(curLevel-plugLevel);
return usPerLevel * (100-curLevel);
+ */
+ if (mNumChargeStepDurations < 1) {
+ return -1;
+ }
+ long msPerLevel = computeTimePerLevel(mChargeStepDurations, mNumChargeStepDurations);
+ if (msPerLevel <= 0) {
+ return -1;
+ }
+ return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000;
}
long getBatteryUptimeLocked() {
@@ -6776,6 +6892,10 @@
mHighDischargeAmountSinceCharge = in.readInt();
mDischargeAmountScreenOnSinceCharge = in.readInt();
mDischargeAmountScreenOffSinceCharge = in.readInt();
+ mNumDischargeStepDurations = in.readInt();
+ in.readLongArray(mDischargeStepDurations);
+ mNumChargeStepDurations = in.readInt();
+ in.readLongArray(mChargeStepDurations);
mStartCount++;
@@ -7030,6 +7150,10 @@
out.writeInt(getHighDischargeAmountSinceCharge());
out.writeInt(getDischargeAmountScreenOnSinceCharge());
out.writeInt(getDischargeAmountScreenOffSinceCharge());
+ out.writeInt(mNumDischargeStepDurations);
+ out.writeLongArray(mDischargeStepDurations);
+ out.writeInt(mNumChargeStepDurations);
+ out.writeLongArray(mChargeStepDurations);
mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
@@ -7344,6 +7468,10 @@
mDischargeAmountScreenOnSinceCharge = in.readInt();
mDischargeAmountScreenOff = in.readInt();
mDischargeAmountScreenOffSinceCharge = in.readInt();
+ mNumDischargeStepDurations = in.readInt();
+ in.readLongArray(mDischargeStepDurations);
+ mNumChargeStepDurations = in.readInt();
+ in.readLongArray(mChargeStepDurations);
mLastWriteTime = in.readLong();
mBluetoothPingCount = in.readInt();
@@ -7464,6 +7592,10 @@
out.writeInt(mDischargeAmountScreenOnSinceCharge);
out.writeInt(mDischargeAmountScreenOff);
out.writeInt(mDischargeAmountScreenOffSinceCharge);
+ out.writeInt(mNumDischargeStepDurations);
+ out.writeLongArray(mDischargeStepDurations);
+ out.writeInt(mNumChargeStepDurations);
+ out.writeLongArray(mChargeStepDurations);
out.writeLong(mLastWriteTime);
out.writeInt(getBluetoothPingCount());
@@ -7573,6 +7705,25 @@
pr.println("*** Bluetooth active type #" + i + ":");
mBluetoothStateTimer[i].logState(pr, " ");
}
+ StringBuilder sb = new StringBuilder(128);
+ if (mNumDischargeStepDurations > 0) {
+ pr.println("*** Discharge step durations:");
+ for (int i=0; i<mNumDischargeStepDurations; i++) {
+ sb.setLength(0);
+ sb.append(" #"); sb.append(i); sb.append(": ");
+ formatTimeMs(sb, mDischargeStepDurations[i]);
+ pr.println(sb.toString());
+ }
+ }
+ if (mNumChargeStepDurations > 0) {
+ pr.println("*** Charge step durations:");
+ for (int i=0; i<mNumChargeStepDurations; i++) {
+ sb.setLength(0);
+ sb.append(" #"); sb.append(i); sb.append(": ");
+ formatTimeMs(sb, mChargeStepDurations[i]);
+ pr.println(sb.toString());
+ }
+ }
}
super.dumpLocked(context, pw, flags, reqUid, histStart);
}
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index d9e3ef6..40834ba 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -85,7 +85,27 @@
public void sendMessage(Message msg) {
mH.sendMessage(msg);
}
-
+
+ public SomeArgs sendMessageAndWait(Message msg) {
+ if (Looper.myLooper() == mH.getLooper()) {
+ throw new IllegalStateException("Can't wait on same thread as looper");
+ }
+ SomeArgs args = (SomeArgs)msg.obj;
+ args.mWaitState = SomeArgs.WAIT_WAITING;
+ mH.sendMessage(msg);
+ synchronized (args) {
+ while (args.mWaitState == SomeArgs.WAIT_WAITING) {
+ try {
+ args.wait();
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
+ }
+ args.mWaitState = SomeArgs.WAIT_NONE;
+ return args;
+ }
+
public Message obtainMessage(int what) {
return mH.obtainMessage(what);
}
@@ -136,6 +156,14 @@
return mH.obtainMessage(what, arg1, 0, args);
}
+ public Message obtainMessageIOOO(int what, int arg1, Object arg2, Object arg3, Object arg4) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg2;
+ args.arg2 = arg3;
+ args.arg3 = arg4;
+ return mH.obtainMessage(what, arg1, 0, args);
+ }
+
public Message obtainMessageOO(int what, Object arg1, Object arg2) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = arg1;
@@ -161,6 +189,17 @@
return mH.obtainMessage(what, 0, 0, args);
}
+ public Message obtainMessageOOOOO(int what, Object arg1, Object arg2,
+ Object arg3, Object arg4, Object arg5) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ args.arg4 = arg4;
+ args.arg5 = arg5;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
public Message obtainMessageIIII(int what, int arg1, int arg2,
int arg3, int arg4) {
SomeArgs args = SomeArgs.obtain();
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index 6fb72f1..7edf4cc 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -35,6 +35,11 @@
private boolean mInPool;
+ static final int WAIT_NONE = 0;
+ static final int WAIT_WAITING = 1;
+ static final int WAIT_FINISHED = 2;
+ int mWaitState = WAIT_NONE;
+
public Object arg1;
public Object arg2;
public Object arg3;
@@ -70,6 +75,9 @@
if (mInPool) {
throw new IllegalStateException("Already recycled.");
}
+ if (mWaitState != WAIT_NONE) {
+ return;
+ }
synchronized (sPoolLock) {
clear();
if (sPoolSize < MAX_POOL_SIZE) {
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index c5fa0a1..54c532a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -18,8 +18,8 @@
import dalvik.system.ZygoteHooks;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
+import android.system.ErrnoException;
+import android.system.Os;
/** @hide */
public final class Zygote {
@@ -141,7 +141,7 @@
public static void execShell(String command) {
String[] args = { "/system/bin/sh", "-c", command };
try {
- Libcore.os.execv(args[0], args);
+ Os.execv(args[0], args);
} catch (ErrnoException e) {
throw new RuntimeException(e);
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 58a8e62..0c48368 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -21,6 +21,8 @@
import android.os.Process;
import android.os.SELinux;
import android.os.SystemProperties;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import dalvik.system.PathClassLoader;
import java.io.BufferedReader;
@@ -34,9 +36,7 @@
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
/**
* A connection that can make spawn requests.
@@ -186,7 +186,7 @@
}
if (parsedArgs.runtimeInit && parsedArgs.invokeWith != null) {
- FileDescriptor[] pipeFds = Libcore.os.pipe();
+ FileDescriptor[] pipeFds = Os.pipe();
childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0];
ZygoteInit.setCloseOnExec(serverPipeFd, true);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ef2908d..3ea749e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -16,8 +16,8 @@
package com.android.internal.os;
-import static libcore.io.OsConstants.S_IRWXG;
-import static libcore.io.OsConstants.S_IRWXO;
+import static android.system.OsConstants.S_IRWXG;
+import static android.system.OsConstants.S_IRWXO;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -28,14 +28,15 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.EventLog;
import android.util.Log;
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
-import libcore.io.OsConstants;
import java.io.BufferedReader;
import java.io.FileDescriptor;
@@ -195,12 +196,12 @@
FileDescriptor fd = sServerSocket.getFileDescriptor();
sServerSocket.close();
if (fd != null) {
- Libcore.os.close(fd);
+ Os.close(fd);
}
}
} catch (IOException ex) {
Log.e(TAG, "Zygote: error closing sockets", ex);
- } catch (libcore.io.ErrnoException ex) {
+ } catch (ErrnoException ex) {
Log.e(TAG, "Zygote: error closing descriptor", ex);
}
@@ -481,7 +482,7 @@
closeServerSocket();
// set umask to 0077 so new files and directories will default to owner-only permissions.
- Libcore.os.umask(S_IRWXG | S_IRWXO);
+ Os.umask(S_IRWXG | S_IRWXO);
if (parsedArgs.niceName != null) {
Process.setArgV0(parsedArgs.niceName);
diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java
index e374563..bf36bb1 100644
--- a/core/java/com/android/server/SystemService.java
+++ b/core/java/com/android/server/SystemService.java
@@ -123,6 +123,38 @@
public void onBootPhase(int phase) {}
/**
+ * Called when a new user is starting, for system services to initialize any per-user
+ * state they maintain for running users.
+ * @param userHandle The identifier of the user.
+ */
+ public void onStartUser(int userHandle) {}
+
+ /**
+ * Called when switching to a different foreground user, for system services that have
+ * special behavior for whichever user is currently in the foreground. This is called
+ * before any application processes are aware of the new user.
+ * @param userHandle The identifier of the user.
+ */
+ public void onSwitchUser(int userHandle) {}
+
+ /**
+ * Called when an existing user is stopping, for system services to finalize any per-user
+ * state they maintain for running users. This is called prior to sending the SHUTDOWN
+ * broadcast to the user; it is a good place to stop making use of any resources of that
+ * user (such as binding to a service running in the user).
+ * @param userHandle The identifier of the user.
+ */
+ public void onStopUser(int userHandle) {}
+
+ /**
+ * Called when an existing user is stopping, for system services to finalize any per-user
+ * state they maintain for running users. This is called after all application process
+ * teardown of the user is complete.
+ * @param userHandle The identifier of the user.
+ */
+ public void onCleanupUser(int userHandle) {}
+
+ /**
* Publish the service so it is accessible to other services and apps.
*/
protected final void publishBinderService(String name, IBinder service) {
diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java
index eb8df0e..87a50a9 100644
--- a/core/java/com/android/server/SystemServiceManager.java
+++ b/core/java/com/android/server/SystemServiceManager.java
@@ -131,6 +131,58 @@
}
}
+ public void startUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onStartUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting start of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void switchUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onSwitchUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void stopUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onStopUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void cleanupUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onCleanupUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
/** Sets the safe mode flag for services to query. */
public void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 711f28a..ee59c8a 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -61,6 +61,7 @@
android_view_MotionEvent.cpp \
android_view_PointerIcon.cpp \
android_view_RenderNode.cpp \
+ android_view_RenderNodeAnimator.cpp \
android_view_VelocityTracker.cpp \
android_text_AndroidCharacter.cpp \
android_text_AndroidBidi.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aa635c6..f964cd2 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -120,6 +120,7 @@
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
+extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
extern int register_android_view_GraphicBuffer(JNIEnv* env);
extern int register_android_view_GLES20Canvas(JNIEnv* env);
extern int register_android_view_GLRenderer(JNIEnv* env);
@@ -1193,6 +1194,7 @@
REG_JNI(register_android_graphics_Graphics),
REG_JNI(register_android_view_DisplayEventReceiver),
REG_JNI(register_android_view_RenderNode),
+ REG_JNI(register_android_view_RenderNodeAnimator),
REG_JNI(register_android_view_GraphicBuffer),
REG_JNI(register_android_view_GLES20Canvas),
REG_JNI(register_android_view_GLRenderer),
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 5148266..2adbf3a 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -509,12 +509,11 @@
}
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 @@
{"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/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp
index 1ad1a8a..420a17f 100644
--- a/core/jni/android/graphics/Path.cpp
+++ b/core/jni/android/graphics/Path.cpp
@@ -163,42 +163,35 @@
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
obj->close();
}
-
- static void addRect__RectFI(JNIEnv* env, jobject clazz, jlong objHandle, jobject jrect, jint dirHandle) {
- SkRect rect;
- SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
- SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
- GraphicsJNI::jrectf_to_rect(env, jrect, &rect);
- obj->addRect(rect, dir);
- }
-
- static void addRect__FFFFI(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jint dirHandle) {
+
+ static void addRect(JNIEnv* env, jobject clazz, jlong objHandle,
+ jfloat left, jfloat top, jfloat right, jfloat bottom, jint dirHandle) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
obj->addRect(left, top, right, bottom, dir);
}
-
- static void addOval(JNIEnv* env, jobject clazz, jlong objHandle, jobject oval, jint dirHandle) {
+
+ static void addOval(JNIEnv* env, jobject clazz, jlong objHandle,
+ jfloat left, jfloat top, jfloat right, jfloat bottom, jint dirHandle) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
- SkRect oval_;
- GraphicsJNI::jrectf_to_rect(env, oval, &oval_);
- obj->addOval(oval_, dir);
+ SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
+ obj->addOval(oval, dir);
}
-
+
static void addCircle(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x, jfloat y, jfloat radius, jint dirHandle) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
obj->addCircle(x, y, radius, dir);
}
-
+
static void addArc(JNIEnv* env, jobject clazz, jlong objHandle, jobject oval, jfloat startAngle, jfloat sweepAngle) {
SkRect oval_;
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
GraphicsJNI::jrectf_to_rect(env, oval, &oval_);
obj->addArc(oval_, startAngle, sweepAngle);
}
-
+
static void addRoundRectXY(JNIEnv* env, jobject clazz, jlong objHandle, jobject jrect,
jfloat rx, jfloat ry, jint dirHandle) {
SkRect rect;
@@ -496,9 +489,8 @@
{"native_rCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo},
{"native_arcTo","(JLandroid/graphics/RectF;FFZ)V", (void*) SkPathGlue::arcTo},
{"native_close","(J)V", (void*) SkPathGlue::close},
- {"native_addRect","(JLandroid/graphics/RectF;I)V", (void*) SkPathGlue::addRect__RectFI},
- {"native_addRect","(JFFFFI)V", (void*) SkPathGlue::addRect__FFFFI},
- {"native_addOval","(JLandroid/graphics/RectF;I)V", (void*) SkPathGlue::addOval},
+ {"native_addRect","(JFFFFI)V", (void*) SkPathGlue::addRect},
+ {"native_addOval","(JFFFFI)V", (void*) SkPathGlue::addOval},
{"native_addCircle","(JFFFI)V", (void*) SkPathGlue::addCircle},
{"native_addArc","(JLandroid/graphics/RectF;FF)V", (void*) SkPathGlue::addArc},
{"native_addRoundRect","(JLandroid/graphics/RectF;FFI)V", (void*) SkPathGlue::addRoundRectXY},
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index a4efed7..01f4d3a 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -30,15 +30,16 @@
#include "android_util_Binder.h"
#include "JNIHelp.h"
-#include <sys/errno.h>
-#include <sys/resource.h>
-#include <sys/types.h>
-#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <grp.h>
+#include <inttypes.h>
#include <pwd.h>
#include <signal.h>
+#include <sys/errno.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
#define POLICY_DEBUG 0
@@ -159,7 +160,7 @@
void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint grp)
{
- ALOGV("%s tid=%d grp=%d", __func__, tid, grp);
+ ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp);
SchedPolicy sp = (SchedPolicy) grp;
int res = set_sched_policy(tid, sp);
if (res != NO_ERROR) {
@@ -169,7 +170,7 @@
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
- ALOGV("%s pid=%d grp=%d", __func__, pid, grp);
+ ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
DIR *d;
FILE *fp;
char proc_path[255];
@@ -322,7 +323,7 @@
}
}
- //ALOGI("Setting priority of %d: %d, getpriority returns %d\n",
+ //ALOGI("Setting priority of %" PRId32 ": %" PRId32 ", getpriority returns %d\n",
// pid, pri, getpriority(PRIO_PROCESS, pid));
}
@@ -340,7 +341,7 @@
if (errno != 0) {
signalExceptionForPriorityError(env, errno);
}
- //ALOGI("Returning priority of %d: %d\n", pid, pri);
+ //ALOGI("Returning priority of %" PRId32 ": %" PRId32 "\n", pid, pri);
return pri;
}
@@ -362,7 +363,7 @@
int fd = open(text, O_WRONLY);
if (fd >= 0) {
- sprintf(text, "%d", pid);
+ sprintf(text, "%" PRId32, pid);
write(fd, text, strlen(text));
close(fd);
}
@@ -403,7 +404,7 @@
static int pid_compare(const void* v1, const void* v2)
{
- //ALOGI("Compare %d vs %d\n", *((const jint*)v1), *((const jint*)v2));
+ //ALOGI("Compare %" PRId32 " vs %" PRId32 "\n", *((const jint*)v1), *((const jint*)v2));
return *((const jint*)v1) - *((const jint*)v2);
}
@@ -517,7 +518,7 @@
return;
}
- //ALOGI("Clearing %d sizes", count);
+ //ALOGI("Clearing %" PRId32 " sizes", count);
for (i=0; i<count; i++) {
sizesArray[i] = 0;
}
@@ -556,7 +557,7 @@
}
char* end;
sizesArray[i] = strtoll(num, &end, 10);
- //ALOGI("Field %s = %d", field.string(), sizesArray[i]);
+ //ALOGI("Field %s = %" PRId64, field.string(), sizesArray[i]);
foundCount++;
break;
}
@@ -758,7 +759,7 @@
}
}
- //ALOGI("Field %d: %d-%d dest=%d mode=0x%x\n", i, start, end, di, mode);
+ //ALOGI("Field %" PRId32 ": %" PRId32 "-%" PRId32 " dest=%" PRId32 " mode=0x%" PRIx32 "\n", i, start, end, di, mode);
if ((mode&(PROC_OUT_FLOAT|PROC_OUT_LONG|PROC_OUT_STRING)) != 0) {
char c = buffer[end];
@@ -857,7 +858,7 @@
void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig)
{
if (pid > 0) {
- ALOGI("Sending signal. PID: %d SIG: %d", pid, sig);
+ ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
kill(pid, sig);
}
}
@@ -887,7 +888,7 @@
{
char filename[64];
- snprintf(filename, sizeof(filename), "/proc/%d/smaps", pid);
+ snprintf(filename, sizeof(filename), "/proc/%" PRId32 "/smaps", pid);
FILE * file = fopen(filename, "r");
if (!file) {
@@ -899,7 +900,7 @@
jlong pss = 0;
while (fgets(line, sizeof(line), file)) {
jlong v;
- if (sscanf(line, "Pss: %lld kB", &v) == 1) {
+ if (sscanf(line, "Pss: %" SCNd64 " kB", &v) == 1) {
pss += v;
}
}
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 8dacfeb..4715c26 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -23,6 +23,7 @@
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
+#include <Animator.h>
#include <DisplayListRenderer.h>
#include <RenderNode.h>
@@ -164,6 +165,12 @@
renderNode->mutateStagingProperties().setHasOverlappingRendering(hasOverlappingRendering);
}
+static void android_view_RenderNode_setElevation(JNIEnv* env,
+ jobject clazz, jlong renderNodePtr, float elevation) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ renderNode->mutateStagingProperties().setElevation(elevation);
+}
+
static void android_view_RenderNode_setTranslationX(JNIEnv* env,
jobject clazz, jlong renderNodePtr, float tx) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -331,6 +338,12 @@
return renderNode->stagingProperties().getScaleY();
}
+static jfloat android_view_RenderNode_getElevation(JNIEnv* env,
+ jobject clazz, jlong renderNodePtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ return renderNode->stagingProperties().getElevation();
+}
+
static jfloat android_view_RenderNode_getTranslationX(JNIEnv* env,
jobject clazz, jlong renderNodePtr) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -426,6 +439,25 @@
return renderNode->stagingProperties().getPivotY();
}
+// ----------------------------------------------------------------------------
+// RenderProperties - Animations
+// ----------------------------------------------------------------------------
+
+static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz,
+ jlong renderNodePtr, jlong animatorPtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
+ renderNode->addAnimator(animator);
+}
+
+static void android_view_RenderNode_removeAnimator(JNIEnv* env, jobject clazz,
+ jlong renderNodePtr, jlong animatorPtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
+ renderNode->removeAnimator(animator);
+}
+
+
#endif // USE_OPENGL_RENDERER
// ----------------------------------------------------------------------------
@@ -457,6 +489,7 @@
{ "nSetAlpha", "(JF)V", (void*) android_view_RenderNode_setAlpha },
{ "nSetHasOverlappingRendering", "(JZ)V",
(void*) android_view_RenderNode_setHasOverlappingRendering },
+ { "nSetElevation", "(JF)V", (void*) android_view_RenderNode_setElevation },
{ "nSetTranslationX", "(JF)V", (void*) android_view_RenderNode_setTranslationX },
{ "nSetTranslationY", "(JF)V", (void*) android_view_RenderNode_setTranslationY },
{ "nSetTranslationZ", "(JF)V", (void*) android_view_RenderNode_setTranslationZ },
@@ -485,6 +518,7 @@
{ "nGetCameraDistance", "(J)F", (void*) android_view_RenderNode_getCameraDistance },
{ "nGetScaleX", "(J)F", (void*) android_view_RenderNode_getScaleX },
{ "nGetScaleY", "(J)F", (void*) android_view_RenderNode_getScaleY },
+ { "nGetElevation", "(J)F", (void*) android_view_RenderNode_getElevation },
{ "nGetTranslationX", "(J)F", (void*) android_view_RenderNode_getTranslationX },
{ "nGetTranslationY", "(J)F", (void*) android_view_RenderNode_getTranslationY },
{ "nGetTranslationZ", "(J)F", (void*) android_view_RenderNode_getTranslationZ },
@@ -499,6 +533,9 @@
{ "nGetPivotX", "(J)F", (void*) android_view_RenderNode_getPivotX },
{ "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY },
+
+ { "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
+ { "nRemoveAnimator", "(JJ)V", (void*) android_view_RenderNode_removeAnimator },
#endif
};
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
new file mode 100644
index 0000000..35cdf60
--- /dev/null
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2013 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 "OpenGLRenderer"
+
+#include "android_view_RenderNodeAnimator.h"
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <Animator.h>
+#include <Interpolator.h>
+#include <RenderProperties.h>
+
+namespace android {
+
+using namespace uirenderer;
+
+static struct {
+ jclass clazz;
+
+ jmethodID callOnFinished;
+} gRenderNodeAnimatorClassInfo;
+
+static JNIEnv* getEnv(JavaVM* vm) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return 0;
+ }
+ return env;
+}
+
+RenderNodeAnimator::RenderNodeAnimator(JNIEnv* env, jobject weakThis,
+ RenderProperty property, DeltaValueType deltaType, float delta)
+ : RenderPropertyAnimator(property, deltaType, delta) {
+ mWeakThis = env->NewGlobalRef(weakThis);
+ env->GetJavaVM(&mJvm);
+}
+
+RenderNodeAnimator::~RenderNodeAnimator() {
+ JNIEnv* env = getEnv(mJvm);
+ env->DeleteGlobalRef(mWeakThis);
+ mWeakThis = NULL;
+}
+
+void RenderNodeAnimator::callOnFinished() {
+ JNIEnv* env = getEnv(mJvm);
+ env->CallStaticVoidMethod(
+ gRenderNodeAnimatorClassInfo.clazz,
+ gRenderNodeAnimatorClassInfo.callOnFinished,
+ mWeakThis);
+}
+
+static jlong createAnimator(JNIEnv* env, jobject clazz, jobject weakThis,
+ jint property, jint deltaType, jfloat deltaValue) {
+ LOG_ALWAYS_FATAL_IF(property < 0 || property > RenderNodeAnimator::ALPHA,
+ "Invalid property %d", property);
+ LOG_ALWAYS_FATAL_IF(deltaType != RenderPropertyAnimator::DELTA
+ && deltaType != RenderPropertyAnimator::ABSOLUTE,
+ "Invalid delta type %d", deltaType);
+
+ RenderNodeAnimator* animator = new RenderNodeAnimator(env, weakThis,
+ static_cast<RenderPropertyAnimator::RenderProperty>(property),
+ static_cast<RenderPropertyAnimator::DeltaValueType>(deltaType),
+ deltaValue);
+ animator->incStrong(0);
+ return reinterpret_cast<jlong>( animator );
+}
+
+static void setDuration(JNIEnv* env, jobject clazz, jlong animatorPtr, jint duration) {
+ LOG_ALWAYS_FATAL_IF(duration < 0, "Duration cannot be negative");
+ RenderNodeAnimator* animator = reinterpret_cast<RenderNodeAnimator*>(animatorPtr);
+ animator->setDuration(duration);
+}
+
+static void unref(JNIEnv* env, jobject clazz, jlong objPtr) {
+ VirtualLightRefBase* obj = reinterpret_cast<VirtualLightRefBase*>(objPtr);
+ obj->decStrong(0);
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/RenderNodeAnimator";
+
+static JNINativeMethod gMethods[] = {
+ { "nCreateAnimator", "(Ljava/lang/ref/WeakReference;IIF)J", (void*) createAnimator },
+ { "nSetDuration", "(JI)V", (void*) setDuration },
+ { "nUnref", "(J)V", (void*) unref },
+};
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+ var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+int register_android_view_RenderNodeAnimator(JNIEnv* env) {
+ FIND_CLASS(gRenderNodeAnimatorClassInfo.clazz, kClassPathName);
+ gRenderNodeAnimatorClassInfo.clazz = jclass(env->NewGlobalRef(gRenderNodeAnimatorClassInfo.clazz));
+
+ GET_STATIC_METHOD_ID(gRenderNodeAnimatorClassInfo.callOnFinished, gRenderNodeAnimatorClassInfo.clazz,
+ "callOnFinished", "(Ljava/lang/ref/WeakReference;)V");
+
+ return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+
+} // namespace android
diff --git a/core/jni/android_view_RenderNodeAnimator.h b/core/jni/android_view_RenderNodeAnimator.h
new file mode 100644
index 0000000..d84003f
--- /dev/null
+++ b/core/jni/android_view_RenderNodeAnimator.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+
+#include <Animator.h>
+
+namespace android {
+
+class RenderNodeAnimator : public uirenderer::RenderPropertyAnimator {
+public:
+ RenderNodeAnimator(JNIEnv* env, jobject callbackObject,
+ RenderProperty property, DeltaValueType deltaType, float delta);
+ virtual ~RenderNodeAnimator();
+
+ void callOnFinished();
+
+private:
+ JavaVM* mJvm;
+ jobject mWeakThis;
+};
+
+}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 8141a00..c293c7a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -508,7 +508,7 @@
nsecs_t presentedTimesNanoSrc[frameCount];
for (size_t i = 0; i < frameCount; i++) {
- nsecs_t presentedTimeNano = stats.desiredPresentTimesNano[i];
+ nsecs_t presentedTimeNano = stats.actualPresentTimesNano[i];
if (presentedTimeNano == INT64_MAX) {
presentedTimeNano = gWindowContentFrameStatsClassInfo.UNDEFINED_TIME_NANO;
}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index b5f489d..58fc1e1 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -16,6 +16,8 @@
#define LOG_TAG "ThreadedRenderer"
+#include <algorithm>
+
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
@@ -24,6 +26,8 @@
#include <android_runtime/android_view_Surface.h>
#include <system/window.h>
+#include "android_view_RenderNodeAnimator.h"
+#include <RenderNode.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
@@ -63,15 +67,75 @@
jobject mRunnable;
};
+class InvokeAnimationListeners : public MessageHandler {
+public:
+ InvokeAnimationListeners(std::vector< sp<RenderNodeAnimator> >& animators) {
+ mAnimators.swap(animators);
+ }
+
+ static void callOnFinished(const sp<RenderNodeAnimator>& animator) {
+ animator->callOnFinished();
+ }
+
+ virtual void handleMessage(const Message& message) {
+ std::for_each(mAnimators.begin(), mAnimators.end(), callOnFinished);
+ mAnimators.clear();
+ }
+
+private:
+ std::vector< sp<RenderNodeAnimator> > mAnimators;
+};
+
+class RootRenderNode : public RenderNode, public AnimationListener {
+public:
+ RootRenderNode() : RenderNode() {
+ mLooper = Looper::getForThread();
+ LOG_ALWAYS_FATAL_IF(!mLooper.get(),
+ "Must create RootRenderNode on a thread with a looper!");
+ }
+
+ virtual ~RootRenderNode() {}
+
+ void onAnimationFinished(const sp<RenderPropertyAnimator>& animator) {
+ mFinishedAnimators.push_back(
+ reinterpret_cast<RenderNodeAnimator*>(animator.get()));
+ }
+
+ virtual void prepareTree(TreeInfo& info) {
+ info.animationListener = this;
+ RenderNode::prepareTree(info);
+ info.animationListener = NULL;
+
+ // post all the finished stuff
+ if (mFinishedAnimators.size()) {
+ sp<InvokeAnimationListeners> message
+ = new InvokeAnimationListeners(mFinishedAnimators);
+ mLooper->sendMessage(message, 0);
+ }
+ }
+
+private:
+ sp<Looper> mLooper;
+ std::vector< sp<RenderNodeAnimator> > mFinishedAnimators;
+};
+
static void android_view_ThreadedRenderer_postToRenderThread(JNIEnv* env, jobject clazz,
jobject jrunnable) {
RenderTask* task = new JavaTask(env, jrunnable);
RenderThread::getInstance().queue(task);
}
+static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
+ RootRenderNode* node = new RootRenderNode();
+ node->incStrong(0);
+ node->setName("RootRenderNode");
+ return reinterpret_cast<jlong>(node);
+}
+
static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
- jboolean translucent) {
- return (jlong) new RenderProxy(translucent);
+ jboolean translucent, jlong rootRenderNodePtr) {
+ RenderNode* rootRenderNode = reinterpret_cast<RenderNode*>(rootRenderNodePtr);
+ return (jlong) new RenderProxy(translucent, rootRenderNode);
}
static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz,
@@ -113,12 +177,11 @@
proxy->setup(width, height);
}
-static void android_view_ThreadedRenderer_drawDisplayList(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jlong displayListPtr, jint dirtyLeft, jint dirtyTop,
+static void android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jint dirtyLeft, jint dirtyTop,
jint dirtyRight, jint dirtyBottom) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
- proxy->drawDisplayList(displayList, dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
+ proxy->syncAndDrawFrame(dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
}
static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, jobject clazz,
@@ -187,13 +250,14 @@
static JNINativeMethod gMethods[] = {
#ifdef USE_OPENGL_RENDERER
{ "postToRenderThread", "(Ljava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_postToRenderThread },
- { "nCreateProxy", "(Z)J", (void*) android_view_ThreadedRenderer_createProxy },
+ { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
+ { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
{ "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
{ "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
{ "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
{ "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface },
{ "nSetup", "(JII)V", (void*) android_view_ThreadedRenderer_setup },
- { "nDrawDisplayList", "(JJIIII)V", (void*) android_view_ThreadedRenderer_drawDisplayList },
+ { "nSyncAndDrawFrame", "(JIIII)V", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
{ "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface },
{ "nInvokeFunctor", "(JJZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
{ "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57e845f..4f093a8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2056,6 +2056,13 @@
android:description="@string/permdesc_bindWallpaper"
android:protectionLevel="signature|system" />
+ <!-- Must be required by a {@link android.service.voice.VoiceInteractionService},
+ to ensure that only the system can bind to it. -->
+ <permission android:name="android.permission.BIND_VOICE_INTERACTION"
+ android:label="@string/permlab_bindVoiceInteraction"
+ android:description="@string/permdesc_bindVoiceInteraction"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
to ensure that only the system can bind to it.
@hide -->
@@ -2620,6 +2627,15 @@
android:description="@string/permdesc_bindNotificationListenerService"
android:protectionLevel="signature" />
+ <!-- Must be required by an {@link
+ android.service.notification.ConditionProviderService},
+ to ensure that only the system can bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_CONDITION_PROVIDER_SERVICE"
+ android:label="@string/permlab_bindConditionProviderService"
+ android:description="@string/permdesc_bindConditionProviderService"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to call into a carrier setup flow. It is up to the
carrier setup application to enforce that this permission is required
@hide This is not a third-party API (intended for OEMs and system apps). -->
diff --git a/core/res/res/drawable/btn_borderless_quantum.xml b/core/res/res/drawable/btn_borderless_quantum.xml
index 69a891a..2e3c515 100644
--- a/core/res/res/drawable/btn_borderless_quantum.xml
+++ b/core/res/res/drawable/btn_borderless_quantum.xml
@@ -16,7 +16,6 @@
<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="?attr/colorButtonPressed">
- <item android:drawable="@color/transparent" />
<item android:id="@id/mask"
android:drawable="@drawable/btn_qntm_alpha" />
</touch-feedback>
diff --git a/core/res/res/drawable/dialog_background_quantum.xml b/core/res/res/drawable/dialog_background_quantum.xml
new file mode 100644
index 0000000..7e5b003
--- /dev/null
+++ b/core/res/res/drawable/dialog_background_quantum.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+
+ <corners
+ android:radius="2dp" />
+ <solid
+ android:color="?attr/colorBackground" />
+
+</shape>
diff --git a/core/res/res/drawable/list_selector_quantum.xml b/core/res/res/drawable/list_selector_quantum.xml
new file mode 100644
index 0000000..c007117
--- /dev/null
+++ b/core/res/res/drawable/list_selector_quantum.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.
+-->
+
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="?attr/colorButtonPressed">
+ <item android:id="@id/mask">
+ <color android:color="@color/white" />
+ </item>
+</touch-feedback>
diff --git a/core/res/res/layout/alert_dialog_quantum.xml b/core/res/res/layout/alert_dialog_quantum.xml
index 537162a..80e34cb 100644
--- a/core/res/res/layout/alert_dialog_quantum.xml
+++ b/core/res/res/layout/alert_dialog_quantum.xml
@@ -20,7 +20,10 @@
android:id="@+id/parentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:background="@drawable/dialog_background_quantum"
+ android:translationZ="@dimen/floating_window_z"
+ android:layout_margin="@dimen/floating_window_margin">
<LinearLayout android:id="@+id/topPanel"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/simple_list_item_2_single_choice.xml b/core/res/res/layout/simple_list_item_2_single_choice.xml
index 65c5856..940c6b8 100644
--- a/core/res/res/layout/simple_list_item_2_single_choice.xml
+++ b/core/res/res/layout/simple_list_item_2_single_choice.xml
@@ -21,7 +21,8 @@
android:gravity="center_vertical"
android:paddingStart="16dip"
android:paddingEnd="12dip"
- android:minHeight="?android:attr/listPreferredItemHeightSmall">
+ android:minHeight="?attr/listPreferredItemHeightSmall"
+ android:background="@color/transparent">
<LinearLayout
android:layout_width="wrap_content"
@@ -33,8 +34,8 @@
<TextView android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceListItem"
- android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:textAppearance="?attr/textAppearanceListItem"
+ android:textColor="?attr/textColorAlertDialogListItem"
android:gravity="center_vertical|start"
android:singleLine="true"
android:ellipsize="marquee" />
@@ -42,8 +43,8 @@
<TextView android:id="@android:id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceListItemSecondary"
- android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:textAppearance="?attr/textAppearanceListItemSecondary"
+ android:textColor="?attr/textColorAlertDialogListItem"
android:gravity="center_vertical|start"
android:singleLine="true"
android:ellipsize="marquee" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 508a557..abac60e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2191,6 +2191,9 @@
(completely opaque). -->
<attr name="alpha" format="float" />
+ <!-- base z depth of the view -->
+ <attr name="elevation" format="dimension" />
+
<!-- translation in x of the view. This value is added post-layout to the left
property of the view, which is set by its layout. -->
<attr name="translationX" format="dimension" />
@@ -2199,7 +2202,7 @@
property of the view, which is set by its layout. -->
<attr name="translationY" format="dimension" />
- <!-- translation in z of the view. This value is added post-layout to its position. -->
+ <!-- translation in z of the view. This value is added to its elevation. -->
<attr name="translationZ" format="dimension" />
<!-- x location of the pivot point around which the view will rotate and scale.
@@ -6267,13 +6270,22 @@
</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="sessionService" format="string" />
+ <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/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml
index 02e61e2..cebee12 100644
--- a/core/res/res/values/dimens_quantum.xml
+++ b/core/res/res/values/dimens_quantum.xml
@@ -47,4 +47,6 @@
<dimen name="text_size_menu_quantum">14sp</dimen>
<dimen name="text_size_button_quantum">14sp</dimen>
+ <dimen name="floating_window_z">16dp</dimen>
+ <dimen name="floating_window_margin">32dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6491b33..85ef004 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2155,6 +2155,15 @@
<public type="attr" name="colorPrimaryDark" />
<public type="attr" name="colorAccent" />
<public type="attr" name="nestedScrollingEnabled" />
+ <public type="attr" name="windowEnterTransition" />
+ <public type="attr" name="windowExitTransition" />
+ <public type="attr" name="windowSharedElementEnterTransition" />
+ <public type="attr" name="windowSharedElementExitTransition" />
+ <public type="attr" name="windowAllowExitTransitionOverlap" />
+ <public type="attr" name="windowAllowEnterTransitionOverlap" />
+ <public type="attr" name="sessionService" />
+ <public type="attr" name="switchStyle" />
+ <public type="attr" name="elevation" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
@@ -2385,17 +2394,14 @@
<public type="style" name="Widget.Holo.Light.Button.Borderless" />
+ <public-padding type="interpolator" name="l_resource_pad" end="0x010c0010" />
+
<!-- An interpolator which accelerates fast but decelerates slowly. -->
<public type="interpolator" name="fast_out_slow_in" />
<!-- An interpolator which starts with a peak non-zero velocity and decelerates slowly. -->
<public type="interpolator" name="linear_out_slow_in" />
<!-- An interpolator which accelerates fast and keeps accelerating until the end. -->
<public type="interpolator" name="fast_out_linear_in" />
- <public type="attr" name="windowEnterTransition" />
- <public type="attr" name="windowExitTransition" />
- <public type="attr" name="windowSharedElementEnterTransition" />
- <public type="attr" name="windowSharedElementExitTransition" />
- <public type="attr" name="windowAllowExitTransitionOverlap" />
- <public type="attr" name="windowAllowEnterTransitionOverlap" />
+
<public type="transition" name="no_transition" id="0x010f0000"/>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6d4ceef..57b2c01 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -36,6 +36,45 @@
the placeholders. -->
<string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g><xliff:g id="unit" example="KB">%2$s</xliff:g></string>
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration in days -->
+ <string name="durationDays"><xliff:g id="days">%1$d</xliff:g> days</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration of one day with hours -->
+ <string name="durationDayHours"><xliff:g id="days">%1$d</xliff:g> day
+ <xliff:g id="hours">%2$d</xliff:g> hrs</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration of one day with one hours -->
+ <string name="durationDayHour"><xliff:g id="days">%1$d</xliff:g> day
+ <xliff:g id="hours">%2$d</xliff:g> hr</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration in hours -->
+ <string name="durationHours"><xliff:g id="hours">%1$d</xliff:g> hrs</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration of one hour with minutes -->
+ <string name="durationHourMinutes"><xliff:g id="hours">%1$d</xliff:g> hr
+ <xliff:g id="minutes">%2$d</xliff:g> mins</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration of one hour with one minute -->
+ <string name="durationHourMinute"><xliff:g id="hours">%1$d</xliff:g> hr
+ <xliff:g id="minutes">%2$d</xliff:g> min</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration in minutes -->
+ <string name="durationMinutes"><xliff:g id="minutes">%1$d</xliff:g> mins</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration of one minute with seconds -->
+ <string name="durationMinuteSeconds"><xliff:g id="minutes">%1$d</xliff:g> min
+ <xliff:g id="seconds">%2$d</xliff:g> secs</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration of one minute with one second -->
+ <string name="durationMinuteSecond"><xliff:g id="minutes">%1$d</xliff:g> min
+ <xliff:g id="seconds">%2$d</xliff:g> sec</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration in seconds -->
+ <string name="durationSeconds"><xliff:g id="seconds">%1$d</xliff:g> secs</string>
+
+ <!-- [CHAR_LIMIT=10] Suffix added to signify duration of one second -->
+ <string name="durationSecond"><xliff:g id="seconds">%1$d</xliff:g> sec</string>
+
<!-- Used in Contacts for a field that has no label and in Note Pad
for a note with no name. -->
<string name="untitled"><Untitled></string>
@@ -1068,6 +1107,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 +2093,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 +3847,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/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index 0cbb655..57f2443 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -480,7 +480,7 @@
<style name="Widget.Quantum.AbsListView" parent="Widget.AbsListView"/>
<style name="Widget.Quantum.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
- <item name="dropDownSelector">@drawable/list_selector_holo_dark</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
</style>
@@ -654,7 +654,7 @@
<style name="Widget.Quantum.Spinner" parent="Widget.Spinner.DropDown">
<item name="background">@drawable/spinner_background_quantum</item>
- <item name="dropDownSelector">@drawable/list_selector_holo_dark</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
@@ -713,7 +713,7 @@
<style name="Widget.Quantum.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge"/>
<style name="Widget.Quantum.ListPopupWindow" parent="Widget.ListPopupWindow">
- <item name="dropDownSelector">@drawable/list_selector_holo_dark</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
@@ -851,7 +851,7 @@
<style name="Widget.Quantum.Light.AbsListView" parent="Widget.Quantum.AbsListView"/>
<style name="Widget.Quantum.Light.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
- <item name="dropDownSelector">@drawable/list_selector_holo_light</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
</style>
@@ -938,7 +938,7 @@
<style name="Widget.Quantum.Light.Spinner" parent="Widget.Quantum.Spinner">
<item name="background">@drawable/spinner_background_quantum</item>
- <item name="dropDownSelector">@drawable/list_selector_holo_light</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
@@ -962,7 +962,7 @@
<style name="Widget.Quantum.Light.QuickContactBadgeSmall.WindowLarge" parent="Widget.Quantum.QuickContactBadgeSmall.WindowLarge"/>
<style name="Widget.Quantum.Light.ListPopupWindow" parent="Widget.ListPopupWindow">
- <item name="dropDownSelector">@drawable/list_selector_holo_light</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
@@ -1019,16 +1019,16 @@
<!-- Dialog styles -->
<style name="AlertDialog.Quantum" parent="AlertDialog">
- <item name="fullDark">?attr/colorBackground</item>
- <item name="topDark">?attr/colorBackground</item>
- <item name="centerDark">?attr/colorBackground</item>
- <item name="bottomDark">?attr/colorBackground</item>
- <item name="fullBright">?attr/colorBackground</item>
- <item name="topBright">?attr/colorBackground</item>
- <item name="centerBright">?attr/colorBackground</item>
- <item name="bottomBright">?attr/colorBackground</item>
- <item name="bottomMedium">?attr/colorBackground</item>
- <item name="centerMedium">?attr/colorBackground</item>
+ <item name="fullDark">@color/transparent</item>
+ <item name="topDark">@color/transparent</item>
+ <item name="centerDark">@color/transparent</item>
+ <item name="bottomDark">@color/transparent</item>
+ <item name="fullBright">@color/transparent</item>
+ <item name="topBright">@color/transparent</item>
+ <item name="centerBright">@color/transparent</item>
+ <item name="bottomBright">@color/transparent</item>
+ <item name="bottomMedium">@color/transparent</item>
+ <item name="centerMedium">@color/transparent</item>
<item name="layout">@layout/alert_dialog_quantum</item>
<item name="listLayout">@layout/select_dialog_quantum</item>
<item name="progressLayout">@layout/progress_dialog_quantum</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bb0d184..d4ac74a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -233,7 +233,6 @@
<java-symbol type="attr" name="searchDialogTheme" />
<java-symbol type="attr" name="searchViewSearchIcon" />
<java-symbol type="attr" name="stackViewStyle" />
- <java-symbol type="attr" name="switchStyle" />
<java-symbol type="attr" name="textAppearanceAutoCorrectionSuggestion" />
<java-symbol type="attr" name="textAppearanceEasyCorrectSuggestion" />
<java-symbol type="attr" name="textAppearanceMisspelledSuggestion" />
@@ -506,6 +505,17 @@
<java-symbol type="string" name="display_manager_overlay_display_secure_suffix" />
<java-symbol type="string" name="display_manager_overlay_display_title" />
<java-symbol type="string" name="double_tap_toast" />
+ <java-symbol type="string" name="durationDays" />
+ <java-symbol type="string" name="durationDayHours" />
+ <java-symbol type="string" name="durationDayHour" />
+ <java-symbol type="string" name="durationHours" />
+ <java-symbol type="string" name="durationHourMinutes" />
+ <java-symbol type="string" name="durationHourMinute" />
+ <java-symbol type="string" name="durationMinutes" />
+ <java-symbol type="string" name="durationMinuteSeconds" />
+ <java-symbol type="string" name="durationMinuteSecond" />
+ <java-symbol type="string" name="durationSeconds" />
+ <java-symbol type="string" name="durationSecond" />
<java-symbol type="string" name="elapsed_time_short_format_h_mm_ss" />
<java-symbol type="string" name="elapsed_time_short_format_mm_ss" />
<java-symbol type="string" name="emailTypeCustom" />
@@ -1580,6 +1590,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/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index c2e31f4..9f76eae 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -125,7 +125,7 @@
<item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum</item>
<item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item>
- <item name="listChoiceBackgroundIndicator">@drawable/list_selector_holo_dark</item>
+ <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item>
<item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item>
@@ -468,7 +468,7 @@
<item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum</item>
<item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item>
- <item name="listChoiceBackgroundIndicator">@drawable/list_selector_holo_light</item>
+ <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item>
<item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item>
@@ -898,7 +898,7 @@
<style name="Theme.Quantum.Dialog">
<item name="windowFrame">@null</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Quantum</item>
- <item name="windowBackground">?attr/colorBackground</item>
+ <item name="windowBackground">@drawable/dialog_background_quantum</item>
<item name="windowIsFloating">true</item>
<item name="windowContentOverlay">@null</item>
<item name="windowAnimationStyle">@style/Animation.Quantum.Dialog</item>
@@ -964,8 +964,6 @@
its pixels. -->
<style name="Theme.Quantum.Dialog.NoFrame">
<item name="windowBackground">@color/transparent</item>
- <item name="windowFrame">@null</item>
- <item name="windowContentOverlay">@null</item>
<item name="windowAnimationStyle">@null</item>
<item name="backgroundDimEnabled">false</item>
<item name="windowIsTranslucent">true</item>
@@ -981,7 +979,6 @@
<style name="Theme.Quantum.Dialog.Alert">
<item name="windowBackground">@color/transparent</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Quantum</item>
- <item name="windowContentOverlay">@null</item>
<item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
<item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
</style>
@@ -1102,7 +1099,6 @@
<style name="Theme.Quantum.Light.Dialog.Alert">
<item name="windowBackground">@color/transparent</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Quantum.Light</item>
- <item name="windowContentOverlay">@null</item>
<item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
<item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
</style>
@@ -1112,7 +1108,6 @@
<style name="Theme.Quantum.Light.Dialog.TimePicker">
<item name="windowBackground">@color/transparent</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Quantum.Light</item>
- <item name="windowContentOverlay">@null</item>
</style>
<!-- Theme for a presentation window on a secondary display. -->
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index e77564f..07a6a10 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -16,7 +16,7 @@
package android.content.pm;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
import com.android.frameworks.coretests.R;
import com.android.internal.content.PackageHelper;
@@ -48,6 +48,9 @@
import android.os.storage.StorageResultCode;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
@@ -62,10 +65,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-import libcore.io.StructStat;
-
public class PackageManagerTests extends AndroidTestCase {
private static final boolean localLOGV = true;
@@ -503,7 +502,7 @@
final StructStat stat;
try {
- stat = Libcore.os.lstat(path);
+ stat = Os.lstat(path);
} catch (ErrnoException e) {
throw new AssertionError(reason + "\n" + "Got: " + path + " does not exist");
}
diff --git a/core/tests/coretests/src/android/net/LinkAddressTest.java b/core/tests/coretests/src/android/net/LinkAddressTest.java
index 17423be..bccf556 100644
--- a/core/tests/coretests/src/android/net/LinkAddressTest.java
+++ b/core/tests/coretests/src/android/net/LinkAddressTest.java
@@ -32,13 +32,13 @@
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
-import static libcore.io.OsConstants.IFA_F_DEPRECATED;
-import static libcore.io.OsConstants.IFA_F_PERMANENT;
-import static libcore.io.OsConstants.IFA_F_TENTATIVE;
-import static libcore.io.OsConstants.RT_SCOPE_HOST;
-import static libcore.io.OsConstants.RT_SCOPE_LINK;
-import static libcore.io.OsConstants.RT_SCOPE_SITE;
-import static libcore.io.OsConstants.RT_SCOPE_UNIVERSE;
+import static android.system.OsConstants.IFA_F_DEPRECATED;
+import static android.system.OsConstants.IFA_F_PERMANENT;
+import static android.system.OsConstants.IFA_F_TENTATIVE;
+import static android.system.OsConstants.RT_SCOPE_HOST;
+import static android.system.OsConstants.RT_SCOPE_LINK;
+import static android.system.OsConstants.RT_SCOPE_SITE;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
/**
* Tests for {@link LinkAddress}.
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index a602e07..553afe0 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -18,14 +18,13 @@
import android.net.LinkProperties;
import android.net.RouteInfo;
+import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
import java.net.InetAddress;
import java.util.ArrayList;
-import libcore.io.OsConstants;
-
public class LinkPropertiesTest extends TestCase {
private static InetAddress ADDRV4 = NetworkUtils.numericToInetAddress("75.208.6.1");
private static InetAddress ADDRV6 = NetworkUtils.numericToInetAddress(
diff --git a/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java b/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java
index 0e3c13a0..3f9e62e 100644
--- a/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java
@@ -46,7 +46,6 @@
import junit.framework.Assert;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
/**
* Tests for {@link FileRotator}.
diff --git a/docs/html/guide/topics/data/backup.jd b/docs/html/guide/topics/data/backup.jd
index 4903852..f09ff9e 100644
--- a/docs/html/guide/topics/data/backup.jd
+++ b/docs/html/guide/topics/data/backup.jd
@@ -899,8 +899,9 @@
{@code tools/} path:
<pre class="no-pretty-print">adb shell bmgr enable true</pre>
</li>
- <li>If using a device, open the system <b>Settings</b>, select <b>Privacy</b>, then enable
-<b>Back up my data</b> and <b>Automatic restore</b>.
+ <li>If using a device, open the system <b>Settings</b>, select
+ <b>Backup & reset</b>, then enable
+ <b>Back up my data</b> and <b>Automatic restore</b>.</li>
</ul>
</li>
<li>Open your application and initialize some data
diff --git a/docs/html/guide/topics/manifest/manifest-element.jd b/docs/html/guide/topics/manifest/manifest-element.jd
index 20dc4ea..7717696 100644
--- a/docs/html/guide/topics/manifest/manifest-element.jd
+++ b/docs/html/guide/topics/manifest/manifest-element.jd
@@ -30,7 +30,7 @@
<br/><code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code>
<br/><code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code>
<br/><code><a href="{@docRoot}guide/topics/manifest/permission-tree-element.html"><permission-tree></a></code>
-<br/><code><a href="{@docRoot}guide/topics/manifest/supports-gl-texture.html"><supports-gl-texture></a></code
+<br/><code><a href="{@docRoot}guide/topics/manifest/supports-gl-texture-element.html"><supports-gl-texture></a></code>
<br/><code><a href="{@docRoot}guide/topics/manifest/supports-screens-element.html"><supports-screens></a></code>
<br/><code><a href="{@docRoot}guide/topics/manifest/uses-configuration-element.html"><uses-configuration></a></code> <!-- ##api level 3## -->
<br/><code><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"><uses-feature></a></code>
@@ -193,4 +193,4 @@
<dd>
<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code></dd>
-</dl>
\ No newline at end of file
+</dl>
diff --git a/docs/html/sdk/installing/installing-adt.jd b/docs/html/sdk/installing/installing-adt.jd
index e4cf1bc..e12dfd8 100644
--- a/docs/html/sdk/installing/installing-adt.jd
+++ b/docs/html/sdk/installing/installing-adt.jd
@@ -1,8 +1,8 @@
page.title=Installing the Eclipse Plugin
-adt.zip.version=22.6.2
-adt.zip.download=ADT-22.6.2.zip
-adt.zip.bytes=14586842
-adt.zip.checksum=f660959fa71262b4285bcb64be284bf5
+adt.zip.version=22.6.3
+adt.zip.download=ADT-22.6.3.zip
+adt.zip.bytes=14590813
+adt.zip.checksum=3982259fd2cc81e53bbbe05dcd6529a7
@jd:body
diff --git a/docs/html/tools/sdk/eclipse-adt.jd b/docs/html/tools/sdk/eclipse-adt.jd
index d106f4a..124e58d 100644
--- a/docs/html/tools/sdk/eclipse-adt.jd
+++ b/docs/html/tools/sdk/eclipse-adt.jd
@@ -53,10 +53,49 @@
<p>For a summary of all known issues in ADT, see <a
href="http://tools.android.com/knownissues">http://tools.android.com/knownissues</a>.</p>
-
<div class="toggle-content opened">
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+ alt=""/>ADT 22.6.3</a> <em>(April 2014)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+<dl>
+ <dt>Dependencies:</dt>
+
+ <dd>
+ <ul>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Indigo (Version 3.7.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r22.6.3</a>.
+ If you haven't already installed SDK Tools r22.6.3 into your SDK, use the
+ Android SDK Manager to do so.</li>
+ </ul>
+ </dd>
+
+ <dt>General Notes:</dt>
+ <dd>
+ <ul>
+ <li>Fixed a problem where the AVD manager allowed creating Android Wear virtual devices
+ with a target API Level lower than 19.</li>
+ <li>Fixed the description of Android Wear system images in the SDK Manager.</li>
+ </ul>
+ </dd>
+
+ <dt>Known Issues:</dt>
+ <dd>
+ <p>When you create an Android Wear virtual device in the AVD manager, a target API Level
+ lower than 19 may be selected by default. Make sure you select the target API Level 19
+ when creating Android Wear virtual devices.</p>
+ </dd>
+</dl>
+</div>
+</div>
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
alt=""/>ADT 22.6.2</a> <em>(March 2014)</em>
</p>
diff --git a/docs/html/tools/sdk/ndk/index.jd b/docs/html/tools/sdk/ndk/index.jd
index a22dc90..0ac1881 100644
--- a/docs/html/tools/sdk/ndk/index.jd
+++ b/docs/html/tools/sdk/ndk/index.jd
@@ -245,8 +245,8 @@
but it always increases your app complexity. In general, you should only use the NDK
if it is essential to your app—never because you simply prefer to program in C/C++.</p>
- <p>Typical good candidates for the NDK are self-contained, CPU-intensive operations that don't
- allocate much memory, such as signal processing, physics simulation, and so on. When examining
+ <p>Typical good candidates for the NDK are CPU-intensive workloads such as game engines,
+ signal processing, physics simulation, and so on. When examining
whether or not you should develop in native code, think about your requirements and see if the
Android framework APIs provide the functionality that you need.</p>
diff --git a/docs/html/tools/sdk/tools-notes.jd b/docs/html/tools/sdk/tools-notes.jd
index 14b5505..9b06a9d 100644
--- a/docs/html/tools/sdk/tools-notes.jd
+++ b/docs/html/tools/sdk/tools-notes.jd
@@ -28,6 +28,46 @@
<div class="toggle-content opened">
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+ alt=""/>SDK Tools, Revision 22.6.3</a> <em>(April 2014)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+
+ <dl>
+ <dt>Dependencies:</dt>
+
+ <dd>
+ <ul>
+ <li>Android SDK Platform-tools revision 18 or later.</li>
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
+ designed for use with ADT 22.6.3 and later. If you haven't already, update your
+ <a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 22.6.3.</li>
+ <li>If you are developing outside Eclipse, you must have
+ <a href="http://ant.apache.org/">Apache Ant</a> 1.8 or later.</li>
+ </ul>
+ </dd>
+
+ <dt>General Notes:</dt>
+ <dd>
+ <ul>
+ <li>Fixed a problem where the AVD manager allowed creating Android Wear virtual devices
+ with a target API Level lower than 19.</li>
+ <li>Fixed the description of Android Wear system images in the SDK Manager.</li>
+ </ul>
+ </dd>
+
+ <dt>Known Issues:</dt>
+ <dd>
+ <p>When you create an Android Wear virtual device in the AVD manager, a target API Level
+ lower than 19 may be selected by default. Make sure you select the target API Level 19
+ when creating Android Wear virtual devices.</p>
+ </dd>
+ </div>
+</div>
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
alt=""/>SDK Tools, Revision 22.6.2</a> <em>(March 2014)</em>
</p>
diff --git a/drm/java/android/drm/DrmOutputStream.java b/drm/java/android/drm/DrmOutputStream.java
index 22e7ac2..ba1c56f 100644
--- a/drm/java/android/drm/DrmOutputStream.java
+++ b/drm/java/android/drm/DrmOutputStream.java
@@ -18,14 +18,14 @@
import static android.drm.DrmConvertedStatus.STATUS_OK;
import static android.drm.DrmManagerClient.INVALID_SESSION;
-import static libcore.io.OsConstants.SEEK_SET;
+import static android.system.OsConstants.SEEK_SET;
import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
-import libcore.io.ErrnoException;
import libcore.io.IoBridge;
-import libcore.io.Libcore;
import libcore.io.Streams;
import java.io.FileDescriptor;
@@ -69,7 +69,7 @@
final DrmConvertedStatus status = mClient.closeConvertSession(mSessionId);
if (status.statusCode == STATUS_OK) {
try {
- Libcore.os.lseek(mFd, status.offset, SEEK_SET);
+ Os.lseek(mFd, status.offset, SEEK_SET);
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index f80ce28..ae3eae1 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1075,11 +1075,20 @@
* @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 @@
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 4ea7ec7..b5c0801 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -16,18 +16,21 @@
package android.graphics;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
import android.view.View;
/**
- * Defines an area of content.
+ * Defines a simple shape, used for bounding graphical regions.
*
- * Can be used with a View or Drawable to drive the shape of shadows cast by a
- * View, and allowing Views to clip inner content.
+ * Can be used with a View, or computed by a Drawable, to drive the shape of shadows cast by a
+ * View.
*
* @see View#setOutline(Outline)
- * @see View#setClipToOutline(boolean)
+ * @see Drawable#getOutline(Outline)
*/
-public class Outline {
+public final class Outline {
/** @hide */
public Rect mRect;
@@ -44,46 +47,74 @@
public Outline() {}
/**
+ * Constructs an Outline with a copy of the data in src.
+ */
+ public Outline(@Nullable Outline src) {
+ set(src);
+ }
+
+ /** @hide */
+ public void markInvalid() {
+ mRadius = 0;
+ mRect = null;
+ mPath = null;
+ }
+
+ /**
* Returns whether the Outline is valid for use with a View.
* <p>
* Outlines are invalid when constructed until a setter method is called.
*/
- public final boolean isValid() {
+ public boolean isValid() {
return mRect != null || mPath != null;
}
/**
- * @hide
- */
- public final boolean canClip() {
- return mPath == null;
- }
-
- /**
* Replace the contents of this Outline with the contents of src.
+ *
+ * @param src Source outline to copy from.
*/
- public void set(Outline src) {
- if (src.mPath != null) {
- if (mPath == null) {
- mPath = new Path();
- }
- mPath.set(src.mPath);
+ public void set(@Nullable Outline src) {
+ if (src == null) {
+ mRadius = 0;
mRect = null;
- }
- if (src.mRect != null) {
- if (mRect == null) {
- mRect = new Rect();
+ mPath = null;
+ } else {
+ if (src.mPath != null) {
+ if (mPath == null) {
+ mPath = new Path();
+ }
+ mPath.set(src.mPath);
+ mRect = null;
}
- mRect.set(src.mRect);
+ if (src.mRect != null) {
+ if (mRect == null) {
+ mRect = new Rect();
+ }
+ mRect.set(src.mRect);
+ }
+ mRadius = src.mRadius;
}
- mRadius = src.mRadius;
}
/**
* 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(@NonNull 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.
+ *
+ * Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)}
*/
public void setRoundRect(int left, int top, int right, int bottom, float radius) {
if (mRect == null) mRect = new Rect();
@@ -93,11 +124,33 @@
}
/**
- * Sets the Constructs an Outline from a {@link android.graphics.Path#isConvex() convex path}.
- *
- * @hide
+ * Convenience for {@link #setRoundRect(int, int, int, int, float)}
*/
- public void setConvexPath(Path convexPath) {
+ public void setRoundRect(@NonNull Rect rect, float radius) {
+ setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
+ }
+
+ /**
+ * Sets the outline to the oval defined by input rect.
+ */
+ public void setOval(int left, int top, int right, int bottom) {
+ mRect = null;
+ if (mPath == null) mPath = new Path();
+ mPath.reset();
+ mPath.addOval(left, top, right, bottom, Path.Direction.CW);
+ }
+
+ /**
+ * Convenience for {@link #setOval(int, int, int, int)}
+ */
+ public void setOval(@NonNull Rect rect) {
+ setOval(rect.left, rect.top, rect.right, rect.bottom);
+ }
+
+ /**
+ * Sets the Constructs an Outline from a {@link android.graphics.Path#isConvex() convex path}.
+ */
+ public void setConvexPath(@NonNull Path convexPath) {
if (!convexPath.isConvex()) {
throw new IllegalArgumentException("path must be convex");
}
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c07a6da..c600f47 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -499,11 +499,7 @@
* @param dir The direction to wind the rectangle's contour
*/
public void addRect(RectF rect, Direction dir) {
- if (rect == null) {
- throw new NullPointerException("need rect parameter");
- }
- detectSimplePath(rect.left, rect.top, rect.right, rect.bottom, dir);
- native_addRect(mNativePath, rect, dir.nativeInt);
+ addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
}
/**
@@ -527,11 +523,17 @@
* @param dir The direction to wind the oval's contour
*/
public void addOval(RectF oval, Direction dir) {
- if (oval == null) {
- throw new NullPointerException("need oval parameter");
- }
+ addOval(oval.left, oval.top, oval.right, oval.bottom, dir);
+ }
+
+ /**
+ * Add a closed oval contour to the path
+ *
+ * @param dir The direction to wind the oval's contour
+ */
+ public void addOval(float left, float top, float right, float bottom, Direction dir) {
isSimplePath = false;
- native_addOval(mNativePath, oval, dir.nativeInt);
+ native_addOval(mNativePath, left, top, right, bottom, dir.nativeInt);
}
/**
@@ -756,10 +758,10 @@
private static native void native_arcTo(long nPath, RectF oval,
float startAngle, float sweepAngle, boolean forceMoveTo);
private static native void native_close(long nPath);
- private static native void native_addRect(long nPath, RectF rect, int dir);
private static native void native_addRect(long nPath, float left, float top,
float right, float bottom, int dir);
- private static native void native_addOval(long nPath, RectF oval, int dir);
+ private static native void native_addOval(long nPath, float left, float top,
+ float right, float bottom, int dir);
private static native void native_addCircle(long nPath, float x, float y, float radius, int dir);
private static native void native_addArc(long nPath, RectF oval,
float startAngle, float sweepAngle);
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 74d1219..b9d5e19 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -864,22 +864,21 @@
}
/**
- * Returns the outline for this drawable if defined, null if not.
+ * Called to get the drawable to populate the Outline.
* <p>
- * This method will be called by a View on its background Drawable after
- * bounds change, if the View's Outline isn't set explicitly. This allows
- * the background Drawable to provide the shape of the shadow casting
- * portion of the View. It can also serve to clip the area of the View if
- * if {@link View#setClipToOutline(boolean)} is set on the View.
- * <p>
- * The Outline queried by the View will not be modified, and is treated as
- * a static shape that only needs to be requeried when the drawable's bounds
- * change.
+ * This method will be called by a View on its background Drawable after bounds change, or its
+ * Drawable is invalidated, if the View's Outline isn't set explicitly. This allows the
+ * background Drawable to define the shape of the shadow cast by the View.
*
- * @see View#setOutline(android.view.Outline)
- * @see View#setClipToOutline(boolean)
+ * The default behavior defines the outline to be the bounding rectangle. Subclasses that wish
+ * to convey a different shape must override this method.
+ *
+ * @see View#setOutline(android.graphics.Outline)
*/
- public Outline getOutline() { return null; }
+ public boolean getOutline(Outline outline) {
+ outline.setRect(getBounds());
+ return true;
+ }
/**
* Make this drawable mutable. This operation cannot be reversed. A mutable
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 1b5cefe..dc06350 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -139,8 +139,7 @@
private final Path mPath = new Path();
private final RectF mRect = new RectF();
- private Outline mOutline;
-
+
private Paint mLayerPaint; // internal, used if we use saveLayer()
private boolean mRectIsDirty; // internal state
private boolean mMutated;
@@ -573,15 +572,11 @@
mStrokePaint.setColorFilter(mColorFilter);
}
}
-
+
switch (st.mShape) {
case RECTANGLE:
if (st.mRadiusArray != null) {
- if (mPathIsDirty || mRectIsDirty) {
- mPath.reset();
- mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
- mPathIsDirty = mRectIsDirty = false;
- }
+ buildPathIfDirty();
canvas.drawPath(mPath, mFillPaint);
if (haveStroke) {
canvas.drawPath(mPath, mStrokePaint);
@@ -638,7 +633,16 @@
}
}
}
-
+
+ private void buildPathIfDirty() {
+ final GradientState st = mGradientState;
+ if (mPathIsDirty || mRectIsDirty) {
+ mPath.reset();
+ mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
+ mPathIsDirty = mRectIsDirty = false;
+ }
+ }
+
private Path buildRing(GradientState st) {
if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
mPathIsDirty = false;
@@ -862,7 +866,7 @@
float x0, x1, y0, y1;
if (st.mGradient == LINEAR_GRADIENT) {
- final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
+ final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
switch (st.mOrientation) {
case TOP_BOTTOM:
x0 = r.left; y0 = r.top;
@@ -1428,42 +1432,40 @@
}
@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,
- bounds.right, bounds.bottom, rad);
- return mOutline;
- case LINE: {
+ outline.setRoundRect(bounds, rad);
+ return true;
+ case OVAL:
+ outline.setOval(bounds);
+ 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/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index 96309f9..61b1b85 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -21,6 +21,7 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
@@ -496,6 +497,16 @@
}
@Override
+ public boolean getOutline(Outline outline) {
+ if (mShapeState.mShape == null) {
+ // don't publish outline without a shape
+ return false;
+ }
+
+ return mShapeState.mShape.getOutline(outline);
+ }
+
+ @Override
public ConstantState getConstantState() {
mShapeState.mChangingConfigurations = getChangingConfigurations();
return mShapeState;
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 3b9d513..0e8831f 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -48,6 +48,7 @@
public class TouchFeedbackDrawable extends LayerDrawable {
private static final String LOG_TAG = TouchFeedbackDrawable.class.getSimpleName();
private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
+ private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
/** The maximum number of ripples supported. */
private static final int MAX_RIPPLES = 10;
@@ -105,6 +106,26 @@
protected boolean onStateChange(int[] stateSet) {
super.onStateChange(stateSet);
+ // TODO: Implicitly tie states to ripple IDs. For now, just clear
+ // focused and pressed if they aren't in the state set.
+ boolean hasFocused = false;
+ boolean hasPressed = false;
+ for (int i = 0; i < stateSet.length; i++) {
+ if (stateSet[i] == R.attr.state_pressed) {
+ hasPressed = true;
+ } else if (stateSet[i] == R.attr.state_focused) {
+ hasFocused = true;
+ }
+ }
+
+ if (!hasPressed) {
+ removeHotspot(R.attr.state_pressed);
+ }
+
+ if (!hasFocused) {
+ removeHotspot(R.attr.state_focused);
+ }
+
if (mRipplePaint != null && mState.mTint != null) {
final ColorStateList stateList = mState.mTint;
final int newColor = stateList.getColorForState(stateSet, 0);
@@ -122,7 +143,7 @@
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
-
+
if (!mOverrideBounds) {
mHotspotBounds.set(bounds);
}
@@ -217,6 +238,27 @@
super.inflate(r, parser, attrs, theme);
setTargetDensity(r.getDisplayMetrics());
+
+ // Find the mask
+ final int N = getNumberOfLayers();
+ for (int i = 0; i < N; i++) {
+ if (mLayerState.mChildren[i].mId == R.id.mask) {
+ mState.mMask = mLayerState.mChildren[i].mDrawable;
+ }
+ }
+ }
+
+ @Override
+ public boolean setDrawableByLayerId(int id, Drawable drawable) {
+ if (super.setDrawableByLayerId(id, drawable)) {
+ if (id == R.id.mask) {
+ mState.mMask = drawable;
+ }
+
+ return true;
+ }
+
+ return false;
}
/**
@@ -310,7 +352,7 @@
mTouchedRipples = new SparseArray<Ripple>();
mActiveRipples = new Ripple[MAX_RIPPLES];
}
-
+
if (mActiveRipplesCount >= MAX_RIPPLES) {
Log.e(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
return;
@@ -415,99 +457,144 @@
@Override
public void draw(Canvas canvas) {
- final boolean projected = getNumberOfLayers() == 0;
- final Ripple[] activeRipples = mActiveRipples;
- final int ripplesCount = mActiveRipplesCount;
+ final int N = mLayerState.mNum;
final Rect bounds = getBounds();
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final boolean maskOnly = mState.mMask != null && N == 1;
+
+ int restoreToCount = drawRippleLayer(canvas, bounds, maskOnly);
+
+ if (restoreToCount >= 0) {
+ // We have a ripple layer that contains ripples. If we also have an
+ // explicit mask drawable, apply it now using DST_IN blending.
+ if (mState.mMask != null) {
+ canvas.saveLayer(bounds.left, bounds.top, bounds.right,
+ bounds.bottom, getMaskingPaint(DST_IN));
+ mState.mMask.draw(canvas);
+ canvas.restoreToCount(restoreToCount);
+ restoreToCount = -1;
+ }
+
+ // If there's more content, we need an extra masking layer to merge
+ // the ripples over the content.
+ if (!maskOnly) {
+ final PorterDuffXfermode xfermode = mState.getTintXfermodeInverse();
+ final int count = canvas.saveLayer(bounds.left, bounds.top,
+ bounds.right, bounds.bottom, getMaskingPaint(xfermode));
+ if (restoreToCount < 0) {
+ restoreToCount = count;
+ }
+ }
+ }
+
+ // Draw everything except the mask.
+ for (int i = 0; i < N; i++) {
+ if (array[i].mId != R.id.mask) {
+ array[i].mDrawable.draw(canvas);
+ }
+ }
+
+ // Composite the layers if needed.
+ if (restoreToCount >= 0) {
+ canvas.restoreToCount(restoreToCount);
+ }
+ }
+
+ private int drawRippleLayer(Canvas canvas, Rect bounds, boolean maskOnly) {
+ final int ripplesCount = mActiveRipplesCount;
+ if (ripplesCount == 0) {
+ return -1;
+ }
+
+ final Ripple[] activeRipples = mActiveRipples;
+ final boolean projected = isProjected();
+ final Rect layerBounds = projected ? getDirtyBounds() : bounds;
+
+ // Separate the ripple color and alpha channel. The alpha will be
+ // applied when we merge the ripples down to the canvas.
+ final int rippleColor;
+ if (mState.mTint != null) {
+ rippleColor = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
+ } else {
+ rippleColor = Color.TRANSPARENT;
+ }
+ final int rippleAlpha = Color.alpha(rippleColor);
+
+ if (mRipplePaint == null) {
+ mRipplePaint = new Paint();
+ mRipplePaint.setAntiAlias(true);
+ }
+ final Paint ripplePaint = mRipplePaint;
+ ripplePaint.setColor(rippleColor);
+
+ boolean drewRipples = false;
+ int restoreToCount = -1;
+ int activeRipplesCount = 0;
// Draw ripples.
- boolean drewRipples = false;
- int rippleRestoreCount = -1;
- int activeRipplesCount = 0;
for (int i = 0; i < ripplesCount; i++) {
final Ripple ripple = activeRipples[i];
final RippleAnimator animator = ripple.animate();
animator.update();
+
+ // Mark and skip inactive ripples.
if (!animator.isRunning()) {
activeRipples[i] = null;
- } else {
- // If we're masking the ripple layer, make sure we have a layer
- // first. This will merge SRC_OVER (directly) onto the canvas.
- if (!projected && rippleRestoreCount < 0) {
- rippleRestoreCount = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, null);
+ continue;
+ }
+
+ // If we're masking the ripple layer, make sure we have a layer
+ // first. This will merge SRC_OVER (directly) onto the canvas.
+ if (restoreToCount < 0) {
+ // If we're projecting or we only have a mask, we want to treat the
+ // underlying canvas as our content and merge the ripple layer down
+ // using the tint xfermode.
+ final PorterDuffXfermode xfermode;
+ if (projected || maskOnly) {
+ xfermode = mState.getTintXfermode();
+ } else {
+ xfermode = SRC_OVER;
}
- drewRipples |= ripple.draw(canvas, getRipplePaint());
-
- activeRipples[activeRipplesCount] = activeRipples[i];
- activeRipplesCount++;
+ final Paint layerPaint = getMaskingPaint(xfermode);
+ layerPaint.setAlpha(rippleAlpha);
+ restoreToCount = canvas.saveLayer(layerBounds.left, layerBounds.top,
+ layerBounds.right, layerBounds.bottom, layerPaint);
+ layerPaint.setAlpha(255);
}
+
+ drewRipples |= ripple.draw(canvas, ripplePaint);
+
+ activeRipples[activeRipplesCount] = activeRipples[i];
+ activeRipplesCount++;
}
+
mActiveRipplesCount = activeRipplesCount;
- // TODO: Use the masking layer first, if there is one.
-
- // If we have ripples and content, we need a masking layer. This will
- // merge DST_ATOP onto (effectively under) the ripple layer.
- if (drewRipples && !projected && rippleRestoreCount >= 0) {
- final PorterDuffXfermode xfermode = mState.getTintXfermode();
- canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, getMaskingPaint(xfermode));
+ // If we created a layer with no content, merge it immediately.
+ if (restoreToCount >= 0 && !drewRipples) {
+ canvas.restoreToCount(restoreToCount);
+ restoreToCount = -1;
}
- Drawable mask = null;
- final ChildDrawable[] array = mLayerState.mChildren;
- final int N = mLayerState.mNum;
- for (int i = 0; i < N; i++) {
- if (array[i].mId != R.id.mask) {
- array[i].mDrawable.draw(canvas);
- } else {
- mask = array[i].mDrawable;
- }
- }
-
- // If we have ripples, mask them.
- if (mask != null && drewRipples) {
- // TODO: This will also mask the lower layer, which is bad.
- canvas.saveLayer(bounds.left, bounds.top, bounds.right,
- bounds.bottom, getMaskingPaint(DST_IN));
- mask.draw(canvas);
- }
-
- // Composite the layers if needed.
- if (rippleRestoreCount >= 0) {
- canvas.restoreToCount(rippleRestoreCount);
- }
+ return restoreToCount;
}
- private Paint getRipplePaint() {
- if (mRipplePaint == null) {
- mRipplePaint = new Paint();
- mRipplePaint.setAntiAlias(true);
-
- if (mState.mTint != null) {
- final int color = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
- mRipplePaint.setColor(color);
- }
- }
- return mRipplePaint;
- }
-
- private Paint getMaskingPaint(PorterDuffXfermode mode) {
+ private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
if (mMaskingPaint == null) {
mMaskingPaint = new Paint();
}
- mMaskingPaint.setXfermode(mode);
+ mMaskingPaint.setXfermode(xfermode);
return mMaskingPaint;
}
@Override
public Rect getDirtyBounds() {
- final Rect dirtyBounds = mDirtyBounds;
final Rect drawingBounds = mDrawingBounds;
+ final Rect dirtyBounds = mDirtyBounds;
dirtyBounds.set(drawingBounds);
drawingBounds.setEmpty();
+
final Rect rippleBounds = mTempRect;
final Ripple[] activeRipples = mActiveRipples;
final int N = mActiveRipplesCount;
@@ -530,6 +617,8 @@
int[] mTouchThemeAttrs;
ColorStateList mTint;
PorterDuffXfermode mTintXfermode;
+ PorterDuffXfermode mTintXfermodeInverse;
+ Drawable mMask;
boolean mPinned;
public TouchFeedbackState(
@@ -540,19 +629,26 @@
mTouchThemeAttrs = orig.mTouchThemeAttrs;
mTint = orig.mTint;
mTintXfermode = orig.mTintXfermode;
+ mTintXfermodeInverse = orig.mTintXfermodeInverse;
mPinned = orig.mPinned;
+ mMask = orig.mMask;
}
}
-
+
public void setTintMode(Mode mode) {
final Mode invertedMode = TouchFeedbackState.invertPorterDuffMode(mode);
- mTintXfermode = new PorterDuffXfermode(invertedMode);
+ mTintXfermodeInverse = new PorterDuffXfermode(invertedMode);
+ mTintXfermode = new PorterDuffXfermode(mode);
}
-
+
public PorterDuffXfermode getTintXfermode() {
return mTintXfermode;
}
+ public PorterDuffXfermode getTintXfermodeInverse() {
+ return mTintXfermodeInverse;
+ }
+
@Override
public boolean canApplyTheme() {
return mTouchThemeAttrs != null || super.canApplyTheme();
diff --git a/graphics/java/android/graphics/drawable/shapes/OvalShape.java b/graphics/java/android/graphics/drawable/shapes/OvalShape.java
index c914999..198dcc1 100644
--- a/graphics/java/android/graphics/drawable/shapes/OvalShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/OvalShape.java
@@ -17,7 +17,9 @@
package android.graphics.drawable.shapes;
import android.graphics.Canvas;
+import android.graphics.Outline;
import android.graphics.Paint;
+import android.graphics.RectF;
/**
* Defines an oval shape.
@@ -36,5 +38,13 @@
public void draw(Canvas canvas, Paint paint) {
canvas.drawOval(rect(), paint);
}
+
+ @Override
+ public boolean getOutline(Outline outline) {
+ final RectF rect = rect();
+ outline.setOval((int) Math.ceil(rect.left), (int) Math.ceil(rect.top),
+ (int) Math.floor(rect.right), (int) Math.floor(rect.bottom));
+ return true;
+ }
}
diff --git a/graphics/java/android/graphics/drawable/shapes/RectShape.java b/graphics/java/android/graphics/drawable/shapes/RectShape.java
index a3d2654..2a0256c 100644
--- a/graphics/java/android/graphics/drawable/shapes/RectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RectShape.java
@@ -17,6 +17,7 @@
package android.graphics.drawable.shapes;
import android.graphics.Canvas;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.RectF;
@@ -40,10 +41,18 @@
}
@Override
+ public boolean getOutline(Outline outline) {
+ final RectF rect = rect();
+ outline.setRect((int) Math.ceil(rect.left), (int) Math.ceil(rect.top),
+ (int) Math.floor(rect.right), (int) Math.floor(rect.bottom));
+ return true;
+ }
+
+ @Override
protected void onResize(float width, float height) {
mRect.set(0, 0, width, height);
}
-
+
/**
* Returns the RectF that defines this rectangle's bounds.
*/
diff --git a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
index b469d2a..a6bb1bb 100644
--- a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
@@ -17,6 +17,7 @@
package android.graphics.drawable.shapes;
import android.graphics.Canvas;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
@@ -77,11 +78,34 @@
public void draw(Canvas canvas, Paint paint) {
canvas.drawPath(mPath, paint);
}
-
+
+ @Override
+ public boolean getOutline(Outline outline) {
+ if (mInnerRect != null) return false; // have a hole, can't produce valid outline
+
+ float radius = 0;
+ if (mOuterRadii != null) {
+ radius = mOuterRadii[0];
+ for (int i = 1; i < 8; i++) {
+ if (mOuterRadii[i] != radius) {
+ // can't call simple constructors, use path
+ outline.setConvexPath(mPath);
+ return true;
+ }
+ }
+ }
+
+ final RectF rect = rect();
+ outline.setRoundRect((int) Math.ceil(rect.left), (int) Math.ceil(rect.top),
+ (int) Math.floor(rect.right), (int) Math.floor(rect.bottom),
+ radius);
+ return true;
+ }
+
@Override
protected void onResize(float w, float h) {
super.onResize(w, h);
-
+
RectF r = rect();
mPath.reset();
diff --git a/graphics/java/android/graphics/drawable/shapes/Shape.java b/graphics/java/android/graphics/drawable/shapes/Shape.java
index 4e192f95..1a20e8b 100644
--- a/graphics/java/android/graphics/drawable/shapes/Shape.java
+++ b/graphics/java/android/graphics/drawable/shapes/Shape.java
@@ -17,6 +17,7 @@
package android.graphics.drawable.shapes;
import android.graphics.Canvas;
+import android.graphics.Outline;
import android.graphics.Paint;
/**
@@ -43,7 +44,6 @@
return mHeight;
}
-
/**
* Draw this shape into the provided Canvas, with the provided Paint.
* Before calling this, you must call {@link #resize(float,float)}.
@@ -52,7 +52,6 @@
* @param paint the Paint object that defines this shape's characteristics
*/
public abstract void draw(Canvas canvas, Paint paint);
-
/**
* Resizes the dimensions of this shape.
@@ -93,8 +92,20 @@
*/
protected void onResize(float width, float height) {}
+ /**
+ * Compute the Outline of the shape.
+ *
+ * The default implementation does not supply an outline.
+ *
+ * @return True if a valid outline has been computed, false otherwise.
+ */
+ public boolean getOutline(Outline outline) {
+ return false;
+ }
+
@Override
public Shape clone() throws CloneNotSupportedException {
return (Shape) super.clone();
}
+
}
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index 904ec8c..c1af5f5 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -147,6 +147,8 @@
/**
* Generate an array of rays' direction vectors.
+ * To make sure the vertices generated are clockwise, the directions are from PI
+ * to -PI.
*
* @param rays The number of rays shooting out from the centroid.
* @param vertices Vertices of the polygon.
@@ -160,8 +162,8 @@
if (vertexCount * 2 > rays) {
float deltaAngle = 2 * M_PI / rays;
for (int i = 0; i < rays; i++) {
- dir[i].x = sinf(deltaAngle * i);
- dir[i].y = cosf(deltaAngle * i);
+ dir[i].x = cosf(M_PI - deltaAngle * i);
+ dir[i].y = sinf(M_PI - deltaAngle * i);
}
return;
}
@@ -178,50 +180,52 @@
// Since the incoming polygon is clockwise, we can find the dip to identify
// the minimal theta.
float polyThetas[vertexCount];
- int minimalPolyThetaIndex = 0;
+ int maxPolyThetaIndex = 0;
for (int i = 0; i < vertexCount; i++) {
polyThetas[i] = atan2(vertices[i].y - centroid3d.y,
vertices[i].x - centroid3d.x);
- if (i > 0 && polyThetas[i] < polyThetas[i - 1]) {
- minimalPolyThetaIndex = i;
+ if (i > 0 && polyThetas[i] > polyThetas[i - 1]) {
+ maxPolyThetaIndex = i;
}
}
- int polyThetaIndex = minimalPolyThetaIndex;
- float polyTheta = polyThetas[minimalPolyThetaIndex];
+ // Both poly's thetas and uniform thetas are in decrease order(clockwise)
+ // from PI to -PI.
+ int polyThetaIndex = maxPolyThetaIndex;
+ float polyTheta = polyThetas[maxPolyThetaIndex];
int uniformThetaIndex = 0;
- float uniformTheta = - M_PI;
+ float uniformTheta = M_PI;
for (int i = 0; i < rays; i++) {
// Compare both thetas and pick the smaller one and move on.
bool hasThetaCollision = abs(polyTheta - uniformTheta) < MINIMAL_DELTA_THETA;
- if (polyTheta < uniformTheta || hasThetaCollision) {
+ if (polyTheta > uniformTheta || hasThetaCollision) {
if (hasThetaCollision) {
// Shift the uniformTheta to middle way between current polyTheta
// and next uniform theta. The next uniform theta can wrap around
// to exactly PI safely here.
// Note that neither polyTheta nor uniformTheta can be FLT_MAX
// due to the hasThetaCollision is true.
- uniformTheta = (polyTheta + deltaAngle * (uniformThetaIndex + 1) - M_PI) / 2;
+ uniformTheta = (polyTheta + M_PI - deltaAngle * (uniformThetaIndex + 1)) / 2;
#if DEBUG_SHADOW
ALOGD("Shifted uniformTheta to %f", uniformTheta);
#endif
}
rayThetas[i] = polyTheta;
polyThetaIndex = (polyThetaIndex + 1) % vertexCount;
- if (polyThetaIndex != minimalPolyThetaIndex) {
+ if (polyThetaIndex != maxPolyThetaIndex) {
polyTheta = polyThetas[polyThetaIndex];
} else {
// out of poly points.
- polyTheta = FLT_MAX;
+ polyTheta = - FLT_MAX;
}
} else {
rayThetas[i] = uniformTheta;
uniformThetaIndex++;
if (uniformThetaIndex < uniformRayCount) {
- uniformTheta = deltaAngle * uniformThetaIndex - M_PI;
+ uniformTheta = M_PI - deltaAngle * uniformThetaIndex;
} else {
// out of uniform points.
- uniformTheta = FLT_MAX;
+ uniformTheta = - FLT_MAX;
}
}
}
@@ -232,8 +236,8 @@
#endif
// TODO: Fix the intersection precision problem and remvoe the delta added
// here.
- dir[i].x = sinf(rayThetas[i] + MINIMAL_DELTA_THETA);
- dir[i].y = cosf(rayThetas[i] + MINIMAL_DELTA_THETA);
+ dir[i].x = cosf(rayThetas[i] + MINIMAL_DELTA_THETA);
+ dir[i].y = sinf(rayThetas[i] + MINIMAL_DELTA_THETA);
}
}
@@ -308,13 +312,12 @@
Vector2 p1 = dir[preIndex] * rayDist[preIndex];
Vector2 p2 = dir[postIndex] * rayDist[postIndex];
- // Now the V (deltaX, deltaY) is the vector going CW around the poly.
+ // Now the rays are going CW around the poly.
Vector2 delta = p2 - p1;
if (delta.length() != 0) {
delta.normalize();
- // Calculate the normal , which is CCW 90 rotate to the V.
- // 90 degrees CCW about z-axis: (x, y, z) -> (-y, x, z)
- normal.x = -delta.y;
+ // Calculate the normal , which is CCW 90 rotate to the delta.
+ normal.x = - delta.y;
normal.y = delta.x;
}
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 52be531..d324439 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -11,6 +11,7 @@
font/CacheTexture.cpp \
font/Font.cpp \
AmbientShadow.cpp \
+ Animator.cpp \
AssetAtlas.cpp \
FontRenderer.cpp \
GammaFontRenderer.cpp \
@@ -25,6 +26,7 @@
FboCache.cpp \
GradientCache.cpp \
Image.cpp \
+ Interpolator.cpp \
Layer.cpp \
LayerCache.cpp \
LayerRenderer.cpp \
@@ -66,6 +68,8 @@
$(LOCAL_PATH)/../../include/utils \
external/skia/src/core
+ include external/stlport/libstlport.mk
+
LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
LOCAL_CFLAGS += -Wno-unused-parameter
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
new file mode 100644
index 0000000..ee16586
--- /dev/null
+++ b/libs/hwui/Animator.cpp
@@ -0,0 +1,240 @@
+/*
+ * 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 "RT-Animator"
+
+#include "Animator.h"
+
+#include <set>
+
+#include "RenderProperties.h"
+
+namespace android {
+namespace uirenderer {
+
+/************************************************************
+ * Private header
+ ************************************************************/
+
+typedef void (RenderProperties::*SetFloatProperty)(float value);
+typedef float (RenderProperties::*GetFloatProperty)() const;
+
+struct PropertyAccessors {
+ GetFloatProperty getter;
+ SetFloatProperty setter;
+};
+
+// Maps RenderProperty enum to accessors
+static const PropertyAccessors PROPERTY_ACCESSOR_LUT[] = {
+ {&RenderProperties::getTranslationX, &RenderProperties::setTranslationX },
+ {&RenderProperties::getTranslationY, &RenderProperties::setTranslationY },
+ {&RenderProperties::getTranslationZ, &RenderProperties::setTranslationZ },
+ {&RenderProperties::getScaleX, &RenderProperties::setScaleX },
+ {&RenderProperties::getScaleY, &RenderProperties::setScaleY },
+ {&RenderProperties::getRotation, &RenderProperties::setRotation },
+ {&RenderProperties::getRotationX, &RenderProperties::setRotationX },
+ {&RenderProperties::getRotationY, &RenderProperties::setRotationY },
+ {&RenderProperties::getX, &RenderProperties::setX },
+ {&RenderProperties::getY, &RenderProperties::setY },
+ {&RenderProperties::getZ, &RenderProperties::setZ },
+ {&RenderProperties::getAlpha, &RenderProperties::setAlpha },
+};
+
+// Helper class to contain generic animator helpers
+class BaseAnimator {
+public:
+ BaseAnimator();
+ virtual ~BaseAnimator();
+
+ void setInterpolator(Interpolator* interpolator);
+ void setDuration(nsecs_t durationInMs);
+
+ bool isFinished() { return mPlayState == FINISHED; }
+
+protected:
+ // This is the main animation entrypoint that subclasses should call
+ // to generate the onAnimation* lifecycle events
+ // Returns true if the animation has finished, false otherwise
+ bool animateFrame(nsecs_t frameTime);
+
+ // Called when PlayState switches from PENDING to RUNNING
+ virtual void onAnimationStarted() {}
+ virtual void onAnimationUpdated(float fraction) = 0;
+ virtual void onAnimationFinished() {}
+
+private:
+ enum PlayState {
+ PENDING,
+ RUNNING,
+ FINISHED,
+ };
+
+ Interpolator* mInterpolator;
+ PlayState mPlayState;
+ long mStartTime;
+ long mDuration;
+};
+
+// Hide the base classes & private bits from the exported RenderPropertyAnimator
+// in this Impl class so that subclasses of RenderPropertyAnimator don't require
+// knowledge of the inner guts but only the public virtual methods.
+// Animates a single property
+class RenderPropertyAnimatorImpl : public BaseAnimator {
+public:
+ RenderPropertyAnimatorImpl(GetFloatProperty getter, SetFloatProperty setter,
+ RenderPropertyAnimator::DeltaValueType deltaType, float delta);
+ ~RenderPropertyAnimatorImpl();
+
+ bool animate(RenderProperties* target, TreeInfo& info);
+
+protected:
+ virtual void onAnimationStarted();
+ virtual void onAnimationUpdated(float fraction);
+
+private:
+ // mTarget is only valid inside animate()
+ RenderProperties* mTarget;
+ GetFloatProperty mGetter;
+ SetFloatProperty mSetter;
+
+ RenderPropertyAnimator::DeltaValueType mDeltaValueType;
+ float mDeltaValue;
+ float mFromValue;
+};
+
+RenderPropertyAnimator::RenderPropertyAnimator(RenderProperty property,
+ DeltaValueType deltaType, float deltaValue) {
+ PropertyAccessors pa = PROPERTY_ACCESSOR_LUT[property];
+ mImpl = new RenderPropertyAnimatorImpl(pa.getter, pa.setter, deltaType, deltaValue);
+}
+
+RenderPropertyAnimator::~RenderPropertyAnimator() {
+ delete mImpl;
+ mImpl = NULL;
+}
+
+void RenderPropertyAnimator::setInterpolator(Interpolator* interpolator) {
+ mImpl->setInterpolator(interpolator);
+}
+
+void RenderPropertyAnimator::setDuration(nsecs_t durationInMs) {
+ mImpl->setDuration(durationInMs);
+}
+
+bool RenderPropertyAnimator::isFinished() {
+ return mImpl->isFinished();
+}
+
+bool RenderPropertyAnimator::animate(RenderProperties* target, TreeInfo& info) {
+ return mImpl->animate(target, info);
+}
+
+
+/************************************************************
+ * Base animator
+ ************************************************************/
+
+BaseAnimator::BaseAnimator()
+ : mInterpolator(0)
+ , mPlayState(PENDING)
+ , mStartTime(0)
+ , mDuration(300) {
+
+}
+
+BaseAnimator::~BaseAnimator() {
+ setInterpolator(NULL);
+}
+
+void BaseAnimator::setInterpolator(Interpolator* interpolator) {
+ delete mInterpolator;
+ mInterpolator = interpolator;
+}
+
+void BaseAnimator::setDuration(nsecs_t duration) {
+ mDuration = duration;
+}
+
+bool BaseAnimator::animateFrame(nsecs_t frameTime) {
+ if (mPlayState == PENDING) {
+ mPlayState = RUNNING;
+ mStartTime = frameTime;
+ // No interpolator was set, use the default
+ if (!mInterpolator) {
+ setInterpolator(Interpolator::createDefaultInterpolator());
+ }
+ onAnimationStarted();
+ }
+
+ float fraction = 1.0f;
+ if (mPlayState == RUNNING) {
+ fraction = mDuration > 0 ? (float)(frameTime - mStartTime) / mDuration : 1.0f;
+ if (fraction >= 1.0f) {
+ fraction = 1.0f;
+ mPlayState = FINISHED;
+ }
+ }
+ fraction = mInterpolator->interpolate(fraction);
+ onAnimationUpdated(fraction);
+
+ if (mPlayState == FINISHED) {
+ onAnimationFinished();
+ return true;
+ }
+ return false;
+}
+
+/************************************************************
+ * RenderPropertyAnimator
+ ************************************************************/
+
+RenderPropertyAnimatorImpl::RenderPropertyAnimatorImpl(
+ GetFloatProperty getter, SetFloatProperty setter,
+ RenderPropertyAnimator::DeltaValueType deltaType, float delta)
+ : mTarget(0)
+ , mGetter(getter)
+ , mSetter(setter)
+ , mDeltaValueType(deltaType)
+ , mDeltaValue(delta)
+ , mFromValue(-1) {
+}
+
+RenderPropertyAnimatorImpl::~RenderPropertyAnimatorImpl() {
+}
+
+bool RenderPropertyAnimatorImpl::animate(RenderProperties* target, TreeInfo& info) {
+ mTarget = target;
+ bool finished = animateFrame(info.frameTimeMs);
+ mTarget = NULL;
+ return finished;
+}
+
+void RenderPropertyAnimatorImpl::onAnimationStarted() {
+ mFromValue = (mTarget->*mGetter)();
+
+ if (mDeltaValueType == RenderPropertyAnimator::ABSOLUTE) {
+ mDeltaValue = (mDeltaValue - mFromValue);
+ mDeltaValueType = RenderPropertyAnimator::DELTA;
+ }
+}
+
+void RenderPropertyAnimatorImpl::onAnimationUpdated(float fraction) {
+ float value = mFromValue + (mDeltaValue * fraction);
+ (mTarget->*mSetter)(value);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
new file mode 100644
index 0000000..1c8361b
--- /dev/null
+++ b/libs/hwui/Animator.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANIMATOR_H
+#define ANIMATOR_H
+
+#include <cutils/compiler.h>
+
+#include "Interpolator.h"
+#include "TreeInfo.h"
+#include "utils/VirtualLightRefBase.h"
+
+namespace android {
+namespace uirenderer {
+
+class RenderProperties;
+class RenderPropertyAnimatorImpl;
+
+class RenderPropertyAnimator : public VirtualLightRefBase {
+public:
+ // Since the UI thread doesn't necessarily know what the current values
+ // actually are and thus can't do the calculations, this is used to inform
+ // the animator how to lazy-resolve the input value
+ enum DeltaValueType {
+ // The delta value represents an absolute value endpoint
+ // mDeltaValue needs to be recalculated to be mDelta = (mDelta - fromValue)
+ // in onAnimationStarted()
+ ABSOLUTE = 0,
+ // The final value represents an offset from the current value
+ // No recalculation is needed
+ DELTA,
+ };
+
+ enum RenderProperty {
+ TRANSLATION_X = 0,
+ TRANSLATION_Y,
+ TRANSLATION_Z,
+ SCALE_X,
+ SCALE_Y,
+ ROTATION,
+ ROTATION_X,
+ ROTATION_Y,
+ X,
+ Y,
+ Z,
+ ALPHA,
+ };
+
+ ANDROID_API void setInterpolator(Interpolator* interpolator);
+ ANDROID_API void setDuration(nsecs_t durationInMs);
+ ANDROID_API bool isFinished();
+
+ bool animate(RenderProperties* target, TreeInfo& info);
+
+protected:
+ ANDROID_API RenderPropertyAnimator(RenderProperty property, DeltaValueType deltaType,
+ float deltaValue);
+ ANDROID_API virtual ~RenderPropertyAnimator();
+
+private:
+ RenderPropertyAnimatorImpl* mImpl;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* ANIMATOR_H */
diff --git a/libs/hwui/Interpolator.cpp b/libs/hwui/Interpolator.cpp
new file mode 100644
index 0000000..004ddf5
--- /dev/null
+++ b/libs/hwui/Interpolator.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Interpolator.h"
+
+#include <math.h>
+
+namespace android {
+namespace uirenderer {
+
+Interpolator* Interpolator::createDefaultInterpolator() {
+ return new AccelerateDecelerateInterpolator();
+}
+
+float AccelerateDecelerateInterpolator::interpolate(float input) {
+ return (float)(cosf((input + 1) * M_PI) / 2.0f) + 0.5f;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/Interpolator.h b/libs/hwui/Interpolator.h
new file mode 100644
index 0000000..2cfb60c
--- /dev/null
+++ b/libs/hwui/Interpolator.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INTERPOLATOR_H
+#define INTERPOLATOR_H
+
+namespace android {
+namespace uirenderer {
+
+class Interpolator {
+public:
+ virtual ~Interpolator() {}
+
+ virtual float interpolate(float input) = 0;
+
+ static Interpolator* createDefaultInterpolator();
+
+protected:
+ Interpolator() {}
+};
+
+class AccelerateDecelerateInterpolator : public Interpolator {
+public:
+ AccelerateDecelerateInterpolator() {}
+ virtual ~AccelerateDecelerateInterpolator() {}
+
+ virtual float interpolate(float input);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* INTERPOLATOR_H */
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 1f5389c..4569152 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -3230,6 +3230,10 @@
const float casterRefinementThresholdSquared = 20.0f; // TODO: experiment with this value
PathTessellator::approximatePathOutlineVertices(*casterPerimeter,
casterRefinementThresholdSquared, casterVertices2d);
+ if (!ShadowTessellator::isClockwisePath(*casterPerimeter)) {
+ ShadowTessellator::reverseVertexArray(casterVertices2d.editArray(),
+ casterVertices2d.size());
+ }
if (casterVertices2d.size() == 0) {
// empty caster polygon computed from path
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 2008f02..dcd6bda 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -18,6 +18,8 @@
#include "RenderNode.h"
+#include <algorithm>
+
#include <SkCanvas.h>
#include <algorithm>
@@ -54,7 +56,8 @@
: mNeedsPropertiesSync(false)
, mNeedsDisplayListDataSync(false)
, mDisplayListData(0)
- , mStagingDisplayListData(0) {
+ , mStagingDisplayListData(0)
+ , mNeedsAnimatorsSync(false) {
}
RenderNode::~RenderNode() {
@@ -97,15 +100,32 @@
}
void RenderNode::prepareTreeImpl(TreeInfo& info) {
- pushStagingChanges(info);
+ if (info.performStagingPush) {
+ pushStagingChanges(info);
+ }
+ if (info.evaluateAnimations) {
+ evaluateAnimations(info);
+ }
prepareSubTree(info, mDisplayListData);
}
+static bool is_finished(const sp<RenderPropertyAnimator>& animator) {
+ return animator->isFinished();
+}
+
void RenderNode::pushStagingChanges(TreeInfo& info) {
if (mNeedsPropertiesSync) {
mNeedsPropertiesSync = false;
mProperties = mStagingProperties;
}
+ if (mNeedsAnimatorsSync) {
+ mAnimators.reserve(mStagingAnimators.size());
+ std::vector< sp<RenderPropertyAnimator> >::iterator it;
+ // hint: this means copy_if_not()
+ it = std::remove_copy_if(mStagingAnimators.begin(), mStagingAnimators.end(),
+ mAnimators.begin(), is_finished);
+ mAnimators.resize(std::distance(mAnimators.begin(), it));
+ }
if (mNeedsDisplayListDataSync) {
mNeedsDisplayListDataSync = false;
// Do a push pass on the old tree to handle freeing DisplayListData
@@ -119,6 +139,34 @@
}
}
+class AnimateFunctor {
+public:
+ AnimateFunctor(RenderProperties* target, TreeInfo& info)
+ : mTarget(target), mInfo(info) {}
+
+ bool operator() (sp<RenderPropertyAnimator>& animator) {
+ bool finished = animator->animate(mTarget, mInfo);
+ if (finished && mInfo.animationListener) {
+ mInfo.animationListener->onAnimationFinished(animator);
+ }
+ return finished;
+ }
+private:
+ RenderProperties* mTarget;
+ TreeInfo& mInfo;
+};
+
+void RenderNode::evaluateAnimations(TreeInfo& info) {
+ if (!mAnimators.size()) return;
+
+ AnimateFunctor functor(&mProperties, info);
+ std::vector< sp<RenderPropertyAnimator> >::iterator newEnd;
+ newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+ mAnimators.erase(newEnd, mAnimators.end());
+ mProperties.updateMatrix();
+ info.hasAnimations |= mAnimators.size();
+}
+
void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
if (subtree) {
TextureCache& cache = Caches::getInstance().textureCache;
@@ -219,11 +267,11 @@
matrix.multiply(anim);
}
- bool applyTranslationZ = true3dTransform && !MathUtils::isZero(properties().getTranslationZ());
+ bool applyTranslationZ = true3dTransform && !MathUtils::isZero(properties().getZ());
if (properties().hasTransformMatrix() || applyTranslationZ) {
if (properties().isTransformTranslateOnly()) {
matrix.translate(properties().getTranslationX(), properties().getTranslationY(),
- true3dTransform ? properties().getTranslationZ() : 0.0f);
+ true3dTransform ? properties().getZ() : 0.0f);
} else {
if (!true3dTransform) {
matrix.multiply(*properties().getTransformMatrix());
@@ -232,7 +280,7 @@
true3dMat.loadTranslate(
properties().getPivotX() + properties().getTranslationX(),
properties().getPivotY() + properties().getTranslationY(),
- properties().getTranslationZ());
+ properties().getZ());
true3dMat.rotate(properties().getRotationX(), 1, 0, 0);
true3dMat.rotate(properties().getRotationY(), 0, 1, 0);
true3dMat.rotate(properties().getRotation(), 0, 0, 1);
@@ -344,7 +392,9 @@
void RenderNode::deferNodeTree(DeferStateStruct& deferStruct) {
DeferOperationHandler handler(deferStruct, 0);
- if (properties().getTranslationZ() > 0.0f) issueDrawShadowOperation(Matrix4::identity(), handler);
+ if (MathUtils::isPositive(properties().getZ())) {
+ issueDrawShadowOperation(Matrix4::identity(), handler);
+ }
issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
}
@@ -380,7 +430,9 @@
void RenderNode::replayNodeTree(ReplayStateStruct& replayStruct) {
ReplayOperationHandler handler(replayStruct, 0);
- if (properties().getTranslationZ() > 0.0f) issueDrawShadowOperation(Matrix4::identity(), handler);
+ if (MathUtils::isPositive(properties().getZ())) {
+ issueDrawShadowOperation(Matrix4::identity(), handler);
+ }
issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
}
@@ -395,7 +447,7 @@
for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
DrawDisplayListOp* childOp = mDisplayListData->children()[i];
RenderNode* child = childOp->mDisplayList;
- float childZ = child->properties().getTranslationZ();
+ float childZ = child->properties().getZ();
if (!MathUtils::isZero(childZ)) {
zTranslatedNodes.add(ZDrawDisplayListOpPair(childZ, childOp));
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index b9edbe5..294f436 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -20,6 +20,9 @@
#define LOG_TAG "OpenGLRenderer"
#endif
+#include <set>
+#include <vector>
+
#include <SkCamera.h>
#include <SkMatrix.h>
@@ -41,6 +44,7 @@
#include "DeferredDisplayList.h"
#include "DisplayList.h"
#include "RenderProperties.h"
+#include "TreeInfo.h"
#include "utils/VirtualLightRefBase.h"
class SkBitmap;
@@ -65,17 +69,6 @@
class RestoreToCountOp;
class DrawDisplayListOp;
-struct TreeInfo {
- TreeInfo()
- : hasFunctors(false)
- , prepareTextures(false)
- {}
-
- bool hasFunctors;
- bool prepareTextures;
- // TODO: Damage calculations? Flag to skip staging pushes for RT animations?
-};
-
/**
* Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties.
*
@@ -91,7 +84,7 @@
class RenderNode : public VirtualLightRefBase {
public:
ANDROID_API RenderNode();
- ANDROID_API ~RenderNode();
+ ANDROID_API virtual ~RenderNode();
// See flags defined in DisplayList.java
enum ReplayFlag {
@@ -152,7 +145,19 @@
return properties().getHeight();
}
- ANDROID_API void prepareTree(TreeInfo& info);
+ ANDROID_API virtual void prepareTree(TreeInfo& info);
+
+ // UI thread only!
+ ANDROID_API void addAnimator(const sp<RenderPropertyAnimator>& animator) {
+ mStagingAnimators.insert(animator);
+ mNeedsAnimatorsSync = true;
+ }
+
+ // UI thread only!
+ ANDROID_API void removeAnimator(const sp<RenderPropertyAnimator>& animator) {
+ mStagingAnimators.erase(animator);
+ mNeedsAnimatorsSync = true;
+ }
private:
typedef key_value_pair_t<float, DrawDisplayListOp*> ZDrawDisplayListOpPair;
@@ -214,6 +219,7 @@
void prepareTreeImpl(TreeInfo& info);
void pushStagingChanges(TreeInfo& info);
+ void evaluateAnimations(TreeInfo& info);
void prepareSubTree(TreeInfo& info, DisplayListData* subtree);
String8 mName;
@@ -226,6 +232,10 @@
DisplayListData* mDisplayListData;
DisplayListData* mStagingDisplayListData;
+ bool mNeedsAnimatorsSync;
+ std::set< sp<RenderPropertyAnimator> > mStagingAnimators;
+ std::vector< sp<RenderPropertyAnimator> > mAnimators;
+
/**
* Draw time state - these properties are only set and used during rendering
*/
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index a922db8..99de1fc 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -37,6 +37,7 @@
, mProjectionReceiver(false)
, mAlpha(1)
, mHasOverlappingRendering(true)
+ , mElevation(0)
, mTranslationX(0), mTranslationY(0), mTranslationZ(0)
, mRotation(0), mRotationX(0), mRotationY(0)
, mScaleX(1), mScaleY(1)
@@ -50,7 +51,8 @@
RenderProperties::ComputedFields::ComputedFields()
: mTransformMatrix(NULL)
- , mClipPath(NULL) {
+ , mClipPath(NULL)
+ , mClipPathOp(SkRegion::kIntersect_Op) {
}
RenderProperties::ComputedFields::~ComputedFields() {
@@ -100,7 +102,7 @@
if (hasTransformMatrix()) {
if (isTransformTranslateOnly()) {
ALOGD("%*sTranslate %.2f, %.2f, %.2f",
- level * 2, "", mPrimitiveFields.mTranslationX, mPrimitiveFields.mTranslationY, mPrimitiveFields.mTranslationZ);
+ level * 2, "", getTranslationX(), getTranslationY(), getZ());
} else {
ALOGD("%*sConcatMatrix %p: " SK_MATRIX_STRING,
level * 2, "", mComputedFields.mTransformMatrix, SK_MATRIX_ARGS(mComputedFields.mTransformMatrix));
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 4270da2..6fc8bce 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -16,7 +16,9 @@
#ifndef RENDERNODEPROPERTIES_H
#define RENDERNODEPROPERTIES_H
+#include <algorithm>
#include <stddef.h>
+#include <vector>
#include <cutils/compiler.h>
#include <androidfw/ResourceTypes.h>
@@ -24,6 +26,7 @@
#include <SkMatrix.h>
#include <SkRegion.h>
+#include "Animator.h"
#include "Rect.h"
#include "RevealClip.h"
#include "Outline.h"
@@ -105,6 +108,17 @@
return mPrimitiveFields.mHasOverlappingRendering;
}
+ void setElevation(float elevation) {
+ if (elevation != mPrimitiveFields.mElevation) {
+ mPrimitiveFields.mElevation = elevation;
+ // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
+ }
+ }
+
+ float getElevation() const {
+ return mPrimitiveFields.mElevation;
+ }
+
void setTranslationX(float translationX) {
if (translationX != mPrimitiveFields.mTranslationX) {
mPrimitiveFields.mTranslationX = translationX;
@@ -130,7 +144,7 @@
void setTranslationZ(float translationZ) {
if (translationZ != mPrimitiveFields.mTranslationZ) {
mPrimitiveFields.mTranslationZ = translationZ;
- mPrimitiveFields.mMatrixOrPivotDirty = true;
+ // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
}
}
@@ -138,6 +152,35 @@
return mPrimitiveFields.mTranslationZ;
}
+ // Animation helper
+ void setX(float value) {
+ setTranslationX(value - getLeft());
+ }
+
+ // Animation helper
+ float getX() const {
+ return getLeft() + getTranslationX();
+ }
+
+ // Animation helper
+ void setY(float value) {
+ setTranslationY(value - getTop());
+ }
+
+ // Animation helper
+ float getY() const {
+ return getTop() + getTranslationY();
+ }
+
+ // Animation helper
+ void setZ(float value) {
+ setTranslationZ(value - getElevation());
+ }
+
+ float getZ() const {
+ return getElevation() + getTranslationZ();
+ }
+
void setRotation(float rotation) {
if (rotation != mPrimitiveFields.mRotation) {
mPrimitiveFields.mRotation = rotation;
@@ -302,7 +345,8 @@
}
void setLeftTopRightBottom(int left, int top, int right, int bottom) {
- if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop || right != mPrimitiveFields.mRight || bottom != mPrimitiveFields.mBottom) {
+ if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop
+ || right != mPrimitiveFields.mRight || bottom != mPrimitiveFields.mBottom) {
mPrimitiveFields.mLeft = left;
mPrimitiveFields.mTop = top;
mPrimitiveFields.mRight = right;
@@ -429,6 +473,7 @@
bool mProjectionReceiver;
float mAlpha;
bool mHasOverlappingRendering;
+ float mElevation;
float mTranslationX, mTranslationY, mTranslationZ;
float mRotation, mRotationX, mRotationY;
float mScaleX, mScaleY;
@@ -440,7 +485,6 @@
bool mCaching;
} mPrimitiveFields;
- // mCameraDistance isn't in mPrimitiveFields as it has a complex setter
SkMatrix* mStaticMatrix;
SkMatrix* mAnimationMatrix;
diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp
index 4d0edfb..ee60a63 100644
--- a/libs/hwui/ShadowTessellator.cpp
+++ b/libs/hwui/ShadowTessellator.cpp
@@ -169,5 +169,67 @@
return centroid;
}
+/**
+ * Test whether the polygon is order in clockwise.
+ *
+ * @param polygon the polygon as a Vector2 array
+ * @param len the number of points of the polygon
+ */
+bool ShadowTessellator::isClockwise(const Vector2* polygon, int len) {
+ double sum = 0;
+ double p1x = polygon[len - 1].x;
+ double p1y = polygon[len - 1].y;
+ for (int i = 0; i < len; i++) {
+
+ double p2x = polygon[i].x;
+ double p2y = polygon[i].y;
+ sum += p1x * p2y - p2x * p1y;
+ p1x = p2x;
+ p1y = p2y;
+ }
+ return sum < 0;
+}
+
+bool ShadowTessellator::isClockwisePath(const SkPath& path) {
+ SkPath::Iter iter(path, false);
+ SkPoint pts[4];
+ SkPath::Verb v;
+
+ Vector<Vector2> arrayForDirection;
+ while (SkPath::kDone_Verb != (v = iter.next(pts))) {
+ switch (v) {
+ case SkPath::kMove_Verb:
+ arrayForDirection.add(Vector2(pts[0].x(), pts[0].y()));
+ break;
+ case SkPath::kLine_Verb:
+ arrayForDirection.add(Vector2(pts[1].x(), pts[1].y()));
+ break;
+ case SkPath::kQuad_Verb:
+ arrayForDirection.add(Vector2(pts[1].x(), pts[1].y()));
+ arrayForDirection.add(Vector2(pts[2].x(), pts[2].y()));
+ break;
+ case SkPath::kCubic_Verb:
+ arrayForDirection.add(Vector2(pts[1].x(), pts[1].y()));
+ arrayForDirection.add(Vector2(pts[2].x(), pts[2].y()));
+ arrayForDirection.add(Vector2(pts[3].x(), pts[3].y()));
+ break;
+ default:
+ break;
+ }
+ }
+
+ return isClockwise(arrayForDirection.array(), arrayForDirection.size());
+}
+
+void ShadowTessellator::reverseVertexArray(Vertex* polygon, int len) {
+ int n = len / 2;
+ for (int i = 0; i < n; i++) {
+ Vertex tmp = polygon[i];
+ int k = len - 1 - i;
+ polygon[i] = polygon[k];
+ polygon[k] = tmp;
+ }
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/ShadowTessellator.h b/libs/hwui/ShadowTessellator.h
index 05370dd..64e69bc 100644
--- a/libs/hwui/ShadowTessellator.h
+++ b/libs/hwui/ShadowTessellator.h
@@ -80,6 +80,27 @@
static void generateShadowIndices(uint16_t* shadowIndices);
static Vector2 centroid2d(const Vector2* poly, int polyLength);
+
+ static bool isClockwise(const Vector2* polygon, int len);
+
+ /**
+ * Determine whether the path is clockwise, using the control points.
+ *
+ * TODO: Given the skia is using inverted Y coordinate, shadow system needs
+ * to convert to the same coordinate to avoid the extra reverse.
+ *
+ * @param path The path to be examined.
+ */
+ static bool isClockwisePath(const SkPath &path);
+
+ /**
+ * Reverse the vertex array.
+ *
+ * @param polygon The vertex array to be reversed.
+ * @param len The length of the vertex array.
+ */
+ static void reverseVertexArray(Vertex* polygon, int len);
+
}; // ShadowTessellator
}; // namespace uirenderer
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index 5fa0ba5..3ebe7b4 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -174,10 +174,10 @@
int SpotShadow::intersection(const Vector2* poly1, int poly1Length,
Vector2* poly2, int poly2Length) {
#if DEBUG_SHADOW
- if (!isClockwise(poly1, poly1Length)) {
+ if (!ShadowTessellator::isClockwise(poly1, poly1Length)) {
ALOGW("Poly1 is not clockwise! Intersection is wrong!");
}
- if (!isClockwise(poly2, poly2Length)) {
+ if (!ShadowTessellator::isClockwise(poly2, poly2Length)) {
ALOGW("Poly2 is not clockwise! Intersection is wrong!");
}
#endif
@@ -407,33 +407,12 @@
if (polygon == 0 || len == 0) {
return;
}
- if (!isClockwise(polygon, len)) {
+ if (!ShadowTessellator::isClockwise(polygon, len)) {
reverse(polygon, len);
}
}
/**
- * Test whether the polygon is order in clockwise.
- *
- * @param polygon the polygon as a Vector2 array
- * @param len the number of points of the polygon
- */
-bool SpotShadow::isClockwise(const Vector2* polygon, int len) {
- double sum = 0;
- double p1x = polygon[len - 1].x;
- double p1y = polygon[len - 1].y;
- for (int i = 0; i < len; i++) {
-
- double p2x = polygon[i].x;
- double p2y = polygon[i].y;
- sum += p1x * p2y - p2x * p1y;
- p1x = p2x;
- p1y = p2y;
- }
- return sum < 0;
-}
-
-/**
* Reverse the polygon
*
* @param polygon the polygon as a Vector2 array
diff --git a/libs/hwui/SpotShadow.h b/libs/hwui/SpotShadow.h
index 599d37e..fb3e6d5 100644
--- a/libs/hwui/SpotShadow.h
+++ b/libs/hwui/SpotShadow.h
@@ -56,7 +56,6 @@
static bool testPointInsidePolygon(const Vector2 testPoint, const Vector2* poly, int len);
static void makeClockwise(Vector2* polygon, int len);
- static bool isClockwise(const Vector2* polygon, int len);
static void reverse(Vector2* polygon, int len);
static inline bool lineIntersection(double x1, double y1, double x2, double y2,
double x3, double y3, double x4, double y4, Vector2& ret);
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
new file mode 100644
index 0000000..8957607
--- /dev/null
+++ b/libs/hwui/TreeInfo.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TREEINFO_H
+#define TREEINFO_H
+
+#include <cutils/compiler.h>
+#include <utils/Timers.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace uirenderer {
+
+class RenderPropertyAnimator;
+
+class AnimationListener {
+public:
+ ANDROID_API virtual void onAnimationFinished(const sp<RenderPropertyAnimator>&) = 0;
+protected:
+ ANDROID_API virtual ~AnimationListener() {}
+};
+
+struct TreeInfo {
+ // The defaults here should be safe for everyone but DrawFrameTask to use as-is.
+ TreeInfo()
+ : hasFunctors(false)
+ , prepareTextures(false)
+ , performStagingPush(true)
+ , frameTimeMs(0)
+ , evaluateAnimations(false)
+ , hasAnimations(false)
+ , animationListener(0)
+ {}
+
+ bool hasFunctors;
+ bool prepareTextures;
+ bool performStagingPush;
+
+ // Animations
+ nsecs_t frameTimeMs;
+ bool evaluateAnimations;
+ // This is only updated if evaluateAnimations is true
+ bool hasAnimations;
+ AnimationListener* animationListener;
+
+ // TODO: Damage calculations
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* TREEINFO_H */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5ce7ba6..63f4b95 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -308,18 +308,20 @@
return value == EGL_BUFFER_PRESERVED;
}
-CanvasContext::CanvasContext(bool translucent)
+CanvasContext::CanvasContext(bool translucent, RenderNode* rootRenderNode)
: mRenderThread(RenderThread::getInstance())
, mEglSurface(EGL_NO_SURFACE)
, mDirtyRegionsEnabled(false)
, mOpaque(!translucent)
, mCanvas(0)
- , mHaveNewSurface(false) {
+ , mHaveNewSurface(false)
+ , mRootRenderNode(rootRenderNode) {
mGlobalContext = GlobalContext::get();
}
CanvasContext::~CanvasContext() {
destroyCanvasAndSurface();
+ mRenderThread.removeFrameCallback(this);
}
void CanvasContext::destroyCanvasAndSurface() {
@@ -403,7 +405,16 @@
}
}
-void CanvasContext::drawDisplayList(RenderNode* displayList, Rect* dirty) {
+void CanvasContext::prepareTree(TreeInfo& info) {
+ mRootRenderNode->prepareTree(info);
+
+ if (info.hasAnimations && !info.hasFunctors) {
+ // TODO: Functors
+ mRenderThread.postFrameCallback(this);
+ }
+}
+
+void CanvasContext::draw(Rect* dirty) {
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
"drawDisplayList called on a context with no canvas or surface!");
@@ -417,7 +428,7 @@
}
status_t status;
- if (dirty) {
+ if (dirty && !dirty->isEmpty()) {
status = mCanvas->prepareDirty(dirty->left, dirty->top,
dirty->right, dirty->bottom, mOpaque);
} else {
@@ -425,7 +436,7 @@
}
Rect outBounds;
- status |= mCanvas->drawDisplayList(displayList, outBounds);
+ status |= mCanvas->drawDisplayList(mRootRenderNode.get(), outBounds);
// TODO: Draw debug info
// TODO: Performance tracking
@@ -437,6 +448,20 @@
}
}
+// Called by choreographer to do an RT-driven animation
+void CanvasContext::doFrame(nsecs_t frameTimeNanos) {
+ ATRACE_CALL();
+
+ TreeInfo info;
+ info.evaluateAnimations = true;
+ info.frameTimeMs = nanoseconds_to_milliseconds(frameTimeNanos);
+ info.performStagingPush = false;
+ info.prepareTextures = false;
+
+ prepareTree(info);
+ draw(NULL);
+}
+
void CanvasContext::invokeFunctor(Functor* functor) {
ATRACE_CALL();
DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index a3fe591..0873ad4 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -25,6 +25,7 @@
#include "../RenderNode.h"
#include "RenderTask.h"
+#include "RenderThread.h"
#define FUNCTOR_PROCESS_DELAY 4
@@ -39,15 +40,13 @@
namespace renderthread {
class GlobalContext;
-class CanvasContext;
-class RenderThread;
// This per-renderer class manages the bridge between the global EGL context
// and the render surface.
-class CanvasContext {
+class CanvasContext : public IFrameCallback {
public:
- CanvasContext(bool translucent);
- ~CanvasContext();
+ CanvasContext(bool translucent, RenderNode* rootRenderNode);
+ virtual ~CanvasContext();
bool initialize(EGLNativeWindowType window);
void updateSurface(EGLNativeWindowType window);
@@ -55,9 +54,13 @@
void setup(int width, int height);
void makeCurrent();
void processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, TreeInfo& info);
- void drawDisplayList(RenderNode* displayList, Rect* dirty);
+ void prepareTree(TreeInfo& info);
+ void draw(Rect* dirty);
void destroyCanvasAndSurface();
+ // IFrameCallback, Chroreographer-driven frame callback entry point
+ virtual void doFrame(nsecs_t frameTimeNanos);
+
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
void invokeFunctor(Functor* functor);
@@ -82,6 +85,8 @@
bool mOpaque;
OpenGLRenderer* mCanvas;
bool mHaveNewSurface;
+
+ const sp<RenderNode> mRootRenderNode;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index f542d43..ff4be71 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -30,7 +30,7 @@
namespace uirenderer {
namespace renderthread {
-DrawFrameTask::DrawFrameTask() : mContext(0), mRenderNode(0) {
+DrawFrameTask::DrawFrameTask() : mContext(0) {
}
DrawFrameTask::~DrawFrameTask() {
@@ -55,25 +55,17 @@
}
}
-void DrawFrameTask::setRenderNode(RenderNode* renderNode) {
- LOG_ALWAYS_FATAL_IF(!mContext, "Lifecycle violation, there's no context to setRenderNode with!");
-
- mRenderNode = renderNode;
-}
-
void DrawFrameTask::setDirty(int left, int top, int right, int bottom) {
mDirty.set(left, top, right, bottom);
}
void DrawFrameTask::drawFrame(RenderThread* renderThread) {
- LOG_ALWAYS_FATAL_IF(!mRenderNode.get(), "Cannot drawFrame with no render node!");
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
postAndWait(renderThread);
// Reset the single-frame data
mDirty.setEmpty();
- mRenderNode = 0;
}
void DrawFrameTask::postAndWait(RenderThread* renderThread) {
@@ -88,8 +80,7 @@
bool canUnblockUiThread = syncFrameState();
// Grab a copy of everything we need
- Rect dirtyCopy(mDirty);
- sp<RenderNode> renderNode = mRenderNode;
+ Rect dirty(mDirty);
CanvasContext* context = mContext;
// From this point on anything in "this" is *UNSAFE TO ACCESS*
@@ -97,15 +88,20 @@
unblockUiThread();
}
- drawRenderNode(context, renderNode.get(), &dirtyCopy);
+ context->draw(&dirty);
if (!canUnblockUiThread) {
unblockUiThread();
}
}
-static void prepareTreeInfo(TreeInfo& info) {
+static void initTreeInfo(TreeInfo& info) {
info.prepareTextures = true;
+ info.performStagingPush = true;
+ info.evaluateAnimations = true;
+ // TODO: Get this from Choreographer
+ nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
+ info.frameTimeMs = nanoseconds_to_milliseconds(frameTimeNs);
}
bool DrawFrameTask::syncFrameState() {
@@ -113,9 +109,9 @@
mContext->makeCurrent();
Caches::getInstance().textureCache.resetMarkInUse();
TreeInfo info;
- prepareTreeInfo(info);
+ initTreeInfo(info);
mContext->processLayerUpdates(&mLayers, info);
- mRenderNode->prepareTree(info);
+ mContext->prepareTree(info);
// If prepareTextures is false, we ran out of texture cache space
return !info.hasFunctors && info.prepareTextures;
}
@@ -125,16 +121,6 @@
mSignal.signal();
}
-void DrawFrameTask::drawRenderNode(CanvasContext* context, RenderNode* renderNode, Rect* dirty) {
- ATRACE_CALL();
-
- if (dirty->bottom == -1 && dirty->left == -1
- && dirty->top == -1 && dirty->right == -1) {
- dirty = 0;
- }
- context->drawDisplayList(renderNode, dirty);
-}
-
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 055d4cf..c280868 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -53,7 +53,6 @@
void addLayer(DeferredLayerUpdater* layer);
void removeLayer(DeferredLayerUpdater* layer);
- void setRenderNode(RenderNode* renderNode);
void setDirty(int left, int top, int right, int bottom);
void drawFrame(RenderThread* renderThread);
@@ -63,7 +62,6 @@
void postAndWait(RenderThread* renderThread);
bool syncFrameState();
void unblockUiThread();
- static void drawRenderNode(CanvasContext* context, RenderNode* renderNode, Rect* dirty);
Mutex mLock;
Condition mSignal;
@@ -73,7 +71,6 @@
/*********************************************
* Single frame data
*********************************************/
- sp<RenderNode> mRenderNode;
Rect mDirty;
/*********************************************
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ce490f1..87886e6 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -51,15 +51,16 @@
MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
ARGS(method) *args = (ARGS(method) *) task->payload()
-CREATE_BRIDGE1(createContext, bool translucent) {
- return new CanvasContext(args->translucent);
+CREATE_BRIDGE2(createContext, bool translucent, RenderNode* rootRenderNode) {
+ return new CanvasContext(args->translucent, args->rootRenderNode);
}
-RenderProxy::RenderProxy(bool translucent)
+RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode)
: mRenderThread(RenderThread::getInstance())
, mContext(0) {
SETUP_TASK(createContext);
args->translucent = translucent;
+ args->rootRenderNode = rootRenderNode;
mContext = (CanvasContext*) postAndWait(task);
mDrawFrameTask.setContext(mContext);
}
@@ -133,9 +134,8 @@
post(task);
}
-void RenderProxy::drawDisplayList(RenderNode* displayList,
+void RenderProxy::syncAndDrawFrame(
int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom) {
- mDrawFrameTask.setRenderNode(displayList);
mDrawFrameTask.setDirty(dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
mDrawFrameTask.drawFrame(&mRenderThread);
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a112493..eab1395 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -56,14 +56,14 @@
*/
class ANDROID_API RenderProxy {
public:
- ANDROID_API RenderProxy(bool translucent);
+ ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode);
ANDROID_API virtual ~RenderProxy();
ANDROID_API bool initialize(const sp<ANativeWindow>& window);
ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
ANDROID_API void pauseSurface(const sp<ANativeWindow>& window);
ANDROID_API void setup(int width, int height);
- ANDROID_API void drawDisplayList(RenderNode* displayList,
+ ANDROID_API void syncAndDrawFrame(
int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
ANDROID_API void destroyCanvasAndSurface();
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 212f475..e95707a 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -18,9 +18,11 @@
#include "RenderThread.h"
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+
#include "CanvasContext.h"
#include "RenderProxy.h"
-#include <utils/Log.h>
namespace android {
using namespace uirenderer::renderthread;
@@ -29,6 +31,14 @@
namespace uirenderer {
namespace renderthread {
+// Number of events to read at a time from the DisplayEventReceiver pipe.
+// The value should be large enough that we can quickly drain the pipe
+// using just a few large reads.
+static const size_t EVENT_BUFFER_SIZE = 100;
+
+// Slight delay to give the UI time to push us a new frame before we replay
+static const int DISPATCH_FRAME_CALLBACKS_DELAY = 0;
+
TaskQueue::TaskQueue() : mHead(0), mTail(0) {}
RenderTask* TaskQueue::next() {
@@ -103,8 +113,25 @@
}
}
+class DispatchFrameCallbacks : public RenderTask {
+private:
+ RenderThread* mRenderThread;
+public:
+ DispatchFrameCallbacks(RenderThread* rt) : mRenderThread(rt) {}
+
+ virtual void run() {
+ mRenderThread->dispatchFrameCallbacks();
+ }
+};
+
RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
- , mNextWakeup(LLONG_MAX) {
+ , mNextWakeup(LLONG_MAX)
+ , mDisplayEventReceiver(0)
+ , mVsyncRequested(false)
+ , mFrameCallbackTaskPending(false)
+ , mFrameCallbackTask(0)
+ , mFrameTime(0) {
+ mFrameCallbackTask = new DispatchFrameCallbacks(this);
mLooper = new Looper(false);
run("RenderThread");
}
@@ -112,10 +139,86 @@
RenderThread::~RenderThread() {
}
+void RenderThread::initializeDisplayEventReceiver() {
+ LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
+ mDisplayEventReceiver = new DisplayEventReceiver();
+ status_t status = mDisplayEventReceiver->initCheck();
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver "
+ "failed with status: %d", status);
+
+ // Register the FD
+ mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
+ Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
+}
+
+int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
+ if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+ ALOGE("Display event receiver pipe was closed or an error occurred. "
+ "events=0x%x", events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & Looper::EVENT_INPUT)) {
+ ALOGW("Received spurious callback for unhandled poll event. "
+ "events=0x%x", events);
+ return 1; // keep the callback
+ }
+
+ reinterpret_cast<RenderThread*>(data)->drainDisplayEventQueue();
+
+ return 1; // keep the callback
+}
+
+static nsecs_t latestVsyncEvent(DisplayEventReceiver* receiver) {
+ DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+ nsecs_t latest = 0;
+ ssize_t n;
+ while ((n = receiver->getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+ for (ssize_t i = 0; i < n; i++) {
+ const DisplayEventReceiver::Event& ev = buf[i];
+ switch (ev.header.type) {
+ case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+ latest = ev.header.timestamp;
+ break;
+ }
+ }
+ }
+ if (n < 0) {
+ ALOGW("Failed to get events from display event receiver, status=%d", status_t(n));
+ }
+ return latest;
+}
+
+void RenderThread::drainDisplayEventQueue() {
+ nsecs_t vsyncEvent = latestVsyncEvent(mDisplayEventReceiver);
+ if (vsyncEvent > 0) {
+ mVsyncRequested = false;
+ mFrameTime = vsyncEvent;
+ if (!mFrameCallbackTaskPending) {
+ mFrameCallbackTaskPending = true;
+ //queueDelayed(mFrameCallbackTask, DISPATCH_FRAME_CALLBACKS_DELAY);
+ queue(mFrameCallbackTask);
+ }
+ }
+}
+
+void RenderThread::dispatchFrameCallbacks() {
+ mFrameCallbackTaskPending = false;
+
+ std::set<IFrameCallback*> callbacks;
+ mFrameCallbacks.swap(callbacks);
+
+ for (std::set<IFrameCallback*>::iterator it = callbacks.begin(); it != callbacks.end(); it++) {
+ (*it)->doFrame(mFrameTime);
+ }
+}
+
bool RenderThread::threadLoop() {
+ initializeDisplayEventReceiver();
+
int timeoutMillis = -1;
for (;;) {
- int result = mLooper->pollAll(timeoutMillis);
+ int result = mLooper->pollOnce(timeoutMillis);
LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
"RenderThread Looper POLL_ERROR!");
@@ -159,6 +262,20 @@
mQueue.remove(task);
}
+void RenderThread::postFrameCallback(IFrameCallback* callback) {
+ mFrameCallbacks.insert(callback);
+ if (!mVsyncRequested) {
+ mVsyncRequested = true;
+ status_t status = mDisplayEventReceiver->requestNextVsync();
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "requestNextVsync failed with status: %d", status);
+ }
+}
+
+void RenderThread::removeFrameCallback(IFrameCallback* callback) {
+ mFrameCallbacks.erase(callback);
+}
+
RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) {
AutoMutex _lock(mLock);
RenderTask* next = mQueue.peek();
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index e444aa0..b93dfd6 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -18,6 +18,10 @@
#define RENDERTHREAD_H_
#include "RenderTask.h"
+
+#include <memory>
+#include <set>
+
#include <cutils/compiler.h>
#include <utils/Looper.h>
#include <utils/Mutex.h>
@@ -25,9 +29,13 @@
#include <utils/Thread.h>
namespace android {
+class DisplayEventReceiver;
+
namespace uirenderer {
namespace renderthread {
+class DispatchFrameCallbacks;
+
class TaskQueue {
public:
TaskQueue();
@@ -42,6 +50,15 @@
RenderTask* mTail;
};
+// Mimics android.view.Choreographer.FrameCallback
+class IFrameCallback {
+public:
+ virtual void doFrame(nsecs_t frameTimeNanos) = 0;
+
+protected:
+ ~IFrameCallback() {}
+};
+
class ANDROID_API RenderThread : public Thread, public Singleton<RenderThread> {
public:
// RenderThread takes complete ownership of tasks that are queued
@@ -50,15 +67,25 @@
void queueDelayed(RenderTask* task, int delayMs);
void remove(RenderTask* task);
+ // Mimics android.view.Choreographer
+ void postFrameCallback(IFrameCallback* callback);
+ void removeFrameCallback(IFrameCallback* callback);
+
protected:
virtual bool threadLoop();
private:
friend class Singleton<RenderThread>;
+ friend class DispatchFrameCallbacks;
RenderThread();
virtual ~RenderThread();
+ void initializeDisplayEventReceiver();
+ static int displayEventReceiverCallback(int fd, int events, void* data);
+ void drainDisplayEventQueue();
+ void dispatchFrameCallbacks();
+
// Returns the next task to be run. If this returns NULL nextWakeup is set
// to the time to requery for the nextTask to run. mNextWakeup is also
// set to this time
@@ -69,6 +96,13 @@
nsecs_t mNextWakeup;
TaskQueue mQueue;
+
+ DisplayEventReceiver* mDisplayEventReceiver;
+ bool mVsyncRequested;
+ std::set<IFrameCallback*> mFrameCallbacks;
+ bool mFrameCallbackTaskPending;
+ DispatchFrameCallbacks* mFrameCallbackTask;
+ nsecs_t mFrameTime;
};
} /* namespace renderthread */
diff --git a/libs/hwui/utils/MathUtils.h b/libs/hwui/utils/MathUtils.h
index 57ba8fa..7deabe9 100644
--- a/libs/hwui/utils/MathUtils.h
+++ b/libs/hwui/utils/MathUtils.h
@@ -29,6 +29,10 @@
inline static bool isZero(float value) {
return (value >= -gNonZeroEpsilon) && (value <= gNonZeroEpsilon);
}
+
+ inline static bool isPositive(float value) {
+ return value >= gNonZeroEpsilon;
+ }
}; // class MathUtils
} /* namespace uirenderer */
diff --git a/location/java/android/location/FusedBatchOptions.java b/location/java/android/location/FusedBatchOptions.java
index 623d707..5600aeb 100644
--- a/location/java/android/location/FusedBatchOptions.java
+++ b/location/java/android/location/FusedBatchOptions.java
@@ -95,8 +95,9 @@
}
public static final class BatchFlags {
- public static int WAKEUP_ON_FIFO_FULL = 1<<0;
- public static int CALLBACK_ON_LOCATION_FIX = 1<<1;
+ // follow the definitions to the letter in fused_location.h
+ public static int WAKEUP_ON_FIFO_FULL = 0x0000001;
+ public static int CALLBACK_ON_LOCATION_FIX =0x0000002;
}
/*
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 04c6e97..ad0d459 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -88,12 +88,22 @@
public static final int CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT;
public static final int CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT);
+ // aka QUAD_BACK
public static final int CHANNEL_OUT_QUAD = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);
+ /** @hide */
+ public static final int CHANNEL_OUT_QUAD_SIDE = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
+ CHANNEL_OUT_SIDE_LEFT | CHANNEL_OUT_SIDE_RIGHT);
public static final int CHANNEL_OUT_SURROUND = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_CENTER);
+ // aka 5POINT1_BACK
public static final int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);
+ /** @hide */
+ public static final int CHANNEL_OUT_5POINT1_SIDE = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
+ CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY |
+ CHANNEL_OUT_SIDE_LEFT | CHANNEL_OUT_SIDE_RIGHT);
+ // TODO does this need an @deprecated ?
// different from AUDIO_CHANNEL_OUT_7POINT1
public static final int CHANNEL_OUT_7POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
diff --git a/media/java/android/media/DngCreator.java b/media/java/android/media/DngCreator.java
new file mode 100644
index 0000000..b2a38ab
--- /dev/null
+++ b/media/java/android/media/DngCreator.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.graphics.Bitmap;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureResult;
+import android.location.Location;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file.
+ *
+ * <p>
+ * This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR}
+ * buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw
+ * pixel data that is otherwise generated by an application. The DNG metadata tags will be
+ * generated from a {@link android.hardware.camera2.CaptureResult} object or set directly.
+ * </p>
+ *
+ * <p>
+ * The DNG file format is a cross-platform file format that is used to store pixel data from
+ * camera sensors with minimal pre-processing applied. DNG files allow for pixel data to be
+ * defined in a user-defined colorspace, and have associated metadata that allow for this
+ * pixel data to be converted to the standard CIE XYZ colorspace during post-processing.
+ * </p>
+ *
+ * <p>
+ * For more information on the DNG file format and associated metadata, please refer to the
+ * <a href=
+ * "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf">
+ * Adobe DNG 1.4.0.0 specification</a>.
+ * </p>
+ */
+public final class DngCreator {
+
+ /**
+ * Create a new DNG object.
+ *
+ * <p>
+ * It is not necessary to call any set methods to write a well-formatted DNG file.
+ * </p>
+ * <p>
+ * DNG metadata tags will be generated from the corresponding parameters in the
+ * {@link android.hardware.camera2.CaptureResult} object. This removes or overrides
+ * all previous tags set.
+ * </p>
+ *
+ * @param characteristics an object containing the static
+ * {@link android.hardware.camera2.CameraCharacteristics}.
+ * @param metadata a metadata object to generate tags from.
+ */
+ public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {/*TODO*/}
+
+ /**
+ * Set the orientation value to write.
+ *
+ * <p>
+ * This will be written as the TIFF "Orientation" tag {@code (0x0112)}.
+ * Calling this will override any prior settings for this tag.
+ * </p>
+ *
+ * @param orientation the orientation value to set, one of:
+ * <ul>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
+ * </ul>
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setOrientation(int orientation) {
+ return this;
+ }
+
+ /**
+ * Set the thumbnail image.
+ *
+ * <p>
+ * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
+ * The alpha channel will be discarded.
+ * </p>
+ *
+ * <p>
+ * The given bitmap should not be altered while this object is in use.
+ * </p>
+ *
+ * @param pixels a {@link android.graphics.Bitmap} of pixel data.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setThumbnail(Bitmap pixels) {
+ return this;
+ }
+
+ /**
+ * Set the thumbnail image.
+ *
+ * <p>
+ * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
+ * </p>
+ *
+ * <p>
+ * The given image should not be altered while this object is in use.
+ * </p>
+ *
+ * @param pixels an {@link android.media.Image} object with the format
+ * {@link android.graphics.ImageFormat#YUV_420_888}.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setThumbnail(Image pixels) {
+ return this;
+ }
+
+
+ /**
+ * Set image location metadata.
+ *
+ * <p>
+ * The given location object must contain at least a valid time, latitude, and longitude
+ * (equivalent to the values returned by {@link android.location.Location#getTime()},
+ * {@link android.location.Location#getLatitude()}, and
+ * {@link android.location.Location#getLongitude()} methods).
+ * </p>
+ *
+ * @param location an {@link android.location.Location} object to set.
+ * @return this {@link #DngCreator} object.
+ *
+ * @throws java.lang.IllegalArgumentException if the given location object doesn't
+ * contain enough information to set location metadata.
+ */
+ public DngCreator setLocation(Location location) { return this; }
+
+ /**
+ * Set the user description string to write.
+ *
+ * <p>
+ * This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}.
+ * </p>
+ *
+ * @param description the user description string.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setDescription(String description) {
+ return this;
+ }
+
+ /**
+ * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
+ * the currently configured metadata.
+ *
+ * <p>
+ * Raw pixel data must have 16 bits per pixel, and the input must contain at least
+ * {@code offset + 2 * (stride * (height - 1) + width * height)} bytes. The width and height of
+ * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
+ * and will typically be equal to the width and height of
+ * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
+ * If insufficient metadata is set to write a well-formatted DNG file, and
+ * {@link java.lang.IllegalStateException} will be thrown.
+ * </p>
+ *
+ * <p>
+ * When reading from the pixel input, {@code stride} pixels will be skipped
+ * after each row (excluding the last).
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param pixels an {@link java.io.InputStream} of pixel data to write.
+ * @param stride the stride of the raw image in pixels.
+ * @param offset the offset of the raw image in bytes. This indicates how many bytes will
+ * be skipped in the input before any pixel data is read.
+ *
+ * @throws IOException if an error was encountered in the input or output stream.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ */
+ public void writeInputStream(OutputStream dngOutput, InputStream pixels, int stride,
+ long offset) throws IOException {
+ /*TODO*/
+ }
+
+ /**
+ * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
+ * the currently configured metadata.
+ *
+ * <p>
+ * Raw pixel data must have 16 bits per pixel, and the input must contain at least
+ * {@code offset + 2 * (stride * (height - 1) + width * height)} bytes. The width and height of
+ * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
+ * and will typically be equal to the width and height of
+ * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
+ * If insufficient metadata is set to write a well-formatted DNG file, and
+ * {@link java.lang.IllegalStateException} will be thrown.
+ * </p>
+ *
+ * <p>
+ * When reading from the pixel input, {@code stride} pixels will be skipped
+ * after each row (excluding the last).
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
+ * @param stride the stride of the raw image in pixels.
+ * @param offset the offset of the raw image in bytes. This indicates how many bytes will
+ * be skipped in the input before any pixel data is read.
+ *
+ * @throws IOException if an error was encountered in the input or output stream.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ */
+ public void writeByteBuffer(OutputStream dngOutput, ByteBuffer pixels, int stride,
+ long offset) throws IOException {/*TODO*/}
+
+ /**
+ * Write the pixel data to a DNG file with the currently configured metadata.
+ *
+ * <p>
+ * For this method to succeed, the {@link android.media.Image} input must contain
+ * {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an
+ * {@link java.lang.IllegalArgumentException} will be thrown.
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param pixels an {@link android.media.Image} to write.
+ *
+ * @throws java.io.IOException if an error was encountered in the output stream.
+ * @throws java.lang.IllegalArgumentException if an image with an unsupported format was used.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ */
+ public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {/*TODO*/}
+
+}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 72f3e1a..9516bf8 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -44,6 +44,8 @@
import android.sax.Element;
import android.sax.ElementListener;
import android.sax.RootElement;
+import android.system.ErrnoException;
+import android.system.Os;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
@@ -60,9 +62,6 @@
import java.util.Iterator;
import java.util.Locale;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-
/**
* Internal service helper that no-one should use directly.
*
@@ -1130,7 +1129,7 @@
if (path != null && path.startsWith("/")) {
boolean exists = false;
try {
- exists = Libcore.os.access(path, libcore.io.OsConstants.F_OK);
+ exists = Os.access(path, android.system.OsConstants.F_OK);
} catch (ErrnoException e1) {
}
if (!exists && !MtpConstants.isAbstractObject(format)) {
@@ -1281,6 +1280,14 @@
mMediaProvider = null;
}
+ private void releaseResources() {
+ // release the DrmManagerClient resources
+ if (mDrmManagerClient != null) {
+ mDrmManagerClient.release();
+ mDrmManagerClient = null;
+ }
+ }
+
private void initialize(String volumeName) {
mMediaProvider = mContext.getContentResolver().acquireProvider("media");
@@ -1341,6 +1348,8 @@
Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
+ } finally {
+ releaseResources();
}
}
@@ -1364,6 +1373,8 @@
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
return null;
+ } finally {
+ releaseResources();
}
}
@@ -1511,6 +1522,7 @@
if (fileList != null) {
fileList.close();
}
+ releaseResources();
}
}
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 716418c..7a86811 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -274,17 +274,11 @@
fid = env->GetStaticFieldID(imageFormatClazz, "JPEG", "I");
jpegFormat = env->GetStaticIntField(imageFormatClazz, fid);
- fid = env->GetStaticFieldID(imageFormatClazz, "RAW_SENSOR", "I");
- rawSensorFormat = env->GetStaticIntField(imageFormatClazz, fid);
- // Translate the JPEG to BLOB for camera purpose, an add more if more mismatch is found.
+ // Translate the JPEG to BLOB for camera purpose.
if (format == jpegFormat) {
format = HAL_PIXEL_FORMAT_BLOB;
}
- // Same thing for RAW_SENSOR format
- if (format == rawSensorFormat) {
- format = HAL_PIXEL_FORMAT_RAW_SENSOR;
- }
return format;
}
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 48ef9db..36c1d5c 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -41,6 +41,9 @@
import android.os.ServiceManager;
import android.os.StatFs;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStatVfs;
import android.util.DisplayMetrics;
import android.util.Slog;
@@ -66,11 +69,8 @@
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
import libcore.io.Streams;
-import libcore.io.StructStatVfs;
/*
* This service copies a downloaded apk to a file passed in as
@@ -245,7 +245,7 @@
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
- final StructStatVfs stat = Libcore.os.statvfs(path);
+ final StructStatVfs stat = Os.statvfs(path);
final long totalSize = stat.f_blocks * stat.f_bsize;
final long availSize = stat.f_bavail * stat.f_bsize;
return new long[] { totalSize, availSize };
@@ -379,7 +379,7 @@
}
try {
- Libcore.os.chmod(resFile.getAbsolutePath(), 0640);
+ Os.chmod(resFile.getAbsolutePath(), 0640);
} catch (ErrnoException e) {
Slog.e(TAG, "Could not chown APK: " + e.getMessage());
PackageHelper.destroySdDir(newCid);
@@ -401,7 +401,7 @@
}
try {
- Libcore.os.chmod(publicZipFile.getAbsolutePath(), 0644);
+ Os.chmod(publicZipFile.getAbsolutePath(), 0644);
} catch (ErrnoException e) {
Slog.e(TAG, "Could not chown public resource file: " + e.getMessage());
PackageHelper.destroySdDir(newCid);
diff --git a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml
index b4847f0..b2d0219 100644
--- a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml
@@ -20,6 +20,7 @@
<!-- This contains emergency call button and carrier as shared by pin/pattern/password screens -->
<com.android.keyguard.EmergencyCarrierArea
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -35,7 +36,8 @@
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/kg_status_line_font_size"
- android:textColor="?android:attr/textColorSecondary" />
+ android:textColor="?android:attr/textColorSecondary"
+ androidprv:allCaps="@bool/kg_use_all_caps" />
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/Keyguard/res/values/attrs.xml b/packages/Keyguard/res/values/attrs.xml
index e045dd2..2410b6a 100644
--- a/packages/Keyguard/res/values/attrs.xml
+++ b/packages/Keyguard/res/values/attrs.xml
@@ -133,4 +133,8 @@
<attr name="digit" format="integer" />
<attr name="textView" format="reference" />
</declare-styleable>
+
+ <declare-styleable name="CarrierText">
+ <attr name="allCaps" format="boolean" />
+ </declare-styleable>
</resources>
diff --git a/packages/Keyguard/src/com/android/keyguard/CarrierText.java b/packages/Keyguard/src/com/android/keyguard/CarrierText.java
index 88558cd..05f2962 100644
--- a/packages/Keyguard/src/com/android/keyguard/CarrierText.java
+++ b/packages/Keyguard/src/com/android/keyguard/CarrierText.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import android.content.Context;
+import android.content.res.TypedArray;
import android.text.method.SingleLineTransformationMethod;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -81,7 +82,14 @@
public CarrierText(Context context, AttributeSet attrs) {
super(context, attrs);
mLockPatternUtils = new LockPatternUtils(mContext);
- boolean useAllCaps = mContext.getResources().getBoolean(R.bool.kg_use_all_caps);
+ boolean useAllCaps;
+ TypedArray a = context.getTheme().obtainStyledAttributes(
+ attrs, R.styleable.CarrierText, 0, 0);
+ try {
+ useAllCaps = a.getBoolean(R.styleable.CarrierText_allCaps, false);
+ } finally {
+ a.recycle();
+ }
setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
index 2e4dbdf..ede23ef 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
@@ -27,11 +27,10 @@
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.MutableInt;
import android.view.View;
import android.widget.TextView;
-import libcore.util.MutableInt;
-
import java.lang.ref.WeakReference;
import com.android.internal.widget.LockPatternUtils;
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 73c2840..ba67a82 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -215,8 +215,29 @@
mUserHasTrust.put(userId, enabled);
}
+ private boolean isTrustDisabled(int userId) {
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (dpm != null) {
+ // TODO once UI is finalized
+ final boolean disabledByGlobalActions = false;
+ final boolean disabledBySettings = false;
+
+ // Don't allow trust agent if device is secured with a SIM PIN. This is here
+ // mainly because there's no other way to prompt the user to enter their SIM PIN
+ // once they get past the keyguard screen.
+ final boolean disabledBySimPin = isSimPinSecure();
+
+ final boolean disabledByDpm = (dpm.getKeyguardDisabledFeatures(null, userId)
+ & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0;
+ return disabledByDpm || disabledByGlobalActions || disabledBySettings
+ || disabledBySimPin;
+ }
+ return false;
+ }
+
public boolean getUserHasTrust(int userId) {
- return mUserHasTrust.get(userId);
+ return !isTrustDisabled(userId) && mUserHasTrust.get(userId);
}
static class DisplayClientState {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e1c17cb..56f5a3a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -88,7 +88,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
- <uses-permission android:name="android.permission.RENDER_STATS" />
+ <uses-permission android:name="android.permission.FRAME_STATS" />
<application android:label="@string/app_label">
<provider
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 88b01c7..809adcd 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/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 69fbc1b..24ccb2b 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -45,6 +45,15 @@
android:layout_marginTop="@dimen/status_bar_height"
android:visibility="gone" />
+ <com.android.keyguard.CarrierText
+ android:id="@+id/keyguard_carrier_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dp"
+ android:layout_marginLeft="8dp"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
index 5d2f330..d81e525 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
@@ -19,34 +19,27 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:focusable="true"
+ android:clickable="true"
+ android:background="@*android:drawable/notification_quantum_bg_dim"
>
- <com.android.systemui.statusbar.LatestItemView
- android:id="@+id/container"
+ <TextView
+ android:id="@+id/more_text"
android:layout_width="match_parent"
- android:layout_height="40dp"
- android:layout_marginTop="@dimen/notification_divider_height"
- android:focusable="true"
- android:clickable="true"
- android:background="@*android:drawable/notification_quantum_bg_dim"
- >
- <TextView
- android:id="@+id/more_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:gravity="center_horizontal"
- android:textColor="@color/keyguard_overflow_content_color"
- android:textAllCaps="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
- <com.android.systemui.statusbar.NotificationOverflowIconsView
- android:id="@+id/overflow_icons_view"
- android:layout_gravity="end|center_vertical"
- android:gravity="end"
- android:paddingLeft="8dp"
- android:paddingRight="8dp"
- android:layout_width="120dp"
- android:layout_height="wrap_content"
- />
- </com.android.systemui.statusbar.LatestItemView>
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:gravity="center_horizontal"
+ android:textColor="@color/keyguard_overflow_content_color"
+ android:textAllCaps="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ />
+ <com.android.systemui.statusbar.NotificationOverflowIconsView
+ android:id="@+id/overflow_icons_view"
+ android:layout_gravity="end|center_vertical"
+ android:gravity="end"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ />
</com.android.systemui.statusbar.NotificationOverflowContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index d61d8b9..41e7dac 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -2,17 +2,18 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:focusable="true"
+ android:clickable="true"
+ android:background="@*android:drawable/notification_quantum_bg"
>
- <View
- android:id="@+id/top_glow"
- android:alpha="0"
- android:visibility="invisible"
+ <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expanded"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expandedPublic"
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_divider_height"
- android:layout_gravity="top|center_horizontal"
- android:background="@drawable/top_divider_glow"
- />
+ android:layout_height="wrap_content" />
<Button
android:id="@+id/veto"
@@ -25,35 +26,6 @@
android:paddingStart="8dp"
/>
- <com.android.systemui.statusbar.LatestItemView android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_divider_height"
- android:layout_marginTop="@dimen/notification_divider_height"
- android:focusable="true"
- android:clickable="true"
- android:background="@*android:drawable/notification_quantum_bg"
- >
-
- <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expanded"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
- <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expandedPublic"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- </com.android.systemui.statusbar.LatestItemView>
-
- <View
- android:id="@+id/bottom_glow"
- android:alpha="0"
- android:visibility="invisible"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_divider_height"
- android:layout_gravity="bottom|center_horizontal"
- android:background="@drawable/bottom_divider_glow"
- />
-
<TextView
android:id="@+id/debug_info"
android:visibility="invisible"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0604817..176879e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -82,12 +82,6 @@
<!-- Height of a medium notification in the status bar -->
<dimen name="notification_mid_height">128dp</dimen>
- <!-- Height of a small notification in the status bar plus glow, padding, etc -->
- <dimen name="notification_row_min_height">68dp</dimen>
-
- <!-- Height of a large notification in the status bar plus glow, padding, etc -->
- <dimen name="notification_row_max_height">260dp</dimen>
-
<!-- size at which Notification icons will be drawn in the status bar -->
<dimen name="status_bar_icon_drawing_size">18dip</dimen>
@@ -261,6 +255,9 @@
<!-- Z distance between notifications if they are in the stack -->
<dimen name="z_distance_between_notifications">2dp</dimen>
+ <!-- The padding between the individual notification cards. -->
+ <dimen name="notification_padding">3dp</dimen>
+
<!-- Width of the zen mode interstitial dialog. -->
<dimen name="zen_mode_dialog_width">320dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f3c956c..b4a13d4 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/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 8dd3f8d..c585a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -19,7 +19,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.media.AudioManager;
@@ -81,8 +80,6 @@
private boolean mHasPopped;
private View mEventSource;
private View mCurrView;
- private View mCurrViewTopGlow;
- private View mCurrViewBottomGlow;
private float mOldHeight;
private float mNaturalHeight;
private float mInitialTouchFocusY;
@@ -99,9 +96,6 @@
private ScaleGestureDetector mSGD;
private ViewScaler mScaler;
private ObjectAnimator mScaleAnimation;
- private AnimatorSet mGlowAnimationSet;
- private ObjectAnimator mGlowTopAnimation;
- private ObjectAnimator mGlowBottomAnimation;
private Vibrator mVibrator;
private int mSmallSize;
@@ -223,14 +217,6 @@
}
};
- mGlowTopAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
- mGlowTopAnimation.addListener(glowVisibilityController);
- mGlowBottomAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
- mGlowBottomAnimation.addListener(glowVisibilityController);
- mGlowAnimationSet = new AnimatorSet();
- mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation);
- mGlowAnimationSet.setDuration(GLOW_DURATION);
-
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mTouchSlop = configuration.getScaledTouchSlop();
@@ -251,7 +237,6 @@
float newHeight = clamp(target);
mScaler.setHeight(newHeight);
- setGlow(calculateGlow(target, newHeight));
mLastFocusY = mSGD.getFocusY();
mLastSpanY = mSGD.getCurrentSpan();
}
@@ -322,37 +307,6 @@
return (GLOW_BASE + strength * (1f - GLOW_BASE));
}
- public void setGlow(float glow) {
- if (!mGlowAnimationSet.isRunning() || glow == 0f) {
- if (mGlowAnimationSet.isRunning()) {
- mGlowAnimationSet.end();
- }
- if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) {
- if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) {
- // animate glow in and out
- mGlowTopAnimation.setTarget(mCurrViewTopGlow);
- mGlowBottomAnimation.setTarget(mCurrViewBottomGlow);
- mGlowTopAnimation.setFloatValues(glow);
- mGlowBottomAnimation.setFloatValues(glow);
- mGlowAnimationSet.setupStartValues();
- mGlowAnimationSet.start();
- } else {
- // set it explicitly in reponse to touches.
- mCurrViewTopGlow.setAlpha(glow);
- mCurrViewBottomGlow.setAlpha(glow);
- handleGlowVisibility();
- }
- }
- }
- }
-
- private void handleGlowVisibility() {
- mCurrViewTopGlow.setVisibility(mCurrViewTopGlow.getAlpha() <= 0.0f ?
- View.INVISIBLE : View.VISIBLE);
- mCurrViewBottomGlow.setVisibility(mCurrViewBottomGlow.getAlpha() <= 0.0f ?
- View.INVISIBLE : View.VISIBLE);
- }
-
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
@@ -466,9 +420,6 @@
if (mHasPopped) {
mScaler.setHeight(newHeight);
- setGlow(GLOW_BASE);
- } else {
- setGlow(calculateGlow(4f * pull, 0f));
}
final int x = (int) mSGD.getFocusX();
@@ -517,7 +468,6 @@
if (DEBUG) Log.d(TAG, "scale type " + expandType + " beginning on view: " + v);
mCallback.setUserLockedChild(v, true);
setView(v);
- setGlow(GLOW_BASE);
mScaler.setView(v);
mOldHeight = mScaler.getHeight();
if (mCallback.canChildBeExpanded(v)) {
@@ -549,7 +499,6 @@
if (mScaleAnimation.isRunning()) {
mScaleAnimation.cancel();
}
- setGlow(0f);
mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight);
if (targetHeight != currentHeight) {
mScaleAnimation.setFloatValues(targetHeight);
@@ -571,23 +520,11 @@
private void clearView() {
mCurrView = null;
- mCurrViewTopGlow = null;
- mCurrViewBottomGlow = null;
+
}
private void setView(View v) {
mCurrView = v;
- if (v instanceof ViewGroup) {
- ViewGroup g = (ViewGroup) v;
- mCurrViewTopGlow = g.findViewById(R.id.top_glow);
- mCurrViewBottomGlow = g.findViewById(R.id.bottom_glow);
- if (DEBUG) {
- String debugLog = "Looking for glows: " +
- (mCurrViewTopGlow != null ? "found top " : "didn't find top") +
- (mCurrViewBottomGlow != null ? "found bottom " : "didn't find bottom");
- Log.v(TAG, debugLog);
- }
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 21c2926..ae18aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -50,7 +50,8 @@
@Override
public void start() {
- mUseAlternateRecents = SystemProperties.getBoolean("persist.recents.use_alternate", false);
+ Configuration config = mContext.getResources().getConfiguration();
+ mUseAlternateRecents = (config.smallestScreenWidthDp < 600);
if (mUseAlternateRecents) {
if (mAlternateRecents == null) {
mAlternateRecents = new AlternateRecentsComponent(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 5e90084..d647dfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 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.
@@ -11,7 +11,7 @@
* 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.
+ * limitations under the License
*/
package com.android.systemui.statusbar;
@@ -21,12 +21,15 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import com.android.internal.R;
-public class LatestItemView extends FrameLayout {
+/**
+ * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
+ * to implement dimming/activating on Keyguard for the double-tap gesture
+ */
+public class ActivatableNotificationView extends FrameLayout {
private static final long DOUBLETAP_TIMEOUT_MS = 1000;
@@ -48,11 +51,12 @@
private OnActivatedListener mOnActivatedListener;
- public LatestItemView(Context context, AttributeSet attrs) {
+ public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
+
private final Runnable mTapTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -66,20 +70,6 @@
}
@Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (super.onRequestSendAccessibilityEvent(child, event)) {
- // Add a record for the entire layout since its content is somehow small.
- // The event comes from a leaf view that is interacted with.
- AccessibilityEvent record = AccessibilityEvent.obtain();
- onInitializeAccessibilityEvent(record);
- dispatchPopulateAccessibilityEvent(record);
- event.appendRecord(record);
- return true;
- }
- return false;
- }
-
- @Override
public boolean onTouchEvent(MotionEvent event) {
if (mLocked) {
return handleTouchEventLocked(event);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 2ea5add..3e21640 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -83,7 +83,7 @@
import java.util.Locale;
public abstract class BaseStatusBar extends SystemUI implements
- CommandQueue.Callbacks, LatestItemView.OnActivatedListener {
+ CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener {
public static final String TAG = "StatusBar";
public static final boolean DEBUG = false;
public static final boolean MULTIUSER_DEBUG = false;
@@ -737,8 +737,9 @@
return false;
}
- Log.v(TAG, "publicNotification: "
- + sbn.getNotification().publicVersion);
+ if (DEBUG) {
+ Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion);
+ }
Notification publicNotification = sbn.getNotification().publicVersion;
@@ -759,20 +760,19 @@
// NB: the large icon is now handled entirely by the template
// bind the click event to the content area
- ViewGroup content = (ViewGroup)row.findViewById(R.id.container);
SizeAdaptiveLayout expanded = (SizeAdaptiveLayout)row.findViewById(R.id.expanded);
SizeAdaptiveLayout expandedPublic
= (SizeAdaptiveLayout)row.findViewById(R.id.expandedPublic);
- content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
PendingIntent contentIntent = sbn.getNotification().contentIntent;
if (contentIntent != null) {
final View.OnClickListener listener = makeClicker(contentIntent,
sbn.getPackageName(), sbn.getTag(), sbn.getId(), isHeadsUp, sbn.getUserId());
- content.setOnClickListener(listener);
+ row.setOnClickListener(listener);
} else {
- content.setOnClickListener(null);
+ row.setOnClickListener(null);
}
// set up the adaptive layout
@@ -882,7 +882,6 @@
entry.row = row;
entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
entry.row.setOnActivatedListener(this);
- entry.content = content;
entry.expanded = contentViewLocal;
entry.expandedPublic = publicViewLocal;
entry.setBigContentView(bigContentViewLocal);
@@ -1349,9 +1348,9 @@
final View.OnClickListener listener = makeClicker(contentIntent,
notification.getPackageName(), notification.getTag(), notification.getId(),
isHeadsUp, notification.getUserId());
- entry.content.setOnClickListener(listener);
+ entry.row.setOnClickListener(listener);
} else {
- entry.content.setOnClickListener(null);
+ entry.row.setOnClickListener(null);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index bb481ec..35c02eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -20,13 +20,12 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
+import android.view.accessibility.AccessibilityEvent;
import com.android.internal.widget.SizeAdaptiveLayout;
import com.android.systemui.R;
-public class ExpandableNotificationRow extends FrameLayout
- implements LatestItemView.OnActivatedListener {
+public class ExpandableNotificationRow extends ActivatableNotificationView {
private int mRowMinHeight;
private int mRowMaxHeight;
@@ -41,8 +40,6 @@
/** Are we showing the "public" version */
private boolean mShowingPublic;
- private LatestItemView mLatestItemView;
-
/**
* Is this notification expanded by the system. The expansion state can be overridden by the
* user expansion.
@@ -53,7 +50,6 @@
private int mMaxExpandHeight;
private boolean mMaxHeightNeedsUpdate;
private NotificationActivator mActivator;
- private LatestItemView.OnActivatedListener mOnActivatedListener;
private boolean mSelfInitiatedLayout;
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
@@ -65,10 +61,22 @@
super.onFinishInflate();
mPublicLayout = (SizeAdaptiveLayout) findViewById(R.id.expandedPublic);
mPrivateLayout = (SizeAdaptiveLayout) findViewById(R.id.expanded);
- mLatestItemView = (LatestItemView) findViewById(R.id.container);
mActivator = new NotificationActivator(this);
- mLatestItemView.setOnActivatedListener(this);
+ }
+
+ @Override
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ if (super.onRequestSendAccessibilityEvent(child, event)) {
+ // Add a record for the entire layout since its content is somehow small.
+ // The event comes from a leaf view that is interacted with.
+ AccessibilityEvent record = AccessibilityEvent.obtain();
+ onInitializeAccessibilityEvent(record);
+ dispatchPopulateAccessibilityEvent(record);
+ event.appendRecord(record);
+ return true;
+ }
+ return false;
}
public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
@@ -220,7 +228,7 @@
* Sets the notification as dimmed, meaning that it will appear in a more gray variant.
*/
public void setDimmed(boolean dimmed) {
- mLatestItemView.setDimmed(dimmed);
+ super.setDimmed(dimmed);
mActivator.setDimmed(dimmed);
}
@@ -232,46 +240,10 @@
return mMaxExpandHeight;
}
- /**
- * Sets the notification as locked. In the locked state, the first tap will produce a quantum
- * ripple to make the notification brighter and only the second tap will cause a click.
- */
- public void setLocked(boolean locked) {
- mLatestItemView.setLocked(locked);
- }
-
- public void setOnActivatedListener(LatestItemView.OnActivatedListener listener) {
- mOnActivatedListener = listener;
- }
-
public NotificationActivator getActivator() {
return mActivator;
}
- @Override
- public void onActivated(View view) {
- if (mOnActivatedListener != null) {
- mOnActivatedListener.onActivated(this);
- }
- }
-
- @Override
- public void onReset(View view) {
- if (mOnActivatedListener != null) {
- mOnActivatedListener.onReset(this);
- }
- }
-
- /**
- * Sets the resource id for the background of this notification.
- *
- * @param bgResId The background resource to use in normal state.
- * @param dimmedBgResId The background resource to use in dimmed state.
- */
- public void setBackgroundResourceIds(int bgResId, int dimmedBgResId) {
- mLatestItemView.setBackgroundResourceIds(bgResId, dimmedBgResId);
- }
-
/**
* @return the potential height this view could expand in addition.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java
index d563968..6401695 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java
@@ -107,7 +107,7 @@
mBar.updateNotification(mSynKey, sbn);
}
final NotificationData.Entry entry = mBar.mNotificationData.findByKey(mSynKey);
- entry.content.setOnClickListener(mSynClickListener);
+ entry.row.setOnClickListener(mSynClickListener);
}
private final View.OnClickListener mSynClickListener = new View.OnClickListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index b9a59dd..6b6f55a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -33,7 +33,6 @@
public StatusBarNotification notification;
public StatusBarIconView icon;
public ExpandableNotificationRow row; // the outer expanded view
- public View content; // takes the click events and sends the PendingIntent
public View expanded; // the inflated RemoteViews
public View expandedPublic; // for insecure lockscreens
public ImageView largeIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
index be58dad..af91314 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
@@ -18,8 +18,6 @@
import android.content.Context;
import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.systemui.R;
@@ -27,65 +25,30 @@
/**
* Container view for overflowing notification icons on Keyguard.
*/
-public class NotificationOverflowContainer extends FrameLayout
- implements LatestItemView.OnActivatedListener {
+public class NotificationOverflowContainer extends ActivatableNotificationView {
private NotificationOverflowIconsView mIconsView;
- private LatestItemView.OnActivatedListener mOnActivatedListener;
private NotificationActivator mActivator;
- public NotificationOverflowContainer(Context context) {
- super(context);
- }
-
public NotificationOverflowContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public NotificationOverflowContainer(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public NotificationOverflowContainer(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
- LatestItemView latestItemView = (LatestItemView) findViewById(R.id.container);
mActivator = new NotificationActivator(this);
mActivator.setDimmed(true);
- latestItemView.setOnActivatedListener(this);
- latestItemView.setLocked(true);
+ setLocked(true);
}
public NotificationOverflowIconsView getIconsView() {
return mIconsView;
}
- public void setOnActivatedListener(LatestItemView.OnActivatedListener onActivatedListener) {
- mOnActivatedListener = onActivatedListener;
- }
-
- @Override
- public void onActivated(View view) {
- if (mOnActivatedListener != null) {
- mOnActivatedListener.onActivated(this);
- }
- }
-
- @Override
- public void onReset(View view) {
- if (mOnActivatedListener != null) {
- mOnActivatedListener.onReset(this);
- }
- }
-
public NotificationActivator getActivator() {
return mActivator;
}
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 0000000..769b1b1
--- /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 7f1ddaf..545352c 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 @@
View mNotificationPanelHeader;
View mKeyguardStatusView;
View mKeyguardBottomArea;
+ KeyguardIndicationTextView mKeyguardIndicationTextView;
+
+ // TODO: Fetch phrase from search/hotword provider.
+ String mKeyguardHotwordPhrase = "";
int mKeyguardMaxNotificationCount;
View mDateTimeView;
View mClearButton;
@@ -243,6 +247,7 @@
private int mCarrierLabelHeight;
private TextView mEmergencyCallLabel;
private int mNotificationHeaderHeight;
+ private View mKeyguardCarrierLabel;
private boolean mShowCarrierInPanel = false;
@@ -371,6 +376,7 @@
private InterceptedNotifications mIntercepted;
private VelocityTracker mSettingsTracker;
private float mSettingsDownY;
+ private boolean mSettingsStarted;
private boolean mSettingsCancelled;
private boolean mSettingsClosing;
private int mNotificationPadding;
@@ -611,6 +617,7 @@
(NotificationOverflowContainer) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
mKeyguardIconOverflowContainer.setOnActivatedListener(this);
+ mKeyguardCarrierLabel = mStatusBarWindow.findViewById(R.id.keyguard_carrier_text);
mStackScroller.addView(mKeyguardIconOverflowContainer);
mExpandedContents = mStackScroller;
@@ -618,7 +625,8 @@
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);
@@ -761,16 +769,16 @@
if (mSettingsTracker != null) {
mSettingsTracker.addMovement(event);
}
-
+ final int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mSettingsTracker = VelocityTracker.obtain();
mSettingsDownY = event.getY();
mSettingsCancelled = false;
+ mSettingsStarted = false;
mSettingsClosing = mFlipSettingsView.getVisibility() == View.VISIBLE;
- mFlipSettingsView.setVisibility(View.VISIBLE);
- mStackScroller.setVisibility(View.VISIBLE);
- positionSettings(0);
- if (!mSettingsClosing) {
+ if (mSettingsClosing) {
+ mStackScroller.setVisibility(View.VISIBLE);
+ } else {
mFlipSettingsView.setTranslationY(-mNotificationPanel.getMeasuredHeight());
}
dispatchSettingsEvent(event);
@@ -779,8 +787,7 @@
final float dy = event.getY() - mSettingsDownY;
final FlipperButton flipper = mOnKeyguard ? mKeyguardFlipper : mHeaderFlipper;
final boolean inButton = flipper.inHolderBounds(event);
- final int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- final boolean qsTap = mSettingsClosing && Math.abs(dy) < slop;
+ final boolean qsTap = mSettingsClosing && Math.abs(dy) < slop;
if (!qsTap && !inButton) {
mSettingsTracker.computeCurrentVelocity(1000);
final float vy = mSettingsTracker.getYVelocity();
@@ -796,10 +803,9 @@
dispatchSettingsEvent(event);
} else if (mSettingsTracker != null && event.getAction() == MotionEvent.ACTION_MOVE) {
final float dy = event.getY() - mSettingsDownY;
- positionSettings(dy);
if (mSettingsClosing) {
- final boolean qsTap =
- Math.abs(dy) < ViewConfiguration.get(mContext).getScaledTouchSlop();
+ positionSettings(dy);
+ final boolean qsTap = Math.abs(dy) < slop;
if (!mSettingsCancelled && !qsTap) {
MotionEvent cancelEvent = MotionEvent.obtainNoHistory(event);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
@@ -807,6 +813,14 @@
mSettingsCancelled = true;
}
} else {
+ if (!mSettingsStarted && dy > slop) {
+ mSettingsStarted = true;
+ mFlipSettingsView.setVisibility(View.VISIBLE);
+ mStackScroller.setVisibility(View.VISIBLE);
+ }
+ if (mSettingsStarted) {
+ positionSettings(dy);
+ }
dispatchSettingsEvent(event);
}
}
@@ -1332,7 +1346,8 @@
!(emergencyCallsShownElsewhere && mNetworkController.isEmergencyOnly())
&& mStackScroller.getHeight() < (mNotificationPanel.getHeight()
- mCarrierLabelHeight - mNotificationHeaderHeight)
- && mStackScroller.getVisibility() == View.VISIBLE;
+ && mStackScroller.getVisibility() == View.VISIBLE
+ && !mOnKeyguard;
if (force || mCarrierLabelVisible != makeVisible) {
mCarrierLabelVisible = makeVisible;
@@ -1610,9 +1625,9 @@
return (mDisabled & StatusBarManager.DISABLE_EXPAND) == 0;
}
- void makeExpandedVisible() {
+ void makeExpandedVisible(boolean force) {
if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
- if (mExpandedVisible || !panelsEnabled()) {
+ if (!force && (mExpandedVisible || !panelsEnabled())) {
return;
}
@@ -2703,8 +2718,8 @@
}
mHeadsUpNotificationDecay = res.getInteger(R.integer.heads_up_notification_decay);
- mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_row_min_height);
- mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_row_max_height);
+ mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
mKeyguardMaxNotificationCount = res.getInteger(R.integer.keyguard_max_notification_count);
@@ -2942,6 +2957,9 @@
}
mKeyguardStatusView.setVisibility(View.VISIBLE);
mKeyguardBottomArea.setVisibility(View.VISIBLE);
+ mKeyguardIndicationTextView.setVisibility(View.VISIBLE);
+ mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
+ mKeyguardCarrierLabel.setVisibility(View.VISIBLE);
mNotificationPanelHeader.setVisibility(View.GONE);
mKeyguardFlipper.setVisibility(View.VISIBLE);
@@ -2949,6 +2967,8 @@
} else {
mKeyguardStatusView.setVisibility(View.GONE);
mKeyguardBottomArea.setVisibility(View.GONE);
+ mKeyguardIndicationTextView.setVisibility(View.GONE);
+ mKeyguardCarrierLabel.setVisibility(View.GONE);
mNotificationPanelHeader.setVisibility(View.VISIBLE);
mKeyguardFlipper.setVisibility(View.GONE);
@@ -2959,6 +2979,7 @@
updateRowStates();
checkBarModes();
updateNotificationIcons();
+ updateCarrierLabelVisibility(false);
}
public void userActivity() {
@@ -2987,14 +3008,23 @@
}
private void instantExpandNotificationsPanel() {
+
+ // Make our window larger and the panel visible.
+ makeExpandedVisible(true);
+ mNotificationPanel.setVisibility(View.VISIBLE);
+
+ // Wait for window manager to pickup the change, so we know the maximum height of the panel
+ // then.
mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- 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 +3034,29 @@
@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/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 8957a77..194774d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -168,6 +168,9 @@
if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
mSimState = IccCardConstants.State.ABSENT;
}
+ else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+ mSimState = IccCardConstants.State.CARD_IO_ERROR;
+ }
else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
mSimState = IccCardConstants.State.READY;
}
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 6ba18b3..79c63f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -106,7 +106,7 @@
@Override
public void onPanelPeeked() {
super.onPanelPeeked();
- mBar.makeExpandedVisible();
+ mBar.makeExpandedVisible(false);
}
@Override
@@ -160,6 +160,18 @@
}
@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/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index c5dae85..6b5ef5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -69,8 +69,8 @@
mStackScrollLayout = (NotificationStackScrollLayout) findViewById(
R.id.notification_stack_scroller);
mNotificationPanel = (NotificationPanelView) findViewById(R.id.notification_panel);
- int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_min_height);
- int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_max_height);
+ int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
+ int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
mExpandHelper = new ExpandHelper(getContext(), mStackScrollLayout,
minHeight, maxHeight);
mExpandHelper.setEventSource(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java
index c1662ba..20011ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java
@@ -212,6 +212,13 @@
}
}
+ @Override
+ protected void onDetachedFromWindow() {
+ if (mAdapter != null) {
+ mAdapter.dispose();
+ }
+ }
+
public void setAutoActivate(boolean value) {
mAutoActivate = value;
}
@@ -396,6 +403,7 @@
void setMode(boolean mode);
void select(ExitCondition ec);
void init();
+ void dispose();
void setCallbacks(Callbacks callbacks);
ExitCondition getExitCondition(int d);
int getExitConditionCount();
@@ -406,6 +414,7 @@
public String line1;
public String line2;
public String action;
+ public Object tag;
}
public interface Callbacks {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
index c97ba8d..a5e016a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
@@ -16,14 +16,22 @@
package com.android.systemui.statusbar.phone;
+import android.app.INotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.util.ArrayMap;
import android.util.Log;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -34,8 +42,10 @@
private final ContentResolver mResolver;
private final Handler mHandler = new Handler();
private final SettingsObserver mObserver;
- private final List<ExitCondition> mExits = Arrays.asList(
- newExit("Until you turn this off", "Until", "You turn this off"));
+ private final List<ExitCondition> mExits = new ArrayList<ExitCondition>(Arrays.asList(
+ newExit("Until you turn this off", "Until", "You turn this off", null)));
+ private final INotificationManager mNoMan;
+ private final ArrayMap<Uri, Condition> mConditions = new ArrayMap<Uri, Condition>();
private Callbacks mCallbacks;
private int mExitIndex;
@@ -45,6 +55,13 @@
mContext = context;
mResolver = mContext.getContentResolver();
mObserver = new SettingsObserver(mHandler);
+ mNoMan = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ try {
+ mNoMan.requestZenModeConditions(mListener, true /*requested*/);
+ } catch (RemoteException e) {
+ // noop
+ }
mObserver.init();
init();
}
@@ -77,6 +94,15 @@
}
}
+ @Override
+ public void dispose() {
+ try {
+ mNoMan.requestZenModeConditions(mListener, false /*requested*/);
+ } catch (RemoteException e) {
+ // noop
+ }
+ }
+
private void dispatchChanged() {
mHandler.removeCallbacks(mChanged);
mHandler.post(mChanged);
@@ -117,13 +143,20 @@
}
mExitIndex = i;
dispatchChanged();
+ final Uri conditionUri = (Uri) ec.tag;
+ try {
+ mNoMan.setZenModeCondition(conditionUri);
+ } catch (RemoteException e) {
+ // noop
+ }
}
- private static ExitCondition newExit(String summary, String line1, String line2) {
+ private static ExitCondition newExit(String summary, String line1, String line2, Object tag) {
final ExitCondition rt = new ExitCondition();
rt.summary = summary;
rt.line1 = line1;
rt.line2 = line2;
+ rt.tag = tag;
return rt;
}
@@ -168,4 +201,21 @@
return v != Settings.Global.ZEN_MODE_OFF;
}
}
+
+ private final IConditionListener mListener = new IConditionListener.Stub() {
+ @Override
+ public void onConditionsReceived(Condition[] conditions) {
+ if (conditions == null || conditions.length == 0) return;
+ for (Condition c : conditions) {
+ mConditions.put(c.id, c);
+ }
+ for (int i = mExits.size() - 1; i > 0; i--) {
+ mExits.remove(i);
+ }
+ for (Condition c : mConditions.values()) {
+ mExits.add(newExit(c.caption, "", "", c.id));
+ }
+ dispatchChanged();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index 2dba669..c94c65f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -135,8 +135,8 @@
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop);
- int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_min_height);
- int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_max_height);
+ int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
+ int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
mExpandHelper = new ExpandHelper(getContext(), this, minHeight, maxHeight);
mContentHolder = (ViewGroup) findViewById(R.id.content_holder);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 36d94a9..948ef90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -145,13 +145,13 @@
mSidePaddings = context.getResources()
.getDimensionPixelSize(R.dimen.notification_side_padding);
mCollapsedSize = context.getResources()
- .getDimensionPixelSize(R.dimen.notification_row_min_height);
+ .getDimensionPixelSize(R.dimen.notification_min_height);
mBottomStackPeekSize = context.getResources()
.getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
mEmptyMarginBottom = context.getResources().getDimensionPixelSize(
R.dimen.notification_stack_margin_bottom);
- // currently the padding is in the elements themself
- mPaddingBetweenElements = 0;
+ mPaddingBetweenElements = context.getResources()
+ .getDimensionPixelSize(R.dimen.notification_padding);
mStackScrollAlgorithm = new StackScrollAlgorithm(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index d9e7f66..2a6e4ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -62,11 +62,10 @@
}
private void initConstants(Context context) {
-
- // currently the padding is in the elements themself
- mPaddingBetweenElements = 0;
+ mPaddingBetweenElements = context.getResources()
+ .getDimensionPixelSize(R.dimen.notification_padding);
mCollapsedSize = context.getResources()
- .getDimensionPixelSize(R.dimen.notification_row_min_height);
+ .getDimensionPixelSize(R.dimen.notification_min_height);
mTopStackPeekSize = context.getResources()
.getDimensionPixelSize(R.dimen.top_stack_peek_amount);
mBottomStackPeekSize = context.getResources()
@@ -323,7 +322,8 @@
// the offset starting at the transitionPosition of the bottom stack
float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
- childViewState.yTranslation = transitioningPositionStart + offset - childHeight;
+ childViewState.yTranslation = transitioningPositionStart + offset - childHeight
+ - mPaddingBetweenElements;
// We want at least to be at the end of the top stack when collapsing
clampPositionToTopStackEnd(childViewState, childHeight);
@@ -339,7 +339,8 @@
if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
// We are visually entering the bottom stack
currentYPosition = transitioningPositionStart
- + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack);
+ + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack)
+ - mPaddingBetweenElements;
childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING;
} else {
// we are fully inside the stack
@@ -542,9 +543,13 @@
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight,
int oldBottom) {
- mFirstChildMaxHeight = getMaxAllowedChildHeight(
- mFirstChildWhileExpanding);
- mFirstChildWhileExpanding.removeOnLayoutChangeListener(this);
+ if (mFirstChildWhileExpanding != null) {
+ mFirstChildMaxHeight = getMaxAllowedChildHeight(
+ mFirstChildWhileExpanding);
+ } else {
+ mFirstChildMaxHeight = 0;
+ }
+ v.removeOnLayoutChangeListener(this);
}
});
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 9742e65..6e2e87e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -197,18 +197,12 @@
* @param clipHeight the desired clip height, the rest of the view will be clipped from the top
*/
private void updateChildClip(View child, int height, float clipHeight) {
- // The children currently have paddings inside themselfs because of the expansion
- // visualization. In order for the clipping to work correctly we have to set the correct
- // clip rect on the child.
- View container = child.findViewById(R.id.container);
- if (container != null) {
- int clipInset = (int) (height - clipHeight);
- mClipRect.set(0,
- clipInset,
- child.getWidth(),
- height);
- child.setClipBounds(mClipRect);
- }
+ int clipInset = (int) (height - clipHeight);
+ mClipRect.set(0,
+ clipInset,
+ child.getWidth(),
+ height);
+ child.setClipBounds(mClipRect);
}
/**
@@ -218,22 +212,15 @@
* @param height the currently applied height of the view
* @param outlineHeight the desired height of the outline, the outline ends on the bottom
*/
- private void updateChildOutline(View child,
- int height,
- float outlineHeight) {
- // The children currently have paddings inside themselfs because of the expansion
- // visualization. In order for the shadows to work correctly we have to set the correct
- // outline on the child.
- View container = child.findViewById(R.id.container);
- if (container != null) {
- int shadowInset = (int) (height - outlineHeight);
- getOutlineForSize(container.getLeft(),
- container.getTop() + shadowInset,
- container.getWidth(),
- container.getHeight() - shadowInset,
- mChildOutline);
- child.setOutline(mChildOutline);
- }
+ private void updateChildOutline(View child, int height,
+ float outlineHeight) {
+ int shadowInset = (int) (height - outlineHeight);
+ getOutlineForSize(child.getLeft(),
+ child.getTop() + shadowInset,
+ child.getWidth(),
+ child.getHeight() - shadowInset,
+ mChildOutline);
+ child.setOutline(mChildOutline);
}
private void getOutlineForSize(int leftInset, int topInset, int width, int height,
diff --git a/services/Android.mk b/services/Android.mk
index 165f456..5fcef64 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -25,7 +25,8 @@
backup \
devicepolicy \
print \
- usb
+ usb \
+ voiceinteraction
# The convention is to name each service module 'services.$(module_name)'
LOCAL_STATIC_JAVA_LIBRARIES := $(addprefix services.,$(services))
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 2f56e62..87b1d32 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -50,6 +50,7 @@
import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.AttributeSet;
+import android.util.MutableInt;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -86,8 +87,6 @@
import java.util.Map.Entry;
import java.util.Set;
-import libcore.util.MutableInt;
-
class AppWidgetServiceImpl {
private static final String KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 0b688b6..0082b1e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -74,6 +74,8 @@
import android.os.Environment.UserEnvironment;
import android.os.storage.IMountService;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -142,9 +144,6 @@
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-
public class BackupManagerService extends IBackupManager.Stub {
private static final String TAG = "BackupManagerService";
@@ -2471,7 +2470,7 @@
// operations any more during this pass).
Slog.w(TAG, "Unable to save widget state for " + pkgName);
try {
- Libcore.os.ftruncate(fd, filepos);
+ Os.ftruncate(fd, filepos);
} catch (ErrnoException ee) {
Slog.w(TAG, "Unable to roll back!");
}
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index fa803e2..fe97c71 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -421,11 +421,8 @@
}
/* This goes in response as msg.arg2 */
- int clientId = -1;
- int keyId = clientInfo.mClientIds.indexOfValue(id);
- if (keyId != -1) {
- clientId = clientInfo.mClientIds.keyAt(keyId);
- } else {
+ int clientId = clientInfo.getClientId(id);
+ if (clientId < 0) {
// This can happen because of race conditions. For example,
// SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
// and we may get in this situation.
@@ -904,5 +901,18 @@
mClientRequests.clear();
}
+ // mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
+ // return the corresponding listener id. mDnsClient id is also called a global id.
+ private int getClientId(final int globalId) {
+ // This doesn't use mClientIds.indexOfValue because indexOfValue uses == (not .equals)
+ // while also coercing the int primitives to Integer objects.
+ for (int i = 0, nSize = mClientIds.size(); i < nSize; i++) {
+ int mDnsId = mClientIds.valueAt(i);
+ if (globalId == mDnsId) {
+ return mClientIds.keyAt(i);
+ }
+ }
+ return -1;
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ed007e9..0c91907 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -28,19 +28,23 @@
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.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.ProcessCpuTracker;
@@ -56,6 +60,7 @@
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.firewall.IntentFirewall;
@@ -333,6 +338,9 @@
// How many bytes to write into the dropbox log before truncating
static final int DROPBOX_MAX_SIZE = 256 * 1024;
+ /** All system services */
+ SystemServiceManager mSystemServiceManager;
+
/** Run all ActivityStacks through this */
ActivityStackSupervisor mStackSupervisor;
@@ -399,7 +407,7 @@
/**
* List of intents that were used to start the most recent tasks.
*/
- private final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
+ final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
@@ -879,17 +887,23 @@
* Set while we are wanting to sleep, to prevent any
* activities from being started/resumed.
*/
- boolean mSleeping = false;
+ private boolean mSleeping = false;
+
+ /**
+ * Set while we are running a voice interaction. This overrides
+ * sleeping while it is active.
+ */
+ private boolean mRunningVoice = false;
/**
* State of external calls telling us if the device is asleep.
*/
- boolean mWentToSleep = false;
+ private boolean mWentToSleep = false;
/**
* State of external call telling us if the lock screen is shown.
*/
- boolean mLockScreenShown = false;
+ private boolean mLockScreenShown = false;
/**
* Set if we are shutting down the system, similar to sleeping.
@@ -1117,6 +1131,8 @@
static final int REQUEST_ALL_PSS_MSG = 39;
static final int START_PROFILES_MSG = 40;
static final int UPDATE_TIME = 41;
+ static final int SYSTEM_USER_START_MSG = 42;
+ static final int SYSTEM_USER_CURRENT_MSG = 43;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1696,15 +1712,15 @@
break;
}
case REPORT_USER_SWITCH_MSG: {
- dispatchUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ dispatchUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case CONTINUE_USER_SWITCH_MSG: {
- continueUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ continueUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case USER_SWITCH_TIMEOUT_MSG: {
- timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+ timeoutUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
}
case IMMERSIVE_MODE_LOCK_MSG: {
@@ -1751,6 +1767,14 @@
}
break;
}
+ case SYSTEM_USER_START_MSG: {
+ mSystemServiceManager.startUser(msg.arg1);
+ break;
+ }
+ case SYSTEM_USER_CURRENT_MSG: {
+ mSystemServiceManager.switchUser(msg.arg1);
+ break;
+ }
}
}
};
@@ -1813,6 +1837,82 @@
}
};
+ /**
+ * Monitor for package changes and update our internal state.
+ */
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ // Remove all tasks with activities in the specified package from the list of recent tasks
+ synchronized (ActivityManagerService.this) {
+ for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
+ TaskRecord tr = mRecentTasks.get(i);
+ ComponentName cn = tr.intent.getComponent();
+ if (cn != null && cn.getPackageName().equals(packageName)) {
+ // If the package name matches, remove the task and kill the process
+ removeTaskByIdLocked(tr.taskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ final PackageManager pm = mContext.getPackageManager();
+ final ArrayList<Pair<Intent, Integer>> recentTaskIntents =
+ new ArrayList<Pair<Intent, Integer>>();
+ final ArrayList<Integer> tasksToRemove = new ArrayList<Integer>();
+ // Copy the list of recent tasks so that we don't hold onto the lock on
+ // ActivityManagerService for long periods while checking if components exist.
+ synchronized (ActivityManagerService.this) {
+ for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
+ TaskRecord tr = mRecentTasks.get(i);
+ recentTaskIntents.add(new Pair<Intent, Integer>(tr.intent, tr.taskId));
+ }
+ }
+ // Check the recent tasks and filter out all tasks with components that no longer exist.
+ Intent tmpI = new Intent();
+ for (int i = recentTaskIntents.size() - 1; i >= 0; i--) {
+ Pair<Intent, Integer> p = recentTaskIntents.get(i);
+ ComponentName cn = p.first.getComponent();
+ if (cn != null && cn.getPackageName().equals(packageName)) {
+ try {
+ // Add the task to the list to remove if the component no longer exists
+ tmpI.setComponent(cn);
+ if (pm.queryIntentActivities(tmpI, PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
+ tasksToRemove.add(p.second);
+ }
+ } catch (Exception e) {}
+ }
+ }
+ // Prune all the tasks with removed components from the list of recent tasks
+ synchronized (ActivityManagerService.this) {
+ for (int i = tasksToRemove.size() - 1; i >= 0; i--) {
+ // Remove the task but don't kill the process (since other components in that
+ // package may still be running and in the background)
+ removeTaskByIdLocked(tasksToRemove.get(i), 0);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+ // Force stop the specified packages
+ if (packages != null) {
+ for (String pkg : packages) {
+ synchronized (ActivityManagerService.this) {
+ if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, 0,
+ "finished booting")) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ };
+
public void setSystemProcess() {
try {
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
@@ -2059,6 +2159,10 @@
Watchdog.getInstance().addThread(mHandler);
}
+ public void setSystemServiceManager(SystemServiceManager mgr) {
+ mSystemServiceManager = mgr;
+ }
+
private void start() {
mProcessCpuThread.start();
@@ -2254,6 +2358,11 @@
if (mFocusedActivity != r) {
if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r);
mFocusedActivity = r;
+ if (r.task != null && r.task.voiceInteractor != null) {
+ startRunningVoiceLocked();
+ } else {
+ finishRunningVoiceLocked();
+ }
mStackSupervisor.setFocusedStack(r);
if (r != null) {
mWindowManager.setFocusedApp(r.appToken, true);
@@ -2262,6 +2371,12 @@
}
}
+ final void clearFocusedActivity(ActivityRecord r) {
+ if (mFocusedActivity == r) {
+ mFocusedActivity = null;
+ }
+ }
+
@Override
public void setFocusedStack(int stackId) {
if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: stackId=" + stackId);
@@ -2990,7 +3105,7 @@
intent.setComponent(new ComponentName(
ri.activityInfo.packageName, ri.activityInfo.name));
mStackSupervisor.startActivityLocked(null, intent, null, ri.activityInfo,
- null, null, 0, 0, 0, null, 0, null, false, null, null);
+ null, null, null, null, 0, 0, 0, null, 0, null, false, null, null);
}
}
}
@@ -3121,7 +3236,7 @@
}
for (int i=0; i<N; i++) {
PendingActivityLaunch pal = mPendingActivityLaunches.get(i);
- mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.startFlags,
+ mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
doResume && i == (N-1), null);
}
mPendingActivityLaunches.clear();
@@ -3147,7 +3262,7 @@
false, true, "startActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+ null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
null, null, options, userId, null);
}
@@ -3162,7 +3277,7 @@
WaitResult res = new WaitResult();
// TODO: Switch to user app stacks here.
mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+ null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
res, null, options, UserHandle.getCallingUserId(), null);
return res;
}
@@ -3177,7 +3292,7 @@
false, true, "startActivityWithConfig", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
- resolvedType, resultTo, resultWho, requestCode, startFlags,
+ resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
null, null, null, config, options, userId, null);
return ret;
}
@@ -3215,6 +3330,31 @@
}
@Override
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) {
+ if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: startVoiceActivity() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (session == null || interactor == null) {
+ throw new NullPointerException("null session or interactor");
+ }
+ userId = handleIncomingUser(callingPid, callingUid, userId,
+ false, true, "startVoiceActivity", null);
+ // TODO: Switch to user app stacks here.
+ return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
+ resolvedType, session, interactor, null, null, 0, startFlags,
+ profileFile, profileFd, null, null, options, userId, null);
+ }
+
+ @Override
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) {
// Refuse possible leaked file descriptors
@@ -3307,7 +3447,7 @@
final long origId = Binder.clearCallingIdentity();
int res = mStackSupervisor.startActivityLocked(r.app.thread, intent,
- r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null,
+ r.resolvedType, aInfo, null, null, resultTo != null ? resultTo.appToken : null,
resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0,
options, false, null, null);
Binder.restoreCallingIdentity(origId);
@@ -3330,7 +3470,7 @@
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags,
+ null, null, resultTo, resultWho, requestCode, startFlags,
null, null, null, null, options, userId, container);
return ret;
}
@@ -3366,6 +3506,10 @@
if (N > 0 && mRecentTasks.get(0) == task) {
return;
}
+ // Another quick case: never add voice sessions.
+ if (task.voiceSession != null) {
+ return;
+ }
// Remove any existing entries that are the same kind of task.
final Intent intent = task.intent;
final boolean document = intent != null && intent.isDocument();
@@ -5200,26 +5344,8 @@
}
final void finishBooting() {
- IntentFilter pkgFilter = new IntentFilter();
- pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
- pkgFilter.addDataScheme("package");
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- if (pkgs != null) {
- for (String pkg : pkgs) {
- synchronized (ActivityManagerService.this) {
- if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, 0,
- "finished booting")) {
- setResultCode(Activity.RESULT_OK);
- return;
- }
- }
- }
- }
- }
- }, pkgFilter);
+ // Register receivers to handle package update events
+ mPackageMonitor.register(mContext, Looper.getMainLooper(), false);
synchronized (this) {
// Ensure that any processes we had put on hold are now started
@@ -8509,11 +8635,27 @@
return mSleeping || mShuttingDown;
}
+ public boolean isSleeping() {
+ return mSleeping;
+ }
+
void goingToSleep() {
synchronized(this) {
mWentToSleep = true;
updateEventDispatchingLocked();
+ goToSleepIfNeededLocked();
+ }
+ }
+ void finishRunningVoiceLocked() {
+ if (mRunningVoice) {
+ mRunningVoice = false;
+ goToSleepIfNeededLocked();
+ }
+ }
+
+ void goToSleepIfNeededLocked() {
+ if (mWentToSleep && !mRunningVoice) {
if (!mSleeping) {
mSleeping = true;
mStackSupervisor.goingToSleepLocked();
@@ -8576,7 +8718,7 @@
}
private void comeOutOfSleepIfNeededLocked() {
- if (!mWentToSleep && !mLockScreenShown) {
+ if ((!mWentToSleep && !mLockScreenShown) || mRunningVoice) {
if (mSleeping) {
mSleeping = false;
mStackSupervisor.comeOutOfSleepIfNeededLocked();
@@ -8592,6 +8734,13 @@
}
}
+ void startRunningVoiceLocked() {
+ if (!mRunningVoice) {
+ mRunningVoice = true;
+ comeOutOfSleepIfNeededLocked();
+ }
+ }
+
private void updateEventDispatchingLocked() {
mWindowManager.setEventDispatching(mBooted && !mWentToSleep && !mShuttingDown);
}
@@ -9298,7 +9447,7 @@
proc.notCachedSinceIdle = true;
proc.initialIdlePss = 0;
proc.nextPssTime = ProcessList.computeNextPssTime(proc.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
}
}
@@ -9597,6 +9746,8 @@
if (goingCallback != null) goingCallback.run();
+ mSystemServiceManager.startUser(mCurrentUserId);
+
synchronized (this) {
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
try {
@@ -9653,6 +9804,8 @@
}, 0, null, null,
android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+ } catch (Throwable t) {
+ Slog.wtf(TAG, "Failed sending first user broadcasts", t);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -11152,8 +11305,8 @@
pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep
+ " mLockScreenShown " + mLockScreenShown);
}
- if (mShuttingDown) {
- pw.println(" mShuttingDown=" + mShuttingDown);
+ if (mShuttingDown || mRunningVoice) {
+ pw.print(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice);
}
}
if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
@@ -15273,7 +15426,7 @@
if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
app.pssProcState = app.setProcState;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
mPendingPssProcesses.add(app);
}
}
@@ -15310,7 +15463,7 @@
}
}
return !processingBroadcasts
- && (mSleeping || mStackSupervisor.allResumedActivitiesIdle());
+ && (isSleeping() || mStackSupervisor.allResumedActivitiesIdle());
}
/**
@@ -15585,7 +15738,7 @@
app.setProcState)) {
app.lastStateTime = now;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
- mSleeping, now);
+ isSleeping(), now);
if (DEBUG_PSS) Slog.d(TAG, "Process state change from "
+ ProcessList.makeProcStateString(app.setProcState) + " to "
+ ProcessList.makeProcStateString(app.curProcState) + " next pss in "
@@ -15595,7 +15748,7 @@
&& now > (app.lastStateTime+ProcessList.PSS_MIN_TIME_FROM_STATE_CHANGE))) {
requestPssLocked(app, app.setProcState);
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false,
- mSleeping, now);
+ isSleeping(), now);
} else if (false && DEBUG_PSS) {
Slog.d(TAG, "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now));
}
@@ -15932,7 +16085,7 @@
}
mLastMemoryLevel = memFactor;
mLastNumProcesses = mLruProcesses.size();
- boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !mSleeping, now);
+ boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleeping(), now);
final int trackerMemFactor = mProcessStats.getMemFactorLocked();
if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {
if (mLowRamStartTime == 0) {
@@ -16475,7 +16628,15 @@
needStart = true;
}
+ if (uss.mState == UserStartedState.STATE_BOOTING) {
+ // Booting up a new user, need to tell system services about it.
+ // Note that this is on the same handler as scheduling of broadcasts,
+ // which is important because it needs to go first.
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId));
+ }
+
if (foreground) {
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId));
mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
@@ -16830,6 +16991,7 @@
}
uss.mState = UserStartedState.STATE_SHUTDOWN;
}
+ mSystemServiceManager.stopUser(userId);
broadcastIntentLocked(null, null, shutdownIntent,
null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, userId);
@@ -16879,7 +17041,12 @@
}
}
- mStackSupervisor.removeUserLocked(userId);
+ if (stopped) {
+ mSystemServiceManager.cleanupUser(userId);
+ synchronized (this) {
+ mStackSupervisor.removeUserLocked(userId);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 3e59def..7a44473 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -633,7 +633,7 @@
// case we will deliver it if this is the current top activity on its
// stack.
boolean unsent = true;
- if ((state == ActivityState.RESUMED || (service.mSleeping
+ if ((state == ActivityState.RESUMED || (service.isSleeping()
&& task.stack.topRunningActivityLocked(null) == this))
&& app != null && app.thread != null) {
try {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9f57728..6769c9c 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -38,6 +38,8 @@
import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.CONTAINER_STATE_HAS_SURFACE;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ItemMatcher;
@@ -501,6 +503,11 @@
if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
+ if (task.voiceSession != null) {
+ // We never match voice sessions; those always run independently.
+ if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": voice session");
+ continue;
+ }
if (task.userId != userId) {
// Looking for a different task.
if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user");
@@ -735,7 +742,9 @@
int w = mThumbnailWidth;
int h = mThumbnailHeight;
if (w < 0) {
- if (SystemProperties.getBoolean("persist.recents.use_alternate", false)) {
+ Configuration config = res.getConfiguration();
+ boolean useAlternateRecents = (config.smallestScreenWidthDp < 600);
+ if (useAlternateRecents) {
mThumbnailWidth = w =
res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_width);
mThumbnailHeight = h =
@@ -1501,7 +1510,7 @@
// If the most recent activity was noHistory but was only stopped rather
// than stopped+finished because the device went to sleep, we need to make
// sure to finish it as we're making a new activity topmost.
- if (mService.mSleeping && mLastNoHistoryActivity != null &&
+ if (mService.isSleeping() && mLastNoHistoryActivity != null &&
!mLastNoHistoryActivity.finishing) {
if (DEBUG_STATES) Slog.d(TAG, "no-history finish of " + mLastNoHistoryActivity +
" on new resume");
@@ -2032,7 +2041,7 @@
+ " out to bottom task " + bottom.task);
} else {
targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
- null, false);
+ null, null, null, false);
newThumbHolder = targetTask;
targetTask.affinityIntent = target.intent;
if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
@@ -2320,7 +2329,10 @@
mStackSupervisor.moveHomeToTop();
}
}
- mService.setFocusedActivityLocked(mStackSupervisor.topRunningActivityLocked());
+ ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
+ if (top != null) {
+ mService.setFocusedActivityLocked(top);
+ }
}
}
@@ -2329,7 +2341,7 @@
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
|| (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {
if (!r.finishing) {
- if (!mService.mSleeping) {
+ if (!mService.isSleeping()) {
if (DEBUG_STATES) {
Slog.d(TAG, "no-history finish of " + r);
}
@@ -2708,7 +2720,7 @@
ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
destIntent.getComponent(), 0, srec.userId);
int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent,
- null, aInfo, parent.appToken, null,
+ null, aInfo, null, null, parent.appToken, null,
0, -1, parent.launchedFromUid, parent.launchedFromPackage,
0, null, true, null, null);
foundParentInTask = res == ActivityManager.START_SUCCESS;
@@ -2737,9 +2749,7 @@
if (mPausingActivity == r) {
mPausingActivity = null;
}
- if (mService.mFocusedActivity == r) {
- mService.mFocusedActivity = null;
- }
+ mService.clearFocusedActivity(r);
r.configDestroy = false;
r.frozenBeforeDestroy = false;
@@ -3721,6 +3731,11 @@
mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true;
}
mTaskHistory.remove(task);
+ if (task.voiceInteractor != null) {
+ // This task was a voice interaction, so it should not remain on the
+ // recent tasks list.
+ mService.mRecentTasks.remove(task);
+ }
if (mTaskHistory.isEmpty()) {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this);
@@ -3734,8 +3749,10 @@
}
}
- TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, boolean toTop) {
- TaskRecord task = new TaskRecord(taskId, info, intent);
+ TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ boolean toTop) {
+ TaskRecord task = new TaskRecord(taskId, info, intent, voiceSession, voiceInteractor);
addTask(task, toTop);
return task;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e044d3f..3770a07 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -17,7 +17,6 @@
package com.android.server.am;
import static android.Manifest.permission.START_ANY_ACTIVITY;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -77,6 +76,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -87,6 +87,7 @@
import android.view.InputEvent;
import android.view.Surface;
import com.android.internal.app.HeavyWeightSwitcherActivity;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.TransferPipe;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
@@ -687,13 +688,14 @@
void startHomeActivity(Intent intent, ActivityInfo aInfo) {
moveHomeToTop();
- startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0,
+ startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0,
null, false, null, null);
}
final int startActivityMayWait(IApplicationThread caller, int callingUid,
- String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, int startFlags, String profileFile,
+ String callingPackage, Intent intent, String resolvedType,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
Bundle options, int userId, IActivityContainer iContainer) {
// Refuse possible leaked file descriptors
@@ -802,7 +804,8 @@
}
}
- int res = startActivityLocked(caller, intent, resolvedType, aInfo, resultTo, resultWho,
+ int res = startActivityLocked(caller, intent, resolvedType, aInfo,
+ voiceSession, voiceInteractor, resultTo, resultWho,
requestCode, callingPid, callingUid, callingPackage, startFlags, options,
componentSpecified, null, container);
@@ -918,7 +921,7 @@
theseOptions = null;
}
int res = startActivityLocked(caller, intent, resolvedTypes[i],
- aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage,
+ aInfo, null, null, resultTo, null, -1, callingPid, callingUid, callingPackage,
0, theseOptions, componentSpecified, outActivity, null);
if (res < 0) {
return res;
@@ -1034,8 +1037,8 @@
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
new Configuration(mService.mConfiguration), r.compat,
- app.repProcState, r.icicle, results, newIntents, !andResume,
- mService.isNextTransitionForward(), profileFile, profileFd,
+ r.task.voiceInteractor, app.repProcState, r.icicle, results, newIntents,
+ !andResume, mService.isNextTransitionForward(), profileFile, profileFd,
profileAutoStop, options);
if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
@@ -1143,8 +1146,9 @@
}
final int startActivityLocked(IApplicationThread caller,
- Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo,
- String resultWho, int requestCode,
+ Intent intent, String resolvedType, ActivityInfo aInfo,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ IBinder resultTo, String resultWho, int requestCode,
int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options,
boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container) {
int err = ActivityManager.START_SUCCESS;
@@ -1187,7 +1191,7 @@
}
ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
- int launchFlags = intent.getFlags();
+ final int launchFlags = intent.getFlags();
if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0
&& sourceRecord != null) {
@@ -1232,6 +1236,38 @@
err = ActivityManager.START_CLASS_NOT_FOUND;
}
+ if (err == ActivityManager.START_SUCCESS && sourceRecord != null
+ && sourceRecord.task.voiceSession != null) {
+ // If this activity is being launched as part of a voice session, we need
+ // to ensure that it is safe to do so. If the upcoming activity will also
+ // be part of the voice session, we can only launch it if it has explicitly
+ // said it supports the VOICE category, or it is a part of the calling app.
+ if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
+ && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
+ try {
+ if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+ intent, resolvedType)) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ } catch (RemoteException e) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ }
+ }
+
+ if (err == ActivityManager.START_SUCCESS && voiceSession != null) {
+ // If the caller is starting a new voice session, just make sure the target
+ // is actually allowing it to run this way.
+ try {
+ if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+ intent, resolvedType)) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ } catch (RemoteException e) {
+ err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+ }
+ }
+
if (err != ActivityManager.START_SUCCESS) {
if (resultRecord != null) {
resultStack.sendActivityResultLocked(-1,
@@ -1305,8 +1341,8 @@
}
final ActivityStack stack = getFocusedStack();
- if (stack.mResumedActivity == null
- || stack.mResumedActivity.info.applicationInfo.uid != callingUid) {
+ if (voiceSession == null && (stack.mResumedActivity == null
+ || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) {
PendingActivityLaunch pal =
new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
@@ -1330,7 +1366,8 @@
mService.doPendingActivityLaunchesLocked(false);
- err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options);
+ err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
+ startFlags, true, options);
if (allPausedActivitiesComplete()) {
// If someone asked to have the keyguard dismissed on the next
@@ -1410,8 +1447,9 @@
}
final int startActivityUncheckedLocked(ActivityRecord r,
- ActivityRecord sourceRecord, int startFlags, boolean doResume,
- Bundle options) {
+ ActivityRecord sourceRecord,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
+ boolean doResume, Bundle options) {
final Intent intent = r.intent;
final int callingUid = r.launchedFromUid;
@@ -1755,7 +1793,7 @@
r.setTask(targetStack.createTaskRecord(getNextTaskId(),
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
- true), null, true);
+ voiceSession, voiceInteractor, true), null, true);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
} else {
@@ -1833,7 +1871,7 @@
targetStack.moveToFront();
ActivityRecord prev = targetStack.topActivity();
r.setTask(prev != null ? prev.task
- : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),
+ : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, null, null, true),
null, true);
mWindowManager.moveTaskToTop(r.task.taskId);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
@@ -3104,7 +3142,7 @@
&& "content".equals(intent.getData().getScheme())) {
mimeType = mService.getProviderMimeType(intent.getData(), userId);
}
- return startActivityMayWait(null, -1, null, intent, mimeType, null, null, 0, 0, null,
+ return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, 0, 0, null,
null, null, null, null, userId, this);
}
@@ -3120,6 +3158,40 @@
null, 0, FORCE_NEW_TASK_FLAGS, FORCE_NEW_TASK_FLAGS, null, this);
}
+ private void checkEmbeddedAllowedInner(Intent intent, String resolvedType) {
+ int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), mCurrentUser, false, true, "ActivityContainer", null);
+ if (resolvedType == null) {
+ resolvedType = intent.getType();
+ if (resolvedType == null && intent.getData() != null
+ && "content".equals(intent.getData().getScheme())) {
+ resolvedType = mService.getProviderMimeType(intent.getData(), userId);
+ }
+ }
+ ActivityInfo aInfo = resolveActivity(intent, resolvedType, 0, null, null, userId);
+ if ((aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
+ throw new SecurityException(
+ "Attempt to embed activity that has not set allowEmbedded=\"true\"");
+ }
+ }
+
+ /** Throw a SecurityException if allowEmbedded is not true */
+ @Override
+ public final void checkEmbeddedAllowed(Intent intent) {
+ checkEmbeddedAllowedInner(intent, null);
+ }
+
+ /** Throw a SecurityException if allowEmbedded is not true */
+ @Override
+ public final void checkEmbeddedAllowedIntentSender(IIntentSender intentSender) {
+ if (!(intentSender instanceof PendingIntentRecord)) {
+ throw new IllegalArgumentException("Bad PendingIntent object");
+ }
+ PendingIntentRecord pendingIntent = (PendingIntentRecord) intentSender;
+ checkEmbeddedAllowedInner(pendingIntent.key.requestIntent,
+ pendingIntent.key.requestResolvedType);
+ }
+
@Override
public IBinder asBinder() {
return this;
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index 443218c..d42d415 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -17,14 +17,13 @@
package com.android.server.am;
import android.app.ApplicationErrorReport.CrashInfo;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.system.StructUcred;
import android.util.Slog;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-import libcore.io.StructTimeval;
-import libcore.io.StructUcred;
-
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -77,7 +76,7 @@
try {
CrashInfo ci = new CrashInfo();
ci.exceptionClassName = "Native crash";
- ci.exceptionMessage = Libcore.os.strsignal(mSignal);
+ ci.exceptionMessage = Os.strsignal(mSignal);
ci.throwFileName = "unknown";
ci.throwClassName = "unknown";
ci.throwMethodName = "unknown";
@@ -117,22 +116,22 @@
}
try {
- FileDescriptor serverFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0);
+ FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH);
- Libcore.os.bind(serverFd, sockAddr, 0);
- Libcore.os.listen(serverFd, 1);
+ Os.bind(serverFd, sockAddr, 0);
+ Os.listen(serverFd, 1);
while (true) {
InetSocketAddress peer = new InetSocketAddress();
FileDescriptor peerFd = null;
try {
if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
- peerFd = Libcore.os.accept(serverFd, peer);
+ peerFd = Os.accept(serverFd, peer);
if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
if (peerFd != null) {
// Only the superuser is allowed to talk to us over this socket
StructUcred credentials =
- Libcore.os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
+ Os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
if (credentials.uid == 0) {
// the reporting thread may take responsibility for
// acking the debugger; make sure we play along.
@@ -146,7 +145,7 @@
// byte written is irrelevant.
if (peerFd != null) {
try {
- Libcore.os.write(peerFd, ackSignal, 0, 1);
+ Os.write(peerFd, ackSignal, 0, 1);
} catch (Exception e) {
/* we don't care about failures here */
if (MORE_DEBUG) {
@@ -154,7 +153,7 @@
}
}
try {
- Libcore.os.close(peerFd);
+ Os.close(peerFd);
} catch (ErrnoException e) {
if (MORE_DEBUG) {
Slog.d(TAG, "Exception closing socket: " + e.getMessage());
@@ -182,7 +181,7 @@
throws ErrnoException, InterruptedIOException {
int totalRead = 0;
while (numBytes > 0) {
- int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes);
+ int n = Os.read(fd, buffer, offset + totalRead, numBytes);
if (n <= 0) {
if (DEBUG) {
Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
@@ -203,8 +202,8 @@
try {
StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
- Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
- Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
+ Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
+ Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
// first, the pid and signal number
int headerBytes = readExactly(fd, buf, 0, 8);
@@ -238,7 +237,7 @@
int bytes;
do {
// get some data
- bytes = Libcore.os.read(fd, buf, 0, buf.length);
+ bytes = Os.read(fd, buf, 0, buf.length);
if (bytes > 0) {
if (MORE_DEBUG) {
String s = new String(buf, 0, bytes, "UTF-8");
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 80a219dd..68da54d 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -28,7 +28,9 @@
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
import android.util.Slog;
+import com.android.internal.app.IVoiceInteractor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -36,6 +38,8 @@
final class TaskRecord extends ThumbnailHolder {
final int taskId; // Unique identifier for this task.
final String affinity; // The affinity name for this task, or null.
+ final IVoiceInteractionSession voiceSession; // Voice interaction session driving task
+ final IVoiceInteractor voiceInteractor; // Associated interactor to provide to app
Intent intent; // The original intent that started the task.
Intent affinityIntent; // Intent of affinity-moved activity that started this task.
ComponentName origActivity; // The non-alias activity component of the intent.
@@ -64,9 +68,12 @@
* Display.DEFAULT_DISPLAY. */
boolean mOnTopOfHome = false;
- TaskRecord(int _taskId, ActivityInfo info, Intent _intent) {
+ TaskRecord(int _taskId, ActivityInfo info, Intent _intent,
+ IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
taskId = _taskId;
affinity = info.taskAffinity;
+ voiceSession = _voiceSession;
+ voiceInteractor = _voiceInteractor;
setIntent(_intent, info);
}
@@ -473,6 +480,12 @@
if (affinity != null) {
pw.print(prefix); pw.print("affinity="); pw.println(affinity);
}
+ if (voiceSession != null || voiceInteractor != null) {
+ pw.print(prefix); pw.print("VOICE: session=0x");
+ pw.print(Integer.toHexString(System.identityHashCode(voiceSession)));
+ pw.print(" interactor=0x");
+ pw.println(Integer.toHexString(System.identityHashCode(voiceInteractor)));
+ }
if (intent != null) {
StringBuilder sb = new StringBuilder(128);
sb.append(prefix); sb.append("intent={");
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
new file mode 100644
index 0000000..5f07108
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
+ * and pass it to CEC HAL so that it sends message to other device. For incoming
+ * message it translates the message and delegates it to proper module.
+ *
+ * <p>It can be created only by {@link HdmiCecController#create}
+ *
+ * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
+ */
+class HdmiCecController {
+ private static final String TAG = "HdmiCecController";
+
+ // Handler instance to process synchronous I/O (mainly send) message.
+ private Handler mIoHandler;
+
+ // Handler instance to process various messages coming from other CEC
+ // device or issued by internal state change.
+ private Handler mMessageHandler;
+
+ // Stores the pointer to the native implementation of the service that
+ // interacts with HAL.
+ private long mNativePtr;
+
+ // Private constructor. Use HdmiCecController.create().
+ private HdmiCecController() {
+ }
+
+ /**
+ * A factory method to get {@link HdmiCecController}. If it fails to initialize
+ * inner device or has no device it will return {@code null}.
+ *
+ * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
+ *
+ * @param ioLooper a Looper instance to handle IO (mainly send message) operation.
+ * @param messageHandler a message handler that processes a message coming from other
+ * CEC compatible device or callback of internal state change.
+ * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
+ * returns {@code null}.
+ */
+ static HdmiCecController create(Looper ioLooper, Handler messageHandler) {
+ HdmiCecController handler = new HdmiCecController();
+ long nativePtr = nativeInit(handler);
+ if (nativePtr == 0L) {
+ handler = null;
+ return null;
+ }
+
+ handler.init(ioLooper, messageHandler, nativePtr);
+ return handler;
+ }
+
+ private void init(Looper ioLooper, Handler messageHandler, long nativePtr) {
+ mIoHandler = new Handler(ioLooper) {
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO: Call native sendMessage.
+ }
+ };
+
+ mMessageHandler = messageHandler;
+ mNativePtr = nativePtr;
+ }
+
+ /**
+ * Called by native when an HDMI-CEC message arrived.
+ */
+ private void handleMessage(int srcAddress, int dstAddres, int opcode, byte[] params) {
+ // TODO: Translate message and delegate it to main message handler.
+ }
+
+ private static native long nativeInit(HdmiCecController handler);
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
new file mode 100644
index 0000000..56c5b49
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+/**
+ * Provides a service for sending and processing HDMI control messages,
+ * HDMI-CEC and MHL control command, and providing the information on both standard.
+ */
+public final class HdmiControlService extends SystemService {
+ private static final String TAG = "HdmiControlService";
+
+ // A thread to handle synchronous IO of CEC and MHL control service.
+ // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
+ // and sparse call it shares a thread to handle IO operations.
+ private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
+
+ // Main handler class to handle incoming message from each controller.
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO: Add handler for each message type.
+ }
+ };
+
+ @Nullable
+ private HdmiCecController mCecController;
+
+ @Nullable
+ private HdmiMhlController mMhlController;
+
+ public HdmiControlService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mCecController = HdmiCecController.create(mIoThread.getLooper(), mHandler);
+ if (mCecController == null) {
+ Slog.i(TAG, "Device does not support HDMI-CEC.");
+ }
+
+ mMhlController = HdmiMhlController.create(mIoThread.getLooper(), mHandler);
+ if (mMhlController == null) {
+ Slog.i(TAG, "Device does not support MHL-control.");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
index fab84a8..79f192d 100644
--- a/services/core/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -60,6 +60,10 @@
private static final int FLP_RESULT_ID_UNKNOWN = -5;
private static final int FLP_RESULT_INVALID_GEOFENCE_TRANSITION = -6;
+ // FlpHal monitor status codes, they must be equal to the ones in fused_location.h
+ private static final int FLP_GEOFENCE_MONITOR_STATUS_UNAVAILABLE = 1<<0;
+ private static final int FLP_GEOFENCE_MONITOR_STATUS_AVAILABLE = 1<<1;
+
public static FlpHardwareProvider getInstance(Context context) {
if (sSingletonInstance == null) {
sSingletonInstance = new FlpHardwareProvider(context);
@@ -141,6 +145,8 @@
int transition,
long timestamp,
int sourcesUsed) {
+ // the transition Id does not require translation because the values in fused_location.h
+ // and GeofenceHardware are in sync
getGeofenceHardwareSink().reportGeofenceTransition(
geofenceId,
updateLocationInformation(location),
@@ -157,9 +163,23 @@
updatedLocation = updateLocationInformation(location);
}
+ int monitorStatus;
+ switch (status) {
+ case FLP_GEOFENCE_MONITOR_STATUS_UNAVAILABLE:
+ monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
+ break;
+ case FLP_GEOFENCE_MONITOR_STATUS_AVAILABLE:
+ monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE;
+ break;
+ default:
+ Log.e(TAG, "Invalid FlpHal Geofence monitor status: " + status);
+ monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
+ break;
+ }
+
getGeofenceHardwareSink().reportGeofenceMonitorStatus(
GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
- status,
+ monitorStatus,
updatedLocation,
source);
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
new file mode 100644
index 0000000..29af433
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -0,0 +1,257 @@
+/**
+ * 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.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.service.notification.Condition;
+import android.service.notification.ConditionProviderService;
+import android.service.notification.IConditionListener;
+import android.service.notification.IConditionProvider;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import libcore.util.Objects;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+public class ConditionProviders extends ManagedServices {
+
+ private final ZenModeHelper mZenModeHelper;
+ private final ArrayMap<IBinder, IConditionListener> mListeners
+ = new ArrayMap<IBinder, IConditionListener>();
+ private final ArrayMap<Uri, ManagedServiceInfo> mConditions
+ = new ArrayMap<Uri, ManagedServiceInfo>();
+
+ private Uri mCurrentConditionId;
+
+ public ConditionProviders(Context context, Handler handler,
+ UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
+ super(context, handler, new Object(), userProfiles);
+ mZenModeHelper = zenModeHelper;
+ mZenModeHelper.addCallback(new ZenModeHelperCallback());
+ }
+
+ @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
+ public void dump(PrintWriter pw) {
+ super.dump(pw);
+ synchronized(mMutex) {
+ pw.print(" mCurrentConditionId="); pw.println(mCurrentConditionId);
+ pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
+ for (int i = 0; i < mListeners.size(); i++) {
+ pw.print(" "); pw.println(mListeners.keyAt(i));
+ }
+ pw.print(" mConditions("); pw.print(mConditions.size()); pw.println("):");
+ for (int i = 0; i < mConditions.size(); i++) {
+ pw.print(" "); pw.print(mConditions.keyAt(i));
+ final ManagedServiceInfo info = mConditions.valueAt(i);
+ pw.print(" -> "); pw.print(info.component);
+ if (!mServices.contains(info)) {
+ pw.print(" (orphan)");
+ }
+ pw.println();
+ }
+ }
+ }
+
+ @Override
+ protected IInterface asInterface(IBinder binder) {
+ return IConditionProvider.Stub.asInterface(binder);
+ }
+
+ @Override
+ protected void onServiceAdded(IInterface service) {
+ Slog.d(TAG, "onServiceAdded " + service);
+ final IConditionProvider provider = (IConditionProvider) service;
+ try {
+ provider.onConnected();
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+
+ @Override
+ protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
+ if (removed == null) return;
+ for (int i = mConditions.size() - 1; i >= 0; i--) {
+ if (removed.equals(mConditions.valueAt(i))) {
+ mConditions.removeAt(i);
+ }
+ }
+ }
+
+ public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
+ synchronized(mMutex) {
+ return checkServiceTokenLocked(provider);
+ }
+ }
+
+ public void requestZenModeConditions(IConditionListener callback, boolean requested) {
+ synchronized(mMutex) {
+ if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
+ + " requested=" + requested);
+ if (callback == null) return;
+ if (requested) {
+ mListeners.put(callback.asBinder(), callback);
+ requestConditionsLocked(Condition.FLAG_RELEVANT_NOW);
+ } else {
+ mListeners.remove(callback.asBinder());
+ if (mListeners.isEmpty()) {
+ requestConditionsLocked(0);
+ }
+ }
+ }
+ }
+
+ public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
+ synchronized(mMutex) {
+ if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
+ + (conditions == null ? null : Arrays.asList(conditions)));
+ if (conditions == null || conditions.length == 0) return;
+ final int N = conditions.length;
+ boolean valid = true;
+ for (int i = 0; i < N; i++) {
+ final Uri id = conditions[i].id;
+ if (!Condition.isValidId(id, pkg)) {
+ Slog.w(TAG, "Ignoring conditions from " + pkg + " for invalid id: " + id);
+ valid = false;
+ }
+ }
+ if (!valid) return;
+
+ for (int i = 0; i < N; i++) {
+ mConditions.put(conditions[i].id, info);
+ }
+ for (IConditionListener listener : mListeners.values()) {
+ try {
+ listener.onConditionsReceived(conditions);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error sending conditions to listener " + listener, e);
+ }
+ }
+ if (mCurrentConditionId != null) {
+ for (int i = 0; i < N; i++) {
+ final Condition c = conditions[i];
+ if (!c.id.equals(mCurrentConditionId)) continue;
+ if (c.state == Condition.STATE_TRUE || c.state == Condition.STATE_ERROR) {
+ triggerExitLocked(c.state == Condition.STATE_ERROR);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ private void triggerExitLocked(boolean error) {
+ if (error) {
+ Slog.w(TAG, "Zen mode exit condition failed");
+ } else if (DEBUG) {
+ Slog.d(TAG, "Zen mode exit condition triggered");
+ }
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
+ unsubscribeLocked(mCurrentConditionId);
+ mCurrentConditionId = null;
+ }
+
+ public void setZenModeCondition(Uri conditionId) {
+ synchronized(mMutex) {
+ if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
+ if (Objects.equal(mCurrentConditionId, conditionId)) return;
+
+ if (mCurrentConditionId != null) {
+ unsubscribeLocked(mCurrentConditionId);
+ }
+ if (conditionId != null) {
+ final ManagedServiceInfo info = mConditions.get(conditionId);
+ final IConditionProvider provider = provider(info);
+ if (provider == null) return;
+ try {
+ provider.onSubscribe(conditionId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error subscribing to " + conditionId
+ + " from " + info.component, e);
+ }
+ }
+ mCurrentConditionId = conditionId;
+ }
+ }
+
+ private void unsubscribeLocked(Uri conditionId) {
+ final ManagedServiceInfo info = mConditions.get(mCurrentConditionId);
+ final IConditionProvider provider = provider(info);
+ if (provider == null) return;
+ try {
+ provider.onUnsubscribe(conditionId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error unsubscribing to " + conditionId + " from " + info.component, e);
+ }
+ }
+
+ private static IConditionProvider provider(ManagedServiceInfo info) {
+ return info == null ? null : (IConditionProvider) info.service;
+ }
+
+ private void requestConditionsLocked(int flags) {
+ for (ManagedServiceInfo info : mServices) {
+ final IConditionProvider provider = provider(info);
+ if (provider == null) continue;
+ try {
+ provider.onRequestConditions(flags);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error requesting conditions from " + info.component, e);
+ }
+ }
+ }
+
+ private class ZenModeHelperCallback extends ZenModeHelper.Callback {
+ @Override
+ void onZenModeChanged() {
+ final int mode = mZenModeHelper.getZenMode();
+ if (mode == Global.ZEN_MODE_OFF) {
+ synchronized (mMutex) {
+ if (mCurrentConditionId != null) {
+ if (DEBUG) Slog.d(TAG, "Zen mode off, forcing unsubscribe from "
+ + mCurrentConditionId);
+ unsubscribeLocked(mCurrentConditionId);
+ mCurrentConditionId = null;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
new file mode 100644
index 0000000..0621f58
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -0,0 +1,621 @@
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Manages the lifecycle of application-provided services bound by system server.
+ *
+ * Services managed by this helper must have:
+ * - An associated system settings value with a list of enabled component names.
+ * - A well-known action for services to use in their intent-filter.
+ * - A system permission for services to require in order to ensure system has exclusive binding.
+ * - A settings page for user configuration of enabled services, and associated intent action.
+ * - A remote interface definition (aidl) provided by the service used for communication.
+ */
+abstract public class ManagedServices {
+ protected final String TAG = getClass().getSimpleName();
+ protected static final boolean DEBUG = true;
+
+ private static final String ENABLED_SERVICES_SEPARATOR = ":";
+
+ private final Context mContext;
+ protected final Object mMutex;
+ private final UserProfiles mUserProfiles;
+ private final SettingsObserver mSettingsObserver;
+ private final Config mConfig;
+
+ // contains connections to all connected services, including app services
+ // and system services
+ protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();
+ // things that will be put into mServices as soon as they're ready
+ private final ArrayList<String> mServicesBinding = new ArrayList<String>();
+ // lists the component names of all enabled (and therefore connected)
+ // app services for current profiles.
+ private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles
+ = new ArraySet<ComponentName>();
+ // Just the packages from mEnabledServicesForCurrentProfiles
+ private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>();
+
+ public ManagedServices(Context context, Handler handler, Object mutex,
+ UserProfiles userProfiles) {
+ mContext = context;
+ mMutex = mutex;
+ mUserProfiles = userProfiles;
+ mConfig = getConfig();
+ mSettingsObserver = new SettingsObserver(handler);
+ }
+
+ abstract protected Config getConfig();
+
+ private String getCaption() {
+ return mConfig.caption;
+ }
+
+ abstract protected IInterface asInterface(IBinder binder);
+
+ abstract protected void onServiceAdded(IInterface service);
+
+ protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
+
+ private ManagedServiceInfo newServiceInfo(IInterface service,
+ ComponentName component, int userid, boolean isSystem, ServiceConnection connection,
+ int targetSdkVersion) {
+ return new ManagedServiceInfo(service, component, userid, isSystem, connection,
+ targetSdkVersion);
+ }
+
+ public void onBootPhaseAppsCanStart() {
+ mSettingsObserver.observe();
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
+ + ") enabled for current profiles:");
+ for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+ pw.println(" " + cmpt);
+ }
+
+ pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
+ for (ManagedServiceInfo info : mServices) {
+ pw.println(" " + info.component
+ + " (user " + info.userid + "): " + info.service
+ + (info.isSystem?" SYSTEM":""));
+ }
+ }
+
+ public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
+ if (DEBUG) Slog.d(TAG, "onPackagesChanged queryReplace=" + queryReplace
+ + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
+ + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
+ boolean anyServicesInvolved = false;
+ if (pkgList != null && (pkgList.length > 0)) {
+ for (String pkgName : pkgList) {
+ if (mEnabledServicesPackageNames.contains(pkgName)) {
+ anyServicesInvolved = true;
+ }
+ }
+ }
+
+ if (anyServicesInvolved) {
+ // if we're not replacing a package, clean up orphaned bits
+ if (!queryReplace) {
+ disableNonexistentServices();
+ }
+ // make sure we're still bound to any of our services who may have just upgraded
+ rebindServices();
+ }
+ }
+
+ public ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
+ checkNotNull(service);
+ final IBinder token = service.asBinder();
+ final int N = mServices.size();
+ for (int i=0; i<N; i++) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (info.service.asBinder() == token) return info;
+ }
+ throw new SecurityException("Disallowed call from unknown " + getCaption() + ": "
+ + service);
+ }
+
+ public void unregisterService(IInterface service, int userid) {
+ checkNotNull(service);
+ // no need to check permissions; if your service binder is in the list,
+ // that's proof that you had permission to add it in the first place
+ unregisterServiceImpl(service, userid);
+ }
+
+ public void registerService(IInterface service, ComponentName component, int userid) {
+ checkNotNull(service);
+ registerServiceImpl(service, component, userid);
+ }
+
+ /**
+ * Remove access for any services that no longer exist.
+ */
+ private void disableNonexistentServices() {
+ int[] userIds = mUserProfiles.getCurrentProfileIds();
+ final int N = userIds.length;
+ for (int i = 0 ; i < N; ++i) {
+ disableNonexistentServices(userIds[i]);
+ }
+ }
+
+ private void disableNonexistentServices(int userId) {
+ String flatIn = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ userId);
+ if (!TextUtils.isEmpty(flatIn)) {
+ if (DEBUG) Slog.v(TAG, "flat before: " + flatIn);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
+ new Intent(mConfig.serviceInterface),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ userId);
+ if (DEBUG) Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
+ Set<ComponentName> installed = new ArraySet<ComponentName>();
+ for (int i = 0, count = installedServices.size(); i < count; i++) {
+ ResolveInfo resolveInfo = installedServices.get(i);
+ ServiceInfo info = resolveInfo.serviceInfo;
+
+ if (!mConfig.bindPermission.equals(info.permission)) {
+ Slog.w(TAG, "Skipping " + getCaption() + " service "
+ + info.packageName + "/" + info.name
+ + ": it does not require the permission "
+ + mConfig.bindPermission);
+ continue;
+ }
+ installed.add(new ComponentName(info.packageName, info.name));
+ }
+
+ String flatOut = "";
+ if (!installed.isEmpty()) {
+ String[] enabled = flatIn.split(ENABLED_SERVICES_SEPARATOR);
+ ArrayList<String> remaining = new ArrayList<String>(enabled.length);
+ for (int i = 0; i < enabled.length; i++) {
+ ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
+ if (installed.contains(enabledComponent)) {
+ remaining.add(enabled[i]);
+ }
+ }
+ flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining);
+ }
+ if (DEBUG) Slog.v(TAG, "flat after: " + flatOut);
+ if (!flatIn.equals(flatOut)) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ flatOut, userId);
+ }
+ }
+ }
+
+ /**
+ * Called whenever packages change, the user switches, or the secure setting
+ * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
+ */
+ private void rebindServices() {
+ if (DEBUG) Slog.d(TAG, "rebindServices");
+ final int[] userIds = mUserProfiles.getCurrentProfileIds();
+ final int nUserIds = userIds.length;
+
+ final SparseArray<String> flat = new SparseArray<String>();
+
+ for (int i = 0; i < nUserIds; ++i) {
+ flat.put(userIds[i], Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ mConfig.secureSettingName,
+ userIds[i]));
+ }
+
+ ManagedServiceInfo[] toRemove = new ManagedServiceInfo[mServices.size()];
+ final SparseArray<ArrayList<ComponentName>> toAdd
+ = new SparseArray<ArrayList<ComponentName>>();
+
+ synchronized (mMutex) {
+ // unbind and remove all existing services
+ toRemove = mServices.toArray(toRemove);
+
+ final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>();
+ final ArraySet<String> newPackages = new ArraySet<String>();
+
+ for (int i = 0; i < nUserIds; ++i) {
+ final ArrayList<ComponentName> add = new ArrayList<ComponentName>();
+ toAdd.put(userIds[i], add);
+
+ // decode the list of components
+ String toDecode = flat.get(userIds[i]);
+ if (toDecode != null) {
+ String[] components = toDecode.split(ENABLED_SERVICES_SEPARATOR);
+ for (int j = 0; j < components.length; j++) {
+ final ComponentName component
+ = ComponentName.unflattenFromString(components[j]);
+ if (component != null) {
+ newEnabled.add(component);
+ add.add(component);
+ newPackages.add(component.getPackageName());
+ }
+ }
+
+ }
+ }
+ mEnabledServicesForCurrentProfiles = newEnabled;
+ mEnabledServicesPackageNames = newPackages;
+ }
+
+ for (ManagedServiceInfo info : toRemove) {
+ final ComponentName component = info.component;
+ final int oldUser = info.userid;
+ Slog.v(TAG, "disabling " + getCaption() + " for user "
+ + oldUser + ": " + component);
+ unregisterService(component, info.userid);
+ }
+
+ for (int i = 0; i < nUserIds; ++i) {
+ final ArrayList<ComponentName> add = toAdd.get(userIds[i]);
+ final int N = add.size();
+ for (int j = 0; j < N; j++) {
+ final ComponentName component = add.get(j);
+ Slog.v(TAG, "enabling " + getCaption() + " for user " + userIds[i] + ": "
+ + component);
+ registerService(component, userIds[i]);
+ }
+ }
+ }
+
+ /**
+ * Version of registerService that takes the name of a service component to bind to.
+ */
+ private void registerService(final ComponentName name, final int userid) {
+ if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
+
+ synchronized (mMutex) {
+ final String servicesBindingTag = name.toString() + "/" + userid;
+ if (mServicesBinding.contains(servicesBindingTag)) {
+ // stop registering this thing already! we're working on it
+ return;
+ }
+ mServicesBinding.add(servicesBindingTag);
+
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ // cut old connections
+ if (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": "
+ + info.service);
+ removeServiceLocked(i);
+ if (info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+ }
+
+ Intent intent = new Intent(mConfig.serviceInterface);
+ intent.setComponent(name);
+
+ intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(
+ mContext, 0, new Intent(mConfig.settingsAction), 0);
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mContext.getPackageManager().getApplicationInfo(
+ name.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ // Ignore if the package doesn't exist we won't be able to bind to the service.
+ }
+ final int targetSdkVersion =
+ appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
+
+ try {
+ if (DEBUG) Slog.v(TAG, "binding: " + intent);
+ if (!mContext.bindServiceAsUser(intent,
+ new ServiceConnection() {
+ IInterface mService;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ boolean added = false;
+ synchronized (mMutex) {
+ mServicesBinding.remove(servicesBindingTag);
+ try {
+ mService = asInterface(binder);
+ ManagedServiceInfo info = newServiceInfo(mService, name,
+ userid, false /*isSystem*/, this, targetSdkVersion);
+ binder.linkToDeath(info, 0);
+ added = mServices.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ if (added) {
+ onServiceAdded(mService);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Slog.v(TAG, getCaption() + " connection lost: " + name);
+ }
+ },
+ Context.BIND_AUTO_CREATE,
+ new UserHandle(userid)))
+ {
+ mServicesBinding.remove(servicesBindingTag);
+ Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
+ return;
+ }
+ } catch (SecurityException ex) {
+ Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Remove a service for the given user by ComponentName
+ */
+ private void unregisterService(ComponentName name, int userid) {
+ synchronized (mMutex) {
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ removeServiceLocked(i);
+ if (info.connection != null) {
+ try {
+ mContext.unbindService(info.connection);
+ } catch (IllegalArgumentException ex) {
+ // something happened to the service: we think we have a connection
+ // but it's bogus.
+ Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a service from the list but does not unbind
+ *
+ * @return the removed service.
+ */
+ private ManagedServiceInfo removeServiceImpl(IInterface service, final int userid) {
+ if (DEBUG) Slog.d(TAG, "removeServiceImpl service=" + service + " u=" + userid);
+ ManagedServiceInfo serviceInfo = null;
+ synchronized (mMutex) {
+ final int N = mServices.size();
+ for (int i=N-1; i>=0; i--) {
+ final ManagedServiceInfo info = mServices.get(i);
+ if (info.service.asBinder() == service.asBinder()
+ && info.userid == userid) {
+ if (DEBUG) Slog.d(TAG, "Removing active service " + info.component);
+ serviceInfo = removeServiceLocked(i);
+ }
+ }
+ }
+ return serviceInfo;
+ }
+
+ private ManagedServiceInfo removeServiceLocked(int i) {
+ final ManagedServiceInfo info = mServices.remove(i);
+ onServiceRemovedLocked(info);
+ return info;
+ }
+
+ private void checkNotNull(IInterface service) {
+ if (service == null) {
+ throw new IllegalArgumentException(getCaption() + " must not be null");
+ }
+ }
+
+ private void registerServiceImpl(final IInterface service,
+ final ComponentName component, final int userid) {
+ synchronized (mMutex) {
+ try {
+ ManagedServiceInfo info = newServiceInfo(service, component, userid,
+ true /*isSystem*/, null, Build.VERSION_CODES.L);
+ service.asBinder().linkToDeath(info, 0);
+ mServices.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ }
+
+ /**
+ * Removes a service from the list and unbinds.
+ */
+ private void unregisterServiceImpl(IInterface service, int userid) {
+ ManagedServiceInfo info = removeServiceImpl(service, userid);
+ if (info != null && info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName);
+
+ private SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ private void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(mSecureSettingsUri,
+ false, this, UserHandle.USER_ALL);
+ update(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update(uri);
+ }
+
+ private void update(Uri uri) {
+ if (uri == null || mSecureSettingsUri.equals(uri)) {
+ rebindServices();
+ }
+ }
+ }
+
+ public class ManagedServiceInfo implements IBinder.DeathRecipient {
+ public IInterface service;
+ public ComponentName component;
+ public int userid;
+ public boolean isSystem;
+ public ServiceConnection connection;
+ public int targetSdkVersion;
+
+ public ManagedServiceInfo(IInterface service, ComponentName component,
+ int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion) {
+ this.service = service;
+ this.component = component;
+ this.userid = userid;
+ this.isSystem = isSystem;
+ this.connection = connection;
+ this.targetSdkVersion = targetSdkVersion;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("ManagedServiceInfo[")
+ .append("component=").append(component)
+ .append(",userid=").append(userid)
+ .append(",isSystem=").append(isSystem)
+ .append(",targetSdkVersion=").append(targetSdkVersion)
+ .append(",connection=").append(connection == null ? null : "<connection>")
+ .append(",service=").append(service)
+ .append(']').toString();
+ }
+
+ public boolean enabledAndUserMatches(int nid) {
+ if (!isEnabledForCurrentProfiles()) {
+ return false;
+ }
+ if (this.userid == UserHandle.USER_ALL) return true;
+ if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
+ return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
+ }
+
+ public boolean supportsProfiles() {
+ return targetSdkVersion >= Build.VERSION_CODES.L;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) Slog.d(TAG, "binderDied");
+ // Remove the service, but don't unbind from the service. The system will bring the
+ // service back up, and the onServiceConnected handler will readd the service with the
+ // new binding. If this isn't a bound service, and is just a registered
+ // service, just removing it from the list is all we need to do anyway.
+ removeServiceImpl(this.service, this.userid);
+ }
+
+ /** convenience method for looking in mEnabledServicesForCurrentProfiles */
+ public boolean isEnabledForCurrentProfiles() {
+ if (this.isSystem) return true;
+ if (this.connection == null) return false;
+ return mEnabledServicesForCurrentProfiles.contains(this.component);
+ }
+ }
+
+ public static class UserProfiles {
+ // Profiles of the current user.
+ private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+
+ public void updateCache(Context context) {
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (userManager != null) {
+ int currentUserId = ActivityManager.getCurrentUser();
+ List<UserInfo> profiles = userManager.getProfiles(currentUserId);
+ synchronized (mCurrentProfiles) {
+ mCurrentProfiles.clear();
+ for (UserInfo user : profiles) {
+ mCurrentProfiles.put(user.id, user);
+ }
+ }
+ }
+ }
+
+ public int[] getCurrentProfileIds() {
+ synchronized (mCurrentProfiles) {
+ int[] users = new int[mCurrentProfiles.size()];
+ final int N = mCurrentProfiles.size();
+ for (int i = 0; i < N; ++i) {
+ users[i] = mCurrentProfiles.keyAt(i);
+ }
+ return users;
+ }
+ }
+
+ public boolean isCurrentProfile(int userId) {
+ synchronized (mCurrentProfiles) {
+ return mCurrentProfiles.get(userId) != null;
+ }
+ }
+ }
+
+ protected static class Config {
+ String caption;
+ String serviceInterface;
+ String secureSettingName;
+ String bindPermission;
+ String settingsAction;
+ int clientLabel;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationListeners.java b/services/core/java/com/android/server/notification/NotificationListeners.java
deleted file mode 100644
index 91d2f98..0000000
--- a/services/core/java/com/android/server/notification/NotificationListeners.java
+++ /dev/null
@@ -1,608 +0,0 @@
-/**
- * Copyright (c) 2014, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.service.notification.INotificationListener;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.UserProfiles;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-public class NotificationListeners {
- private static final String TAG = "NotificationListeners";
- private static final boolean DBG = NotificationManagerService.DBG;
-
- private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
-
- private final Context mContext;
- private final Handler mHandler;
- private final Object mMutex;
- private final UserProfiles mUserProfiles;
- private final SettingsObserver mSettingsObserver;
-
- // contains connections to all connected listeners, including app services
- // and system listeners
- private final ArrayList<NotificationListenerInfo> mListeners
- = new ArrayList<NotificationListenerInfo>();
- // things that will be put into mListeners as soon as they're ready
- private final ArrayList<String> mServicesBinding = new ArrayList<String>();
- // lists the component names of all enabled (and therefore connected) listener
- // app services for current profiles.
- private ArraySet<ComponentName> mEnabledListenersForCurrentProfiles
- = new ArraySet<ComponentName>();
- // Just the packages from mEnabledListenersForCurrentProfiles
- private ArraySet<String> mEnabledListenerPackageNames = new ArraySet<String>();
-
- public NotificationListeners(Context context, Handler handler, Object mutex,
- UserProfiles userProfiles) {
- mContext = context;
- mHandler = handler;
- mMutex = mutex;
- mUserProfiles = userProfiles;
- mSettingsObserver = new SettingsObserver(mHandler);
- }
-
- public void onBootPhaseAppsCanStart() {
- mSettingsObserver.observe();
- }
-
- protected void onServiceAdded(INotificationListener mListener) {
- // for subclasses
- }
-
- public void dump(PrintWriter pw) {
- pw.println(" Listeners (" + mEnabledListenersForCurrentProfiles.size()
- + ") enabled for current profiles:");
- for (ComponentName cmpt : mEnabledListenersForCurrentProfiles) {
- pw.println(" " + cmpt);
- }
-
- pw.println(" Live listeners (" + mListeners.size() + "):");
- for (NotificationListenerInfo info : mListeners) {
- pw.println(" " + info.component
- + " (user " + info.userid + "): " + info.listener
- + (info.isSystem?" SYSTEM":""));
- }
- }
-
- public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
- boolean anyListenersInvolved = false;
- if (pkgList != null && (pkgList.length > 0)) {
- for (String pkgName : pkgList) {
- if (mEnabledListenerPackageNames.contains(pkgName)) {
- anyListenersInvolved = true;
- }
- }
- }
-
- if (anyListenersInvolved) {
- // if we're not replacing a package, clean up orphaned bits
- if (!queryReplace) {
- disableNonexistentListeners();
- }
- // make sure we're still bound to any of our
- // listeners who may have just upgraded
- rebindListenerServices();
- }
- }
-
- /**
- * asynchronously notify all listeners about a new notification
- */
- public void notifyPostedLocked(StatusBarNotification sbn) {
- // make a copy in case changes are made to the underlying Notification object
- final StatusBarNotification sbnClone = sbn.clone();
- for (final NotificationListenerInfo info : mListeners) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- info.notifyPostedIfUserMatch(sbnClone);
- }
- });
- }
- }
-
- /**
- * asynchronously notify all listeners about a removed notification
- */
- public void notifyRemovedLocked(StatusBarNotification sbn) {
- // make a copy in case changes are made to the underlying Notification object
- // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
- final StatusBarNotification sbnLight = sbn.cloneLight();
-
- for (final NotificationListenerInfo info : mListeners) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- info.notifyRemovedIfUserMatch(sbnLight);
- }
- });
- }
- }
-
- public NotificationListenerInfo checkListenerTokenLocked(INotificationListener listener) {
- checkNullListener(listener);
- final IBinder token = listener.asBinder();
- final int N = mListeners.size();
- for (int i=0; i<N; i++) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (info.listener.asBinder() == token) return info;
- }
- throw new SecurityException("Disallowed call from unknown listener: " + listener);
- }
-
- public void unregisterListener(INotificationListener listener, int userid) {
- checkNullListener(listener);
- // no need to check permissions; if your listener binder is in the list,
- // that's proof that you had permission to add it in the first place
- unregisterListenerImpl(listener, userid);
- }
-
- public void registerListener(INotificationListener listener,
- ComponentName component, int userid) {
- checkNullListener(listener);
- registerListenerImpl(listener, component, userid);
- }
-
- /**
- * Remove notification access for any services that no longer exist.
- */
- private void disableNonexistentListeners() {
- int[] userIds = mUserProfiles.getCurrentProfileIds();
- final int N = userIds.length;
- for (int i = 0 ; i < N; ++i) {
- disableNonexistentListeners(userIds[i]);
- }
- }
-
- private void disableNonexistentListeners(int userId) {
- String flatIn = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- userId);
- if (!TextUtils.isEmpty(flatIn)) {
- if (DBG) Slog.v(TAG, "flat before: " + flatIn);
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
- new Intent(NotificationListenerService.SERVICE_INTERFACE),
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
- userId);
-
- Set<ComponentName> installed = new ArraySet<ComponentName>();
- for (int i = 0, count = installedServices.size(); i < count; i++) {
- ResolveInfo resolveInfo = installedServices.get(i);
- ServiceInfo info = resolveInfo.serviceInfo;
-
- if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
- info.permission)) {
- Slog.w(TAG, "Skipping notification listener service "
- + info.packageName + "/" + info.name
- + ": it does not require the permission "
- + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
- continue;
- }
- installed.add(new ComponentName(info.packageName, info.name));
- }
-
- String flatOut = "";
- if (!installed.isEmpty()) {
- String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
- ArrayList<String> remaining = new ArrayList<String>(enabled.length);
- for (int i = 0; i < enabled.length; i++) {
- ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
- if (installed.contains(enabledComponent)) {
- remaining.add(enabled[i]);
- }
- }
- flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining);
- }
- if (DBG) Slog.v(TAG, "flat after: " + flatOut);
- if (!flatIn.equals(flatOut)) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- flatOut, userId);
- }
- }
- }
-
- /**
- * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS
- * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
- */
- private void rebindListenerServices() {
- final int[] userIds = mUserProfiles.getCurrentProfileIds();
- final int nUserIds = userIds.length;
-
- final SparseArray<String> flat = new SparseArray<String>();
-
- for (int i = 0; i < nUserIds; ++i) {
- flat.put(userIds[i], Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- userIds[i]));
- }
-
- NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
- final SparseArray<ArrayList<ComponentName>> toAdd
- = new SparseArray<ArrayList<ComponentName>>();
-
- synchronized (mMutex) {
- // unbind and remove all existing listeners
- toRemove = mListeners.toArray(toRemove);
-
- final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>();
- final ArraySet<String> newPackages = new ArraySet<String>();
-
- for (int i = 0; i < nUserIds; ++i) {
- final ArrayList<ComponentName> add = new ArrayList<ComponentName>();
- toAdd.put(userIds[i], add);
-
- // decode the list of components
- String toDecode = flat.get(userIds[i]);
- if (toDecode != null) {
- String[] components = toDecode.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
- for (int j = 0; j < components.length; j++) {
- final ComponentName component
- = ComponentName.unflattenFromString(components[j]);
- if (component != null) {
- newEnabled.add(component);
- add.add(component);
- newPackages.add(component.getPackageName());
- }
- }
-
- }
- }
- mEnabledListenersForCurrentProfiles = newEnabled;
- mEnabledListenerPackageNames = newPackages;
- }
-
- for (NotificationListenerInfo info : toRemove) {
- final ComponentName component = info.component;
- final int oldUser = info.userid;
- Slog.v(TAG, "disabling notification listener for user "
- + oldUser + ": " + component);
- unregisterListenerService(component, info.userid);
- }
-
- for (int i = 0; i < nUserIds; ++i) {
- final ArrayList<ComponentName> add = toAdd.get(userIds[i]);
- final int N = add.size();
- for (int j = 0; j < N; j++) {
- final ComponentName component = add.get(j);
- Slog.v(TAG, "enabling notification listener for user " + userIds[i] + ": "
- + component);
- registerListenerService(component, userIds[i]);
- }
- }
- }
-
- /**
- * Version of registerListener that takes the name of a
- * {@link android.service.notification.NotificationListenerService} to bind to.
- *
- * This is the mechanism by which third parties may subscribe to notifications.
- */
- private void registerListenerService(final ComponentName name, final int userid) {
- NotificationUtil.checkCallerIsSystem();
-
- if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid);
-
- synchronized (mMutex) {
- final String servicesBindingTag = name.toString() + "/" + userid;
- if (mServicesBinding.contains(servicesBindingTag)) {
- // stop registering this thing already! we're working on it
- return;
- }
- mServicesBinding.add(servicesBindingTag);
-
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
- // cut old connections
- if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener);
- mListeners.remove(i);
- if (info.connection != null) {
- mContext.unbindService(info.connection);
- }
- }
- }
-
- Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
- intent.setComponent(name);
-
- intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
- R.string.notification_listener_binding_label);
-
- final PendingIntent pendingIntent = PendingIntent.getActivity(
- mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
-
- ApplicationInfo appInfo = null;
- try {
- appInfo = mContext.getPackageManager().getApplicationInfo(
- name.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- // Ignore if the package doesn't exist we won't be able to bind to the service.
- }
- final int targetSdkVersion =
- appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
-
- try {
- if (DBG) Slog.v(TAG, "binding: " + intent);
- if (!mContext.bindServiceAsUser(intent,
- new ServiceConnection() {
- INotificationListener mListener;
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- boolean added = false;
- synchronized (mMutex) {
- mServicesBinding.remove(servicesBindingTag);
- try {
- mListener = INotificationListener.Stub.asInterface(service);
- NotificationListenerInfo info
- = new NotificationListenerInfo(
- mListener, name, userid, this,
- targetSdkVersion);
- service.linkToDeath(info, 0);
- added = mListeners.add(info);
- } catch (RemoteException e) {
- // already dead
- }
- }
- if (added) {
- onServiceAdded(mListener);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Slog.v(TAG, "notification listener connection lost: " + name);
- }
- },
- Context.BIND_AUTO_CREATE,
- new UserHandle(userid)))
- {
- mServicesBinding.remove(servicesBindingTag);
- Slog.w(TAG, "Unable to bind listener service: " + intent);
- return;
- }
- } catch (SecurityException ex) {
- Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
- return;
- }
- }
- }
-
- /**
- * Remove a listener service for the given user by ComponentName
- */
- private void unregisterListenerService(ComponentName name, int userid) {
- NotificationUtil.checkCallerIsSystem();
-
- synchronized (mMutex) {
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
- mListeners.remove(i);
- if (info.connection != null) {
- try {
- mContext.unbindService(info.connection);
- } catch (IllegalArgumentException ex) {
- // something happened to the service: we think we have a connection
- // but it's bogus.
- Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex);
- }
- }
- }
- }
- }
- }
-
- /**
- * Removes a listener from the list but does not unbind from the listener's service.
- *
- * @return the removed listener.
- */
- private NotificationListenerInfo removeListenerImpl(
- final INotificationListener listener, final int userid) {
- NotificationListenerInfo listenerInfo = null;
- synchronized (mMutex) {
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (info.listener.asBinder() == listener.asBinder()
- && info.userid == userid) {
- listenerInfo = mListeners.remove(i);
- }
- }
- }
- return listenerInfo;
- }
-
- private void checkNullListener(INotificationListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("Listener must not be null");
- }
- }
-
- private void registerListenerImpl(final INotificationListener listener,
- final ComponentName component, final int userid) {
- synchronized (mMutex) {
- try {
- NotificationListenerInfo info
- = new NotificationListenerInfo(listener, component, userid,
- /*isSystem*/ true, Build.VERSION_CODES.L);
- listener.asBinder().linkToDeath(info, 0);
- mListeners.add(info);
- } catch (RemoteException e) {
- // already dead
- }
- }
- }
-
- /**
- * Removes a listener from the list and unbinds from its service.
- */
- private void unregisterListenerImpl(final INotificationListener listener, final int userid) {
- NotificationListenerInfo info = removeListenerImpl(listener, userid);
- if (info != null && info.connection != null) {
- mContext.unbindService(info.connection);
- }
- }
-
- private class SettingsObserver extends ContentObserver {
- private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
- = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
- private SettingsObserver(Handler handler) {
- super(handler);
- }
-
- private void observe() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
- false, this, UserHandle.USER_ALL);
- update(null);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- update(uri);
- }
-
- private void update(Uri uri) {
- if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
- rebindListenerServices();
- }
- }
- }
-
- public class NotificationListenerInfo implements IBinder.DeathRecipient {
- public INotificationListener listener;
- public ComponentName component;
- public int userid;
- public boolean isSystem;
- public ServiceConnection connection;
- public int targetSdkVersion;
-
- public NotificationListenerInfo(INotificationListener listener, ComponentName component,
- int userid, boolean isSystem, int targetSdkVersion) {
- this.listener = listener;
- this.component = component;
- this.userid = userid;
- this.isSystem = isSystem;
- this.connection = null;
- this.targetSdkVersion = targetSdkVersion;
- }
-
- public NotificationListenerInfo(INotificationListener listener, ComponentName component,
- int userid, ServiceConnection connection, int targetSdkVersion) {
- this.listener = listener;
- this.component = component;
- this.userid = userid;
- this.isSystem = false;
- this.connection = connection;
- this.targetSdkVersion = targetSdkVersion;
- }
-
- public boolean enabledAndUserMatches(StatusBarNotification sbn) {
- final int nid = sbn.getUserId();
- if (!isEnabledForCurrentProfiles()) {
- return false;
- }
- if (this.userid == UserHandle.USER_ALL) return true;
- if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
- return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
- }
-
- public boolean supportsProfiles() {
- return targetSdkVersion >= Build.VERSION_CODES.L;
- }
-
- public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
- if (!enabledAndUserMatches(sbn)) {
- return;
- }
- try {
- listener.onNotificationPosted(sbn);
- } catch (RemoteException ex) {
- Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
- }
- }
-
- public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
- if (!enabledAndUserMatches(sbn)) return;
- try {
- listener.onNotificationRemoved(sbn);
- } catch (RemoteException ex) {
- Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
- }
- }
-
- @Override
- public void binderDied() {
- // Remove the listener, but don't unbind from the service. The system will bring the
- // service back up, and the onServiceConnected handler will readd the listener with the
- // new binding. If this isn't a bound service, and is just a registered
- // INotificationListener, just removing it from the list is all we need to do anyway.
- removeListenerImpl(this.listener, this.userid);
- }
-
- /** convenience method for looking in mEnabledListenersForCurrentProfiles */
- public boolean isEnabledForCurrentProfiles() {
- if (this.isSystem) return true;
- if (this.connection == null) return false;
- return mEnabledListenersForCurrentProfiles.contains(this.component);
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5f096cb..6e4eb565 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -22,6 +22,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -35,10 +36,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -49,15 +50,19 @@
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.IConditionListener;
+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 +70,6 @@
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.Xml;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -78,7 +82,8 @@
import com.android.server.SystemService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
-import com.android.server.notification.NotificationListeners.NotificationListenerInfo;
+import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.notification.NotificationUsageStats.SingleNotificationStats;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -101,7 +106,6 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.NoSuchElementException;
/** {@hide} */
@@ -191,8 +195,9 @@
final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
- private NotificationListeners mListeners;
private final UserProfiles mUserProfiles = new UserProfiles();
+ private NotificationListeners mListeners;
+ private ConditionProviders mConditionProviders;
private final NotificationUsageStats mUsageStats = new NotificationUsageStats();
@@ -702,7 +707,7 @@
String pkgList[] = null;
boolean queryReplace = queryRemove &&
intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- if (DBG) Slog.i(TAG, "queryReplace=" + queryReplace);
+ if (DBG) Slog.i(TAG, "action=" + action + " queryReplace=" + queryReplace);
if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else if (queryRestart) {
@@ -745,6 +750,7 @@
}
}
mListeners.onPackagesChanged(queryReplace, pkgList);
+ mConditionProviders.onPackagesChanged(queryReplace, pkgList);
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@@ -834,7 +840,7 @@
mHandler = new WorkerHandler();
mZenModeHelper = new ZenModeHelper(getContext(), mHandler);
- mZenModeHelper.setCallback(new ZenModeHelper.Callback() {
+ mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
public void onConfigChanged() {
savePolicyFile();
@@ -845,18 +851,9 @@
importOldBlockDb();
- mListeners = new NotificationListeners(getContext(),
- mHandler, mNotificationList, mUserProfiles) {
- @Override
- public void onServiceAdded(INotificationListener listener) {
- final String[] keys = getActiveNotificationKeysFromListener(listener);
- try {
- listener.onListenerConnected(keys);
- } catch (RemoteException e) {
- // we tried
- }
- }
- };
+ mListeners = new NotificationListeners();
+ mConditionProviders = new ConditionProviders(getContext(),
+ mHandler, mUserProfiles, mZenModeHelper);
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -972,6 +969,7 @@
// bind to listener services.
mSettingsObserver.observe();
mListeners.onBootPhaseAppsCanStart();
+ mConditionProviders.onBootPhaseAppsCanStart();
}
}
@@ -1005,8 +1003,7 @@
return ;
}
- final boolean isSystemToast =
- NotificationUtil.isCallerSystem() || ("android".equals(pkg));
+ final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
if (!isSystemToast) {
@@ -1097,7 +1094,7 @@
@Override
public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
- NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
+ checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
// Don't allow client applications to cancel foreground service notis.
@@ -1109,7 +1106,7 @@
@Override
public void cancelAllNotifications(String pkg, int userId) {
- NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
+ checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
@@ -1123,7 +1120,7 @@
@Override
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
- NotificationUtil.checkCallerIsSystem();
+ checkCallerIsSystem();
setNotificationsEnabledForPackageImpl(pkg, uid, enabled);
}
@@ -1133,7 +1130,7 @@
*/
@Override
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
- NotificationUtil.checkCallerIsSystem();
+ checkCallerIsSystem();
return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
== AppOpsManager.MODE_ALLOWED);
}
@@ -1201,8 +1198,8 @@
@Override
public void registerListener(final INotificationListener listener,
final ComponentName component, final int userid) {
- NotificationUtil.checkCallerIsSystem();
- mListeners.registerListener(listener, component, userid);
+ checkCallerIsSystem();
+ mListeners.registerService(listener, component, userid);
}
/**
@@ -1210,7 +1207,7 @@
*/
@Override
public void unregisterListener(INotificationListener listener, int userid) {
- mListeners.unregisterListener(listener, userid);
+ mListeners.unregisterService(listener, userid);
}
/**
@@ -1227,8 +1224,7 @@
long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
- final NotificationListenerInfo info =
- mListeners.checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (keys != null) {
final int N = keys.length;
for (int i = 0; i < N; i++) {
@@ -1237,7 +1233,7 @@
if (userId != info.userid && userId != UserHandle.USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
throw new SecurityException("Disallowed call from listener: "
- + info.listener);
+ + info.service);
}
if (r != null) {
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
@@ -1255,7 +1251,7 @@
}
}
- private void cancelNotificationFromListenerLocked(NotificationListenerInfo info,
+ private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
@@ -1278,8 +1274,7 @@
long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
- final NotificationListenerInfo info =
- mListeners.checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (info.supportsProfiles()) {
Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
+ "from " + info.component
@@ -1305,14 +1300,14 @@
public StatusBarNotification[] getActiveNotificationsFromListener(
INotificationListener token, String[] keys) {
synchronized (mNotificationList) {
- final NotificationListenerInfo info = mListeners.checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final ArrayList<StatusBarNotification> list
= new ArrayList<StatusBarNotification>();
if (keys == null) {
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn)) {
+ if (info.enabledAndUserMatches(sbn.getUserId())) {
list.add(sbn);
}
}
@@ -1320,7 +1315,7 @@
final int N = keys.length;
for (int i=0; i<N; i++) {
NotificationRecord r = mNotificationsByKey.get(keys[i]);
- if (r != null && info.enabledAndUserMatches(r.sbn)) {
+ if (r != null && info.enabledAndUserMatches(r.sbn.getUserId())) {
list.add(r.sbn);
}
}
@@ -1336,17 +1331,48 @@
@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 notifyConditions(String pkg, IConditionProvider provider,
+ Condition[] conditions) {
+ final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider);
+ checkCallerIsSystemOrSameApp(pkg);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mConditionProviders.notifyConditions(pkg, info, conditions);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void requestZenModeConditions(IConditionListener callback, boolean requested) {
+ enforceSystemOrSystemUI("INotificationManager.requestZenModeConditions");
+ mConditionProviders.requestZenModeConditions(callback, requested);
+ }
+
+ @Override
+ public void setZenModeCondition(Uri conditionId) {
+ enforceSystemOrSystemUI("INotificationManager.setZenModeCondition");
+ mConditionProviders.setZenModeCondition(conditionId);
+ }
+
+ private void enforceSystemOrSystemUI(String message) {
+ if (isCallerSystem()) return;
+ getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ message);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -1362,12 +1388,12 @@
private String[] getActiveNotificationKeysFromListener(INotificationListener token) {
synchronized (mNotificationList) {
- final NotificationListenerInfo info = mListeners.checkListenerTokenLocked(token);
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final ArrayList<String> keys = new ArrayList<String>();
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
final StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn)) {
+ if (info.enabledAndUserMatches(sbn.getUserId())) {
keys.add(sbn.getKey());
}
}
@@ -1378,8 +1404,6 @@
void dumpImpl(PrintWriter pw) {
pw.println("Current Notification Manager state:");
- mListeners.dump(pw);
-
int N;
synchronized (mToastQueue) {
@@ -1433,6 +1457,12 @@
pw.println("\n Zen Mode:");
mZenModeHelper.dump(pw, " ");
+
+ pw.println("\n Notification listeners:");
+ mListeners.dump(pw);
+
+ pw.println("\n Condition providers:");
+ mConditionProviders.dump(pw);
}
}
@@ -1455,9 +1485,8 @@
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
- NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
- final boolean isSystemNotification =
- NotificationUtil.isUidSystem(callingUid) || ("android".equals(pkg));
+ checkCallerIsSystemOrSameApp(pkg);
+ final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
@@ -2028,7 +2057,7 @@
void cancelNotification(final int callingUid, final int callingPid,
final String pkg, final String tag, final int id,
final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
- final int userId, final int reason, final NotificationListenerInfo listener) {
+ final int userId, final int reason, final ManagedServiceInfo listener) {
// In enqueueNotificationInternal notifications are added by scheduling the
// work on the worker handler. Hence, we also schedule the cancel on this
// handler to avoid a scenario where an add notification call followed by a
@@ -2099,7 +2128,7 @@
*/
boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags,
int mustNotHaveFlags, boolean doit, int userId, int reason,
- NotificationListenerInfo listener) {
+ ManagedServiceInfo listener) {
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
listener == null ? null : listener.component.toShortString());
@@ -2141,7 +2170,7 @@
}
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
- NotificationListenerInfo listener, boolean includeCurrentProfiles) {
+ ManagedServiceInfo listener, boolean includeCurrentProfiles) {
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
null, userId, 0, 0, reason,
listener == null ? null : listener.component.toShortString());
@@ -2234,38 +2263,129 @@
}
}
- public static class UserProfiles {
- // Profiles of the current user.
- private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+ private static boolean isUidSystem(int uid) {
+ final int appid = UserHandle.getAppId(uid);
+ return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
+ }
- private void updateCache(Context context) {
- UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- if (userManager != null) {
- int currentUserId = ActivityManager.getCurrentUser();
- List<UserInfo> profiles = userManager.getProfiles(currentUserId);
- synchronized (mCurrentProfiles) {
- mCurrentProfiles.clear();
- for (UserInfo user : profiles) {
- mCurrentProfiles.put(user.id, user);
+ private static boolean isCallerSystem() {
+ return isUidSystem(Binder.getCallingUid());
+ }
+
+ private static void checkCallerIsSystem() {
+ if (isCallerSystem()) {
+ return;
+ }
+ throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
+ }
+
+ private static void checkCallerIsSystemOrSameApp(String pkg) {
+ if (isCallerSystem()) {
+ return;
+ }
+ final int uid = Binder.getCallingUid();
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
+ pkg, 0, UserHandle.getCallingUserId());
+ if (!UserHandle.isSameApp(ai.uid, uid)) {
+ throw new SecurityException("Calling uid " + uid + " gave package"
+ + pkg + " which is owned by uid " + ai.uid);
+ }
+ } catch (RemoteException re) {
+ throw new SecurityException("Unknown package " + pkg + "\n" + re);
+ }
+ }
+
+ public class NotificationListeners extends ManagedServices {
+
+ public NotificationListeners() {
+ super(getContext(), mHandler, mNotificationList, mUserProfiles);
+ }
+
+ @Override
+ protected Config getConfig() {
+ Config c = new Config();
+ c.caption = "notification listener";
+ c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
+ c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
+ c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
+ c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
+ c.clientLabel = R.string.notification_listener_binding_label;
+ return c;
+ }
+
+ @Override
+ protected IInterface asInterface(IBinder binder) {
+ return INotificationListener.Stub.asInterface(binder);
+ }
+
+ @Override
+ public void onServiceAdded(IInterface service) {
+ final INotificationListener listener = (INotificationListener) service;
+ final String[] keys = getActiveNotificationKeysFromListener(listener);
+ try {
+ listener.onListenerConnected(keys);
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+
+ /**
+ * asynchronously notify all listeners about a new notification
+ */
+ public void notifyPostedLocked(StatusBarNotification sbn) {
+ // make a copy in case changes are made to the underlying Notification object
+ final StatusBarNotification sbnClone = sbn.clone();
+ for (final ManagedServiceInfo info : mServices) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyPostedIfUserMatch(info, sbnClone);
}
- }
+ });
}
}
- public int[] getCurrentProfileIds() {
- synchronized (mCurrentProfiles) {
- int[] users = new int[mCurrentProfiles.size()];
- final int N = mCurrentProfiles.size();
- for (int i = 0; i < N; ++i) {
- users[i] = mCurrentProfiles.keyAt(i);
- }
- return users;
+ /**
+ * asynchronously notify all listeners about a removed notification
+ */
+ public void notifyRemovedLocked(StatusBarNotification sbn) {
+ // make a copy in case changes are made to the underlying Notification object
+ // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
+ // notification
+ final StatusBarNotification sbnLight = sbn.cloneLight();
+ for (ManagedServiceInfo serviceInfo : mServices) {
+ final ManagedServiceInfo info = (ManagedServiceInfo) serviceInfo;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyRemovedIfUserMatch(info, sbnLight);
+ }
+ });
}
}
- public boolean isCurrentProfile(int userId) {
- synchronized (mCurrentProfiles) {
- return mCurrentProfiles.get(userId) != null;
+ private void notifyPostedIfUserMatch(ManagedServiceInfo info, StatusBarNotification sbn) {
+ if (!info.enabledAndUserMatches(sbn.getUserId())) {
+ return;
+ }
+ final INotificationListener listener = (INotificationListener)info.service;
+ try {
+ listener.onNotificationPosted(sbn);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
+ }
+ }
+
+ private void notifyRemovedIfUserMatch(ManagedServiceInfo info, StatusBarNotification sbn) {
+ if (!info.enabledAndUserMatches(sbn.getUserId())) {
+ return;
+ }
+ final INotificationListener listener = (INotificationListener)info.service;
+ try {
+ listener.onNotificationRemoved(sbn);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationUtil.java b/services/core/java/com/android/server/notification/NotificationUtil.java
deleted file mode 100644
index 459adce..0000000
--- a/services/core/java/com/android/server/notification/NotificationUtil.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Copyright (c) 2014, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.app.AppGlobals;
-import android.content.pm.ApplicationInfo;
-import android.os.Binder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-
-public class NotificationUtil {
-
- // Return true if the UID is a system or phone UID and therefore should not have
- // any notifications or toasts blocked.
- public static boolean isUidSystem(int uid) {
- final int appid = UserHandle.getAppId(uid);
- return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
- }
-
- // same as isUidSystem(int, int) for the Binder caller's UID.
- public static boolean isCallerSystem() {
- return isUidSystem(Binder.getCallingUid());
- }
-
- public static void checkCallerIsSystem() {
- if (isCallerSystem()) {
- return;
- }
- throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
- }
-
- public static void checkCallerIsSystemOrSameApp(String pkg) {
- if (isCallerSystem()) {
- return;
- }
- final int uid = Binder.getCallingUid();
- try {
- ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
- pkg, 0, UserHandle.getCallingUserId());
- if (!UserHandle.isSameApp(ai.uid, uid)) {
- throw new SecurityException("Calling uid " + uid + " gave package"
- + pkg + " which is owned by uid " + ai.uid);
- }
- } catch (RemoteException re) {
- throw new SecurityException("Unknown package " + pkg + "\n" + re);
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 80f5b5c..137730a 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -46,6 +46,7 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -69,8 +70,8 @@
private final SettingsObserver mSettingsObserver;
private final AppOpsManager mAppOps;
private final ZenModeConfig mDefaultConfig;
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
- private Callback mCallback;
private int mZenMode;
private ZenModeConfig mConfig;
@@ -115,8 +116,8 @@
return new ZenModeConfig();
}
- public void setCallback(Callback callback) {
- mCallback = callback;
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
}
public boolean shouldIntercept(String pkg, Notification n) {
@@ -132,6 +133,14 @@
return false;
}
+ public int getZenMode() {
+ return mZenMode;
+ }
+
+ public void setZenMode(int zenModeValue) {
+ Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue);
+ }
+
public void updateZenMode() {
final int mode = Global.getInt(mContext.getContentResolver(),
Global.ZEN_MODE, Global.ZEN_MODE_OFF);
@@ -157,6 +166,7 @@
mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.USE_DEFAULT_STREAM_TYPE,
zen ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
exceptionPackages);
+ dispatchOnZenModeChanged();
}
public boolean allowDisable(int what, IBinder token, String pkg) {
@@ -193,7 +203,7 @@
if (config.equals(mConfig)) return true;
mConfig = config;
Slog.d(TAG, "mConfig=" + mConfig);
- if (mCallback != null) mCallback.onConfigChanged();
+ dispatchOnConfigChanged();
final String val = Integer.toString(mConfig.hashCode());
Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
updateAlarms();
@@ -201,6 +211,18 @@
return true;
}
+ private void dispatchOnConfigChanged() {
+ for (Callback callback : mCallbacks) {
+ callback.onConfigChanged();
+ }
+ }
+
+ private void dispatchOnZenModeChanged() {
+ for (Callback callback : mCallbacks) {
+ callback.onZenModeChanged();
+ }
+ }
+
private boolean isCall(String pkg, Notification n) {
return CALL_PACKAGES.contains(pkg);
}
@@ -300,13 +322,14 @@
if (skip) {
Slog.d(TAG, "Skipping zen mode update for the weekend");
} else {
- Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue);
+ ZenModeHelper.this.setZenMode(zenModeValue);
}
updateAlarms();
}
}
- public interface Callback {
- void onConfigChanged();
+ public static class Callback {
+ void onConfigChanged() {}
+ void onZenModeChanged() {}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 702d9d2..d0412ef 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -23,13 +23,13 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.system.OsConstants.S_IRWXU;
+import static android.system.OsConstants.S_IRGRP;
+import static android.system.OsConstants.S_IXGRP;
+import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IXOTH;
import static com.android.internal.util.ArrayUtils.appendInt;
import static com.android.internal.util.ArrayUtils.removeInt;
-import static libcore.io.OsConstants.S_IRWXU;
-import static libcore.io.OsConstants.S_IRGRP;
-import static libcore.io.OsConstants.S_IXGRP;
-import static libcore.io.OsConstants.S_IROTH;
-import static libcore.io.OsConstants.S_IXOTH;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
@@ -104,7 +104,6 @@
import android.os.FileObserver;
import android.os.FileUtils;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -120,6 +119,9 @@
import android.os.UserManager;
import android.security.KeyStore;
import android.security.SystemKeyStore;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -143,6 +145,8 @@
import java.io.PrintWriter;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -158,12 +162,10 @@
import java.util.Map;
import java.util.Set;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
-import libcore.io.StructStat;
import com.android.internal.R;
+import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.storage.DeviceStorageMonitorInternal;
/**
@@ -199,8 +201,6 @@
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 +278,6 @@
final PackageHandler mHandler;
final int mSdkVersion = Build.VERSION.SDK_INT;
- final String mSdkCodename = "REL".equals(Build.VERSION.CODENAME)
- ? null : Build.VERSION.CODENAME;
final Context mContext;
final boolean mFactoryTest;
@@ -1537,6 +1535,9 @@
mSettings.readDefaultPreferredAppsLPw(this, 0);
}
+ // All the changes are done during package scanning.
+ mSettings.updateInternalDatabaseVersion();
+
// can downgrade to reader
mSettings.writeLPr();
@@ -2165,6 +2166,24 @@
}
@Override
+ public boolean activitySupportsIntent(ComponentName component, Intent intent,
+ String resolvedType) {
+ synchronized (mPackages) {
+ PackageParser.Activity a = mActivities.mActivities.get(component);
+ if (a == null) {
+ return false;
+ }
+ for (int i=0; i<a.intents.size(); i++) {
+ if (a.intents.get(i).match(intent.getAction(), resolvedType, intent.getScheme(),
+ intent.getData(), intent.getCategories(), TAG) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get receiver info");
@@ -2702,6 +2721,59 @@
return PackageManager.SIGNATURE_NO_MATCH;
}
+ /**
+ * If the database version for this type of package (internal storage or
+ * external storage) is less than the version where package signatures
+ * were updated, return true.
+ */
+ private boolean isCompatSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
+ return (isExternal(scannedPkg) && mSettings.isExternalDatabaseVersionOlderThan(
+ DatabaseVersion.SIGNATURE_END_ENTITY))
+ || (!isExternal(scannedPkg) && mSettings.isInternalDatabaseVersionOlderThan(
+ DatabaseVersion.SIGNATURE_END_ENTITY));
+ }
+
+ /**
+ * Used for backward compatibility to make sure any packages with
+ * certificate chains get upgraded to the new style. {@code existingSigs}
+ * will be in the old format (since they were stored on disk from before the
+ * system upgrade) and {@code scannedSigs} will be in the newer format.
+ */
+ private int compareSignaturesCompat(PackageSignatures existingSigs,
+ PackageParser.Package scannedPkg) {
+ if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
+ return PackageManager.SIGNATURE_NO_MATCH;
+ }
+
+ HashSet<Signature> existingSet = new HashSet<Signature>();
+ for (Signature sig : existingSigs.mSignatures) {
+ existingSet.add(sig);
+ }
+ HashSet<Signature> scannedCompatSet = new HashSet<Signature>();
+ for (Signature sig : scannedPkg.mSignatures) {
+ try {
+ Signature[] chainSignatures = sig.getChainSignatures();
+ for (Signature chainSig : chainSignatures) {
+ scannedCompatSet.add(chainSig);
+ }
+ } catch (CertificateEncodingException e) {
+ scannedCompatSet.add(sig);
+ }
+ }
+ /*
+ * Make sure the expanded scanned set contains all signatures in the
+ * existing one.
+ */
+ if (scannedCompatSet.equals(existingSet)) {
+ // Migrate the old signatures to the new scheme.
+ existingSigs.assignSignatures(scannedPkg.mSignatures);
+ // The new KeySets will be re-added later in the scanning process.
+ mSettings.mKeySetManager.removeAppKeySetData(scannedPkg.packageName);
+ return PackageManager.SIGNATURE_MATCH;
+ }
+ return PackageManager.SIGNATURE_NO_MATCH;
+ }
+
public String[] getPackagesForUid(int uid) {
uid = UserHandle.getAppId(uid);
// reader
@@ -3783,27 +3855,26 @@
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 (ps != null
+ && ps.codePath.equals(srcFile)
+ && ps.timeStamp == srcFile.lastModified()
+ && !isCompatSignatureUpdateNeeded(pkg)) {
+ 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;
}
-
- if (!pp.collectCertificates(pkg, parseFlags)) {
- mLastScanError = pp.getParseError();
- return false;
- }
+
+ 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;
}
@@ -4037,22 +4108,32 @@
return processName;
}
- private boolean verifySignaturesLP(PackageSetting pkgSetting,
- PackageParser.Package pkg) {
+ private boolean verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg) {
if (pkgSetting.signatures.mSignatures != null) {
// Already existing package. Make sure signatures match
- if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures) !=
- PackageManager.SIGNATURE_MATCH) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " signatures do not match the previously installed version; ignoring!");
- mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
- return false;
- }
+ boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
+ == PackageManager.SIGNATURE_MATCH;
+ if (!match) {
+ match = compareSignaturesCompat(pkgSetting.signatures, pkg)
+ == PackageManager.SIGNATURE_MATCH;
+ }
+ if (!match) {
+ Slog.e(TAG, "Package " + pkg.packageName
+ + " signatures do not match the previously installed version; ignoring!");
+ mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+ return false;
+ }
}
// Check for shared user signatures
if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
- if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
- pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
+ // Already existing package. Make sure signatures match
+ boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+ pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+ if (!match) {
+ match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
+ == PackageManager.SIGNATURE_MATCH;
+ }
+ if (!match) {
Slog.e(TAG, "Package " + pkg.packageName
+ " has no signatures that match those in shared user "
+ pkgSetting.sharedUser.name + "; ignoring!");
@@ -4713,7 +4794,7 @@
if (dataPath.exists()) {
int currentUid = 0;
try {
- StructStat stat = Libcore.os.stat(dataPath.getPath());
+ StructStat stat = Os.stat(dataPath.getPath());
currentUid = stat.st_uid;
} catch (ErrnoException e) {
Slog.e(TAG, "Couldn't stat path " + dataPath.getPath(), e);
@@ -4839,7 +4920,6 @@
pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString;
}
}
-
pkgSetting.uidError = uidError;
}
@@ -5420,12 +5500,13 @@
}
}
- 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();
} else if (FileUtils.contains(Environment.getOemDirectory(), codePath)) {
- codeRoot = Environment.getRootDirectory();
+ codeRoot = Environment.getOemDirectory();
} else if (FileUtils.contains(Environment.getVendorDirectory(), codePath)) {
codeRoot = Environment.getVendorDirectory();
} else {
@@ -5457,12 +5538,12 @@
PackageSetting pkgSetting) {
// "bundled" here means system-installed with no overriding update
final boolean bundledApk = isSystemApp(pkg) && !isUpdatedSystemApp(pkg);
- final String apkName = getApkName(pkgSetting.codePathString);
+ final String apkName = getApkName(pkg.applicationInfo.sourceDir);
final File libDir;
if (bundledApk) {
// If "/system/lib64/apkname" exists, assume that is the per-package
// native library directory to use; otherwise use "/system/lib/apkname".
- String apkRoot = calculateApkRoot(pkgSetting.codePath);
+ String apkRoot = calculateApkRoot(pkg.applicationInfo.sourceDir);
File lib64 = new File(apkRoot, LIB64_DIR_NAME);
File packLib64 = new File(lib64, apkName);
libDir = (packLib64.exists()) ? lib64 : new File(apkRoot, LIB_DIR_NAME);
@@ -5484,8 +5565,7 @@
}
try {
- Libcore.os.chmod(nativeLibraryDir.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH
- | S_IXOTH);
+ Os.chmod(nativeLibraryDir.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
} catch (ErrnoException e) {
throw new IOException("Cannot chmod native library directory "
+ nativeLibraryDir.getPath(), e);
@@ -9452,7 +9532,7 @@
return;
}
}
- if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {
+ if (!pp.collectCertificates(pkg, parseFlags)) {
res.returnCode = pp.getParseError();
return;
}
@@ -11063,6 +11143,8 @@
public static final int DUMP_KEYSETS = 1 << 11;
+ public static final int DUMP_VERSION = 1 << 12;
+
public static final int OPTION_SHOW_FILTERS = 1 << 0;
private int mTypes;
@@ -11161,6 +11243,7 @@
pw.println(" s[hared-users]: dump shared user IDs");
pw.println(" m[essages]: print collected runtime messages");
pw.println(" v[erifiers]: print package verifier info");
+ pw.println(" version: print database version info");
pw.println(" <package.name>: info about given package");
pw.println(" k[eysets]: print known keysets");
return;
@@ -11209,6 +11292,8 @@
dumpState.setDump(DumpState.DUMP_MESSAGES);
} else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERIFIERS);
+ } else if ("version".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_VERSION);
} else if ("k".equals(cmd) || "keysets".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_KEYSETS);
}
@@ -11220,6 +11305,24 @@
// reader
synchronized (mPackages) {
+ if (dumpState.isDumping(DumpState.DUMP_VERSION) && packageName == null) {
+ if (!checkin) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Database versions:");
+ pw.print(" SDK Version:");
+ pw.print(" internal=");
+ pw.print(mSettings.mInternalSdkPlatform);
+ pw.print(" external=");
+ pw.println(mSettings.mExternalSdkPlatform);
+ pw.print(" DB Version:");
+ pw.print(" internal=");
+ pw.print(mSettings.mInternalDatabaseVersion);
+ pw.print(" external=");
+ pw.println(mSettings.mExternalDatabaseVersion);
+ }
+ }
+
if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
if (!checkin) {
if (dumpState.onTitlePrinted())
@@ -11750,6 +11853,9 @@
| (regrantPermissions
? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL)
: 0));
+
+ mSettings.updateExternalDatabaseVersion();
+
// can downgrade to reader
// Persist settings
mSettings.writeLPr();
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 80f716c..e4dd2d4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -89,6 +89,34 @@
final class Settings {
private static final String TAG = "PackageSettings";
+ /**
+ * Current version of the package database. Set it to the latest version in
+ * the {@link DatabaseVersion} class below to ensure the database upgrade
+ * doesn't happen repeatedly.
+ * <p>
+ * Note that care should be taken to make sure all database upgrades are
+ * idempotent.
+ */
+ private static final int CURRENT_DATABASE_VERSION = DatabaseVersion.SIGNATURE_END_ENTITY;
+
+ /**
+ * This class contains constants that can be referred to from upgrade code.
+ * Insert constant values here that describe the upgrade reason. The version
+ * code must be monotonically increasing.
+ */
+ public static class DatabaseVersion {
+ /**
+ * The initial version of the database.
+ */
+ public static final int FIRST_VERSION = 1;
+
+ /**
+ * Migrating the Signature array from the entire certificate chain to
+ * just the signing certificate.
+ */
+ public static final int SIGNATURE_END_ENTITY = 2;
+ }
+
private static final boolean DEBUG_STOPPED = false;
private static final boolean DEBUG_MU = false;
@@ -133,6 +161,14 @@
int mInternalSdkPlatform;
int mExternalSdkPlatform;
+ /**
+ * The current database version for apps on internal storage. This is
+ * used to upgrade the format of the packages.xml database not necessarily
+ * tied to an SDK version.
+ */
+ int mInternalDatabaseVersion;
+ int mExternalDatabaseVersion;
+
Boolean mReadExternalStorageEnforced;
/** Device identity for the purpose of package verification. */
@@ -825,6 +861,40 @@
}
}
+ /**
+ * Returns whether the current database has is older than {@code version}
+ * for apps on internal storage.
+ */
+ public boolean isInternalDatabaseVersionOlderThan(int version) {
+ return mInternalDatabaseVersion < version;
+ }
+
+ /**
+ * Returns whether the current database has is older than {@code version}
+ * for apps on external storage.
+ */
+ public boolean isExternalDatabaseVersionOlderThan(int version) {
+ return mExternalDatabaseVersion < version;
+ }
+
+ /**
+ * Updates the database version for apps on internal storage. Called after
+ * call the updates to the database format are done for apps on internal
+ * storage after the initial start-up scan.
+ */
+ public void updateInternalDatabaseVersion() {
+ mInternalDatabaseVersion = CURRENT_DATABASE_VERSION;
+ }
+
+ /**
+ * Updates the database version for apps on internal storage. Called after
+ * call the updates to the database format are done for apps on internal
+ * storage after the initial start-up scan.
+ */
+ public void updateExternalDatabaseVersion() {
+ mExternalDatabaseVersion = CURRENT_DATABASE_VERSION;
+ }
+
private void readPreferredActivitiesLPw(XmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
@@ -1355,6 +1425,11 @@
serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform));
serializer.endTag(null, "last-platform-version");
+ serializer.startTag(null, "database-version");
+ serializer.attribute(null, "internal", Integer.toString(mInternalDatabaseVersion));
+ serializer.attribute(null, "external", Integer.toString(mExternalDatabaseVersion));
+ serializer.endTag(null, "database-version");
+
if (mVerifierDeviceIdentity != null) {
serializer.startTag(null, "verifier");
serializer.attribute(null, "device", mVerifierDeviceIdentity.toString());
@@ -1830,6 +1905,19 @@
}
} catch (NumberFormatException e) {
}
+ } else if (tagName.equals("database-version")) {
+ mInternalDatabaseVersion = mExternalDatabaseVersion = 0;
+ try {
+ String internalDbVersionString = parser.getAttributeValue(null, "internal");
+ if (internalDbVersionString != null) {
+ mInternalDatabaseVersion = Integer.parseInt(internalDbVersionString);
+ }
+ String externalDbVersionString = parser.getAttributeValue(null, "external");
+ if (externalDbVersionString != null) {
+ mExternalDatabaseVersion = Integer.parseInt(externalDbVersionString);
+ }
+ } catch (NumberFormatException ignored) {
+ }
} else if (tagName.equals("verifier")) {
final String deviceIdentity = parser.getAttributeValue(null, "device");
try {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 210e151b..f8103de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -22,7 +22,6 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityThread;
-import android.app.admin.DevicePolicyManager;
import android.app.IStopUserCallback;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -263,46 +262,58 @@
if (userId != UserHandle.getCallingUserId()) {
checkManageUsersPermission("getting profiles related to user " + userId);
}
- synchronized (mPackagesLock) {
- // Getting the service here is not good for testing purposes. However, this service
- // is not available when UserManagerService starts up so we need a lazy load.
-
- DevicePolicyManager dpm = null;
- if (enabledOnly) {
- dpm = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mPackagesLock) {
+ return getProfilesLocked(userId, enabledOnly);
}
-
- UserInfo user = getUserInfoLocked(userId);
- ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
- for (int i = 0; i < mUsers.size(); i++) {
- UserInfo profile = mUsers.valueAt(i);
- if (!isProfileOf(user, profile)) {
- continue;
- }
-
- if (enabledOnly && profile.isManagedProfile()) {
- if (dpm != null) {
- if(!dpm.isProfileEnabled(profile.id)) {
- continue;
- }
- } else {
- Log.w(LOG_TAG,
- "Attempting to reach DevicePolicyManager before it was started");
- // TODO: There might be system apps that need to call this. Make sure that
- // DevicePolicyManagerService is ready at that time (otherwise, any default
- // value is a bad one).
- throw new IllegalArgumentException(String.format(
- "Attempting to get enabled profiles for %d before "
- + "DevicePolicyManagerService has been started.", userId));
- }
- }
- users.add(profile);
- }
- return users;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
+ /** Assume permissions already checked and caller's identity cleared */
+ private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
+ // Getting the service here is not good for testing purposes.
+ // However, this service is not available when UserManagerService starts
+ // up so we need a lazy load.
+
+ DevicePolicyManager dpm = null;
+ if (enabledOnly) {
+ dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ }
+
+ UserInfo user = getUserInfoLocked(userId);
+ ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo profile = mUsers.valueAt(i);
+ if (!isProfileOf(user, profile)) {
+ continue;
+ }
+
+ if (enabledOnly && profile.isManagedProfile()) {
+ if (dpm != null) {
+ if (!dpm.isProfileEnabled(profile.id)) {
+ continue;
+ }
+ } else {
+ Log.w(LOG_TAG,
+ "Attempting to reach DevicePolicyManager before it is started");
+ // TODO: There might be system apps that need to call this.
+ // Make sure that DevicePolicyManagerService is ready at that
+ // time (otherwise, any default value is a bad one).
+ throw new IllegalArgumentException(String.format(
+ "Attempting to get enabled profiles for %d before "
+ + "DevicePolicyManagerService has been started.",
+ userId));
+ }
+ }
+ users.add(profile);
+ }
+ return users;
+ }
+
private boolean isProfileOf(UserInfo user, UserInfo profile) {
return user.id == profile.id ||
(user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
@@ -459,13 +470,13 @@
synchronized (mPackagesLock) {
Bundle restrictions = mUserRestrictions.get(userId);
- return restrictions != null ? restrictions : Bundle.EMPTY;
+ return restrictions != null ? new Bundle(restrictions) : new Bundle();
}
}
@Override
public void setUserRestrictions(Bundle restrictions, int userId) {
- checkProfileOwnerOrManageUsersPermission("setUserRestrictions");
+ checkManageUsersPermission("setUserRestrictions");
if (restrictions == null) return;
synchronized (mPackagesLock) {
@@ -491,51 +502,14 @@
* @param message used as message if SecurityException is thrown
* @throws SecurityException if the caller is not system or root
*/
- private final void checkManageUsersPermission(String message) {
+ private static final void checkManageUsersPermission(String message) {
final int uid = Binder.getCallingUid();
-
- if (missingManageUsersPermission(uid)) {
- throw new SecurityException("You need MANAGE_USERS permission to: " + message);
- }
- }
-
- /**
- * Enforces that only the system UID, root's UID, apps that have the
- * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS}
- * permission, the profile owner, or the device owner can make certain calls to the
- * UserManager.
- *
- * @param message used as message if SecurityException is thrown
- * @throws SecurityException if the caller is not system, root, or device
- * owner
- */
- private final void checkProfileOwnerOrManageUsersPermission(String message) {
- final int uid = Binder.getCallingUid();
- boolean isProfileOwner = false;
- if (mContext != null && mContext.getPackageManager() != null) {
- String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid);
- DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- if (dpm != null) {
- for (String pkg : pkgs) {
- if (dpm.isDeviceOwnerApp(pkg) || dpm.isProfileOwnerApp(pkg)) {
- isProfileOwner = true;
- }
- }
- }
- }
-
- if (missingManageUsersPermission(uid) && !isProfileOwner) {
- throw new SecurityException(
- "You need MANAGE_USERS permission or device owner privileges to: " + message);
- }
- }
-
- private boolean missingManageUsersPermission(int uid) {
- return uid != Process.SYSTEM_UID && uid != 0
+ if (uid != Process.SYSTEM_UID && uid != 0
&& ActivityManager.checkComponentPermission(
android.Manifest.permission.MANAGE_USERS,
- uid, -1, true) != PackageManager.PERMISSION_GRANTED;
+ uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need MANAGE_USERS permission to: " + message);
+ }
}
private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
@@ -1240,8 +1214,7 @@
public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
if (UserHandle.getCallingUserId() != userId
|| !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
- checkProfileOwnerOrManageUsersPermission(
- "Only system or device owner can get restrictions for other users/apps");
+ checkManageUsersPermission("Only system can get restrictions for other users/apps");
}
synchronized (mPackagesLock) {
// Read the restrictions from XML
@@ -1254,8 +1227,7 @@
int userId) {
if (UserHandle.getCallingUserId() != userId
|| !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
- checkProfileOwnerOrManageUsersPermission(
- "Only system or device owner can set restrictions for other users/apps");
+ checkManageUsersPermission("Only system can set restrictions for other users/apps");
}
synchronized (mPackagesLock) {
// Write the restrictions to XML
@@ -1357,8 +1329,7 @@
@Override
public void removeRestrictions() {
- checkProfileOwnerOrManageUsersPermission(
- "Only system or device owner can remove restrictions");
+ checkManageUsersPermission("Only system can remove restrictions");
final int userHandle = UserHandle.getCallingUserId();
removeRestrictionsForUser(userHandle, true);
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6700895..649f9dc 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,6 +19,9 @@
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -26,13 +29,18 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.TvContract;
import android.tv.ITvInputClient;
import android.tv.ITvInputManager;
import android.tv.ITvInputService;
@@ -41,13 +49,14 @@
import android.tv.ITvInputSessionCallback;
import android.tv.TvInputInfo;
import android.tv.TvInputService;
-import android.util.ArrayMap;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.InputChannel;
import android.view.Surface;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.SomeArgs;
+import com.android.server.IoThread;
import com.android.server.SystemService;
import java.util.ArrayList;
@@ -63,6 +72,8 @@
private final Context mContext;
+ private final ContentResolver mContentResolver;
+
// A global lock.
private final Object mLock = new Object();
@@ -72,10 +83,17 @@
// A map from user id to UserState.
private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
+ private final Handler mLogHandler;
+
public TvInputManagerService(Context context) {
super(context);
+
mContext = context;
+ mContentResolver = context.getContentResolver();
+ mLogHandler = new LogHandler(IoThread.get().getLooper());
+
registerBroadcastReceivers();
+
synchronized (mLock) {
mUserStates.put(mCurrentUserId, new UserState());
buildTvInputListLocked(mCurrentUserId);
@@ -274,6 +292,9 @@
Slog.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
+ ")");
}
+
+ final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
+
// Set up a callback to send the session token.
ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
@Override
@@ -286,30 +307,32 @@
if (session == null) {
removeSessionStateLocked(sessionToken, userId);
sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
- sessionState.seq, userId);
+ null, sessionState.seq, userId);
} else {
sendSessionTokenToClientLocked(sessionState.client, sessionState.name,
- sessionToken, sessionState.seq, userId);
+ sessionToken, channels[0], sessionState.seq, userId);
}
+ channels[0].dispose();
}
}
};
// Create a session. When failed, send a null token immediately.
try {
- service.createSession(callback);
+ service.createSession(channels[1], callback);
} catch (RemoteException e) {
Slog.e(TAG, "error in createSession", e);
removeSessionStateLocked(sessionToken, userId);
- sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
+ sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, null,
sessionState.seq, userId);
}
+ channels[1].dispose();
}
private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name,
- IBinder sessionToken, int seq, int userId) {
+ IBinder sessionToken, InputChannel channel, int seq, int userId) {
try {
- client.onSessionCreated(name, sessionToken, seq);
+ client.onSessionCreated(name, sessionToken, channel, seq);
} catch (RemoteException exception) {
Slog.e(TAG, "error in onSessionCreated", exception);
}
@@ -325,6 +348,14 @@
UserState userState = getUserStateLocked(userId);
SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
+ // Close the open log entry, if any.
+ if (sessionState.logUri != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = sessionState.logUri;
+ args.arg2 = System.currentTimeMillis();
+ mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
+ }
+
// Also remove the session token from the session token list of the current service.
ServiceState serviceState = userState.serviceStateMap.get(sessionState.name);
if (serviceState != null) {
@@ -384,7 +415,7 @@
UserState userState = getUserStateLocked(resolvedUserId);
ServiceState serviceState = userState.serviceStateMap.get(name);
if (serviceState == null) {
- serviceState = new ServiceState(name, resolvedUserId);
+ serviceState = new ServiceState(resolvedUserId);
userState.serviceStateMap.put(name, serviceState);
}
IBinder iBinder = client.asBinder();
@@ -468,7 +499,7 @@
// Also, add them to the session state map of the current service.
ServiceState serviceState = userState.serviceStateMap.get(name);
if (serviceState == null) {
- serviceState = new ServiceState(name, resolvedUserId);
+ serviceState = new ServiceState(resolvedUserId);
userState.serviceStateMap.put(name, serviceState);
}
serviceState.sessionTokens.add(sessionToken);
@@ -557,6 +588,35 @@
synchronized (mLock) {
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
+
+ long currentTime = System.currentTimeMillis();
+ long channelId = ContentUris.parseId(channelUri);
+
+ // Close the open log entry first, if any.
+ UserState userState = getUserStateLocked(resolvedUserId);
+ SessionState sessionState = userState.sessionStateMap.get(sessionToken);
+ if (sessionState.logUri != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = sessionState.logUri;
+ args.arg2 = currentTime;
+ mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
+ .sendToTarget();
+ }
+
+ // Create a log entry and fill it later.
+ ContentValues values = new ContentValues();
+ values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
+ currentTime);
+ values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 0);
+ values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
+
+ sessionState.logUri = mContentResolver.insert(
+ TvContract.WatchedPrograms.CONTENT_URI, values);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = sessionState.logUri;
+ args.arg2 = ContentUris.parseId(channelUri);
+ args.arg3 = currentTime;
+ mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
} catch (RemoteException e) {
Slog.e(TAG, "error in tune", e);
return;
@@ -652,7 +712,7 @@
private boolean bound;
private boolean available;
- private ServiceState(ComponentName name, int userId) {
+ private ServiceState(int userId) {
this.connection = new InputServiceConnection(userId);
}
}
@@ -664,6 +724,7 @@
private final int callingUid;
private ITvInputSession session;
+ private Uri logUri;
private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) {
this.name = name;
@@ -738,4 +799,147 @@
}
}
}
+
+ private final class LogHandler extends Handler {
+ private static final int MSG_OPEN_ENTRY = 1;
+ private static final int MSG_UPDATE_ENTRY = 2;
+ private static final int MSG_CLOSE_ENTRY = 3;
+
+ public LogHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_OPEN_ENTRY: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Uri uri = (Uri) args.arg1;
+ long channelId = (long) args.arg2;
+ long time = (long) args.arg3;
+ onOpenEntry(uri, channelId, time);
+ args.recycle();
+ return;
+ }
+ case MSG_UPDATE_ENTRY: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Uri uri = (Uri) args.arg1;
+ long channelId = (long) args.arg2;
+ long time = (long) args.arg3;
+ onUpdateEntry(uri, channelId, time);
+ args.recycle();
+ return;
+ }
+ case MSG_CLOSE_ENTRY: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Uri uri = (Uri) args.arg1;
+ long time = (long) args.arg2;
+ onCloseEntry(uri, time);
+ args.recycle();
+ return;
+ }
+ default: {
+ Slog.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+
+ private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
+ String[] projection = {
+ TvContract.Programs.TITLE,
+ TvContract.Programs.START_TIME_UTC_MILLIS,
+ TvContract.Programs.END_TIME_UTC_MILLIS,
+ TvContract.Programs.DESCRIPTION
+ };
+ String selection = TvContract.Programs.CHANNEL_ID + "=? AND "
+ + TvContract.Programs.START_TIME_UTC_MILLIS + "<=? AND "
+ + TvContract.Programs.END_TIME_UTC_MILLIS + ">?";
+ String[] selectionArgs = {
+ String.valueOf(channelId),
+ String.valueOf(watchStarttime),
+ String.valueOf(watchStarttime)
+ };
+ String sortOrder = TvContract.Programs.START_TIME_UTC_MILLIS + " ASC";
+ Cursor cursor = null;
+ try {
+ cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
+ selection, selectionArgs, sortOrder);
+ if (cursor != null && cursor.moveToNext()) {
+ ContentValues values = new ContentValues();
+ values.put(TvContract.WatchedPrograms.TITLE, cursor.getString(0));
+ values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, cursor.getLong(1));
+ long endTime = cursor.getLong(2);
+ values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
+ values.put(TvContract.WatchedPrograms.DESCRIPTION, cursor.getString(3));
+ mContentResolver.update(uri, values, null, null);
+
+ // Schedule an update when the current program ends.
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = uri;
+ args.arg2 = channelId;
+ args.arg3 = endTime;
+ Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
+ sendMessageDelayed(msg, endTime - System.currentTimeMillis());
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private void onUpdateEntry(Uri uri, long channelId, long time) {
+ String[] projection = {
+ TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.TITLE,
+ TvContract.WatchedPrograms.START_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.END_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.DESCRIPTION
+ };
+ Cursor cursor = null;
+ try {
+ cursor = mContentResolver.query(uri, projection, null, null, null);
+ if (cursor != null && cursor.moveToNext()) {
+ long watchStartTime = cursor.getLong(0);
+ long watchEndTime = cursor.getLong(1);
+ String title = cursor.getString(2);
+ long startTime = cursor.getLong(3);
+ long endTime = cursor.getLong(4);
+ String description = cursor.getString(5);
+
+ // Do nothing if the current log entry is already closed.
+ if (watchEndTime > 0) {
+ return;
+ }
+
+ // The current program has just ended. Create a (complete) log entry off the
+ // current entry.
+ ContentValues values = new ContentValues();
+ values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
+ watchStartTime);
+ values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, time);
+ values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
+ values.put(TvContract.WatchedPrograms.TITLE, title);
+ values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, startTime);
+ values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
+ values.put(TvContract.WatchedPrograms.DESCRIPTION, description);
+ mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ // Re-open the current log entry with the next program information.
+ onOpenEntry(uri, channelId, time);
+ }
+
+ private void onCloseEntry(Uri uri, long watchEndTime) {
+ ContentValues values = new ContentValues();
+ values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, watchEndTime);
+ mContentResolver.update(uri, values, null, null);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
index fdc604f..3c960c7 100644
--- a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
@@ -20,6 +20,8 @@
import android.content.Intent;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Base64;
import android.util.Slog;
@@ -28,9 +30,7 @@
import java.io.FileInputStream;
import java.io.IOException;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver {
@@ -110,16 +110,16 @@
File update = new File(updateDir.getParentFile(), "update");
File tmp = new File(updateDir.getParentFile(), "tmp");
if (current.exists()) {
- Libcore.os.symlink(updateDir.getPath(), update.getPath());
- Libcore.os.rename(update.getPath(), current.getPath());
+ Os.symlink(updateDir.getPath(), update.getPath());
+ Os.rename(update.getPath(), current.getPath());
} else {
- Libcore.os.symlink(updateDir.getPath(), current.getPath());
+ Os.symlink(updateDir.getPath(), current.getPath());
}
contexts.mkdirs();
backupContexts(contexts);
copyUpdate(contexts);
- Libcore.os.symlink(contexts.getPath(), tmp.getPath());
- Libcore.os.rename(tmp.getPath(), current.getPath());
+ Os.symlink(contexts.getPath(), tmp.getPath());
+ Os.rename(tmp.getPath(), current.getPath());
SystemProperties.set("selinux.reload_policy", "1");
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 87953fe..3eb2d5f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -25,6 +25,7 @@
import android.app.IWallpaperManagerCallback;
import android.app.PendingIntent;
import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
import android.app.backup.BackupManager;
import android.app.backup.WallpaperBackupHelper;
import android.content.BroadcastReceiver;
@@ -844,13 +845,7 @@
try {
if (componentName == null) {
- String defaultComponent =
- mContext.getString(com.android.internal.R.string.default_wallpaper_component);
- if (defaultComponent != null) {
- // See if there is a default wallpaper component specified
- componentName = ComponentName.unflattenFromString(defaultComponent);
- if (DEBUG) Slog.v(TAG, "Use default component wallpaper:" + componentName);
- }
+ componentName = WallpaperManager.getDefaultWallpaperComponent(mContext);
if (componentName == null) {
// Fall back to static image wallpaper
componentName = IMAGE_WALLPAPER;
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f17b2f4..9039236 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -162,13 +162,10 @@
private final Interpolator mThumbnailFadeoutInterpolator;
private int mCurrentUserId = 0;
- private boolean mUseAlternateThumbnailAnimation;
AppTransition(Context context, Handler h) {
mContext = context;
mH = h;
- mUseAlternateThumbnailAnimation =
- SystemProperties.getBoolean("persist.anim.use_alt_thumbnail", false);
mConfigShortAnimTime = context.getResources().getInteger(
com.android.internal.R.integer.config_shortAnimTime);
mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
@@ -668,7 +665,7 @@
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
int appWidth, int appHeight, int orientation,
- Rect containingFrame, Rect contentInsets) {
+ Rect containingFrame, Rect contentInsets, Configuration configuration) {
Animation a;
if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
a = loadAnimation(mNextAppTransitionPackage, enter ?
@@ -689,7 +686,8 @@
mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
- if (mUseAlternateThumbnailAnimation) {
+ boolean useAlternateThumbnailAnimation = (configuration.smallestScreenWidthDp < 600);
+ if (useAlternateThumbnailAnimation) {
a = createAlternateThumbnailEnterExitAnimationLocked(
getThumbnailTransitionState(enter), appWidth, appHeight, orientation,
transit, containingFrame, contentInsets);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b56377c..637beec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3190,7 +3190,7 @@
}
Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
- mCurConfiguration.orientation, containingFrame, contentInsets);
+ mCurConfiguration.orientation, containingFrame, contentInsets, mCurConfiguration);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -8660,8 +8660,7 @@
wtoken.deferClearAllDrawn = false;
}
- boolean useAlternateThumbnailAnimation =
- SystemProperties.getBoolean("persist.anim.use_alt_thumbnail", false);
+ boolean useAlternateThumbnailAnimation = (mCurConfiguration.smallestScreenWidthDp < 600);
AppWindowAnimator appAnimator =
topOpeningApp == null ? null : topOpeningApp.mAppAnimator;
Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail();
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 1b3887c..51583a5 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -11,7 +11,9 @@
$(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
$(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \
$(LOCAL_REL_DIR)/com_android_server_dreams_McuHal.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecController.cpp \
$(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiMhlController.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputApplicationHandle.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputWindowHandle.cpp \
diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
new file mode 100644
index 0000000..f3e8f3c
--- /dev/null
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "HdmiCecControllerJni"
+
+#define LOG_NDEBUG 1
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <hardware/hdmi_cec.h>
+
+namespace android {
+
+static struct {
+ jmethodID handleMessage;
+} gHdmiCecControllerClassInfo;
+
+
+class HdmiCecController {
+public:
+ HdmiCecController(jobject callbacksObj);
+
+private:
+ static void onReceived(const hdmi_event_t* event, void* arg);
+
+ jobject mCallbacksObj;
+};
+
+HdmiCecController::HdmiCecController(jobject callbacksObj) :
+ mCallbacksObj(callbacksObj) {
+}
+
+// static
+void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
+ HdmiCecController* handler = static_cast<HdmiCecController*>(arg);
+ if (handler == NULL) {
+ return;
+ }
+
+ // TODO: propagate message to Java layer.
+}
+
+
+//------------------------------------------------------------------------------
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj) {
+ // TODO: initialize hal and pass it to controller if ready.
+
+ HdmiCecController* controller = new HdmiCecController(
+ env->NewGlobalRef(callbacksObj));
+
+ return reinterpret_cast<jlong>(controller);
+}
+
+static JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeInit", "(Lcom/android/server/hdmi/HdmiCecController;)J",
+ (void *) nativeInit },
+};
+
+#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
+
+int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
+ LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+ return 0;
+}
+
+} /* namespace android */
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index bf9f7f4..1feb325 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -38,7 +38,9 @@
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_dreams_McuHal(JNIEnv* env);
+int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
int register_android_server_hdmi_HdmiCecService(JNIEnv* env);
+int register_android_server_hdmi_HdmiMhlController(JNIEnv* env);
};
using namespace android;
@@ -72,7 +74,10 @@
register_android_server_ConsumerIrService(env);
register_android_server_dreams_McuHal(env);
register_android_server_BatteryStatsService(env);
+ register_android_server_hdmi_HdmiCecController(env);
+ // TODO: remove this once replaces HdmiCecService with HdmiControlService.
register_android_server_hdmi_HdmiCecService(env);
+ register_android_server_hdmi_HdmiMhlController(env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
index 629dea2..3c46e40 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
@@ -208,7 +208,7 @@
int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USERID));
mProfileOwners.put(userId,
new OwnerInfo(
- profileOwnerPackageName, profileOwnerName, profileEnabled));
+ profileOwnerName, profileOwnerPackageName, profileEnabled));
} else {
throw new XmlPullParserException(
"Unexpected tag in device owner file: " + tag);
@@ -304,4 +304,4 @@
this.packageName = packageName;
}
}
-}
\ No newline at end of file
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b82a126..f1ee280 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3118,4 +3118,24 @@
}
}
}
+
+ @Override
+ public void setUserRestriction(ComponentName who, String key, boolean enabled) {
+ final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ UserManager um = UserManager.get(mContext);
+ long id = Binder.clearCallingIdentity();
+ try {
+ um.setUserRestriction(key, enabled, userHandle);
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f08d69f..3d82027 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -29,6 +29,7 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioService;
+import android.os.Build;
import android.os.Environment;
import android.os.FactoryTest;
import android.os.Handler;
@@ -59,8 +60,10 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.clipboard.ClipboardService;
import com.android.server.content.ContentService;
+import com.android.server.devicepolicy.DevicePolicyManagerService;
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;
@@ -110,10 +113,10 @@
*/
private static final String BACKUP_MANAGER_SERVICE_CLASS =
"com.android.server.backup.BackupManagerService$Lifecycle";
- private static final String DEVICE_POLICY_MANAGER_SERVICE_CLASS =
- "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 =
@@ -198,6 +201,10 @@
// as efficient as possible with its memory usage.
VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
+ // Some devices rely on runtime fingerprint generation, so make sure
+ // we've defined it before booting further.
+ Build.ensureFingerprintProperty();
+
// Within the system server, it is an error to access Environment paths without
// explicitly specifying a user.
Environment.setUserRequired(true);
@@ -290,6 +297,7 @@
// Activity manager runs the show.
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
+ mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
}
private void startCoreServices() {
@@ -546,9 +554,9 @@
}
try {
- if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
- mSystemServiceManager.startService(DEVICE_POLICY_MANAGER_SERVICE_CLASS);
- }
+ // Always start the Device Policy Manager, so that the API is compatible with
+ // API8.
+ mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting DevicePolicyService", e);
}
@@ -826,6 +834,15 @@
} catch (Throwable e) {
reportWtf("starting Recognition Service", e);
}
+
+ try {
+ if (pm.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) {
+ Slog.i(TAG, "Voice Recognition Service");
+ mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+ }
+ } catch (Throwable e) {
+ reportWtf("starting Voice Recognition Service", e);
+ }
}
try {
@@ -919,6 +936,12 @@
}
try {
+ mSystemServiceManager.startService(HdmiControlService.class);
+ } catch (Throwable e) {
+ reportWtf("starting HdmiControlService", e);
+ }
+
+ try {
Slog.i(TAG, "TvInputManagerService");
mSystemServiceManager.startService(TvInputManagerService.class);
} catch (Throwable e) {
diff --git a/services/voiceinteraction/Android.mk b/services/voiceinteraction/Android.mk
new file mode 100644
index 0000000..c9e5dd0
--- /dev/null
+++ b/services/voiceinteraction/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.voiceinteraction
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,java)
+
+LOCAL_JAVA_LIBRARIES := services.core
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
new file mode 100644
index 0000000..045c0f6
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -0,0 +1,247 @@
+/*
+ * 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.Manifest;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+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 void startVoiceActivity(Intent intent, String resolvedType,
+ IVoiceInteractionService service, Bundle args) {
+ 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 {
+ mImpl.startVoiceActivityLocked(callingPid, callingUid,
+ intent, resolvedType, args);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public int deliverNewSession(IBinder token, IVoiceInteractionSession session,
+ IVoiceInteractor interactor) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "deliverNewSession without running voice interaction service");
+ return ActivityManager.START_CANCELED;
+ }
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mImpl.deliverNewSessionLocked(callingPid, callingUid, token, session,
+ interactor);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump PowerManager from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ synchronized (this) {
+ pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)\n");
+ if (mImpl == null) {
+ pw.println(" (No active implementation)");
+ return;
+ }
+ mImpl.dumpLocked(fd, pw, args);
+ }
+ }
+
+ class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.VOICE_INTERACTION_SERVICE), false, this);
+ }
+
+ @Override public void onChange(boolean selfChange) {
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ switchImplementationIfNeededLocked();
+ }
+ }
+ }
+
+ PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+ return super.onHandleForceStop(intent, packages, uid, doit);
+ }
+
+ @Override
+ public void onHandleUserStop(Intent intent, int userHandle) {
+ super.onHandleUserStop(intent, userHandle);
+ }
+
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ super.onPackageDisappeared(packageName, reason);
+ }
+
+ @Override
+ public void onPackageAppeared(String packageName, int reason) {
+ super.onPackageAppeared(packageName, reason);
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ super.onPackageModified(packageName);
+ }
+
+ @Override
+ public void onSomePackagesChanged() {
+ super.onSomePackagesChanged();
+ }
+ };
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
new file mode 100644
index 0000000..6bbd1c1
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -0,0 +1,238 @@
+/*
+ * 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.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.Binder;
+import android.os.Bundle;
+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.IVoiceInteractionSessionService;
+import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
+import android.util.Slog;
+import com.android.internal.app.IVoiceInteractor;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+class VoiceInteractionManagerServiceImpl {
+ final static String TAG = "VoiceInteractionServiceManager";
+
+ final boolean mValid;
+
+ final Context mContext;
+ final Handler mHandler;
+ final Object mLock;
+ final int mUser;
+ final ComponentName mComponent;
+ final IActivityManager mAm;
+ final VoiceInteractionServiceInfo mInfo;
+ final ComponentName mSessionComponentName;
+ boolean mBound = false;
+ IVoiceInteractionService mService;
+
+ SessionConnection mActiveSession;
+
+ 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;
+ }
+ };
+
+ final class SessionConnection implements ServiceConnection {
+ final IBinder mToken = new Binder();
+ final Intent mIntent;
+ final String mResolvedType;
+ final Bundle mArgs;
+ boolean mBound;
+ IVoiceInteractionSessionService mService;
+ IVoiceInteractionSession mSession;
+ IVoiceInteractor mInteractor;
+
+ SessionConnection(Intent intent, String resolvedType, Bundle args) {
+ mIntent = intent;
+ mResolvedType = resolvedType;
+ mArgs = args;
+ Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+ serviceIntent.setComponent(mSessionComponentName);
+ mBound = mContext.bindServiceAsUser(serviceIntent, this,
+ Context.BIND_AUTO_CREATE, new UserHandle(mUser));
+ if (!mBound) {
+ Slog.w(TAG, "Failed binding to voice interaction session service " + mComponent);
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mService = IVoiceInteractionSessionService.Stub.asInterface(service);
+ if (mActiveSession == this) {
+ try {
+ mService.newSession(mToken, mArgs);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed making new session", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+
+ public void cancel() {
+ if (mBound) {
+ mContext.unbindService(this);
+ mBound = false;
+ mService = null;
+ mSession = null;
+ mInteractor = null;
+ }
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mToken="); pw.println(mToken);
+ pw.print(prefix); pw.print("mIntent="); pw.println(mIntent);
+ pw.print(" mResolvedType="); pw.println(mResolvedType);
+ pw.print(prefix); pw.print("mArgs="); pw.println(mArgs);
+ pw.print(prefix); pw.print("mBound="); pw.println(mBound);
+ if (mBound) {
+ pw.print(prefix); pw.print("mService="); pw.println(mService);
+ pw.print(prefix); pw.print("mSession="); pw.println(mSession);
+ pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
+ }
+ }
+ };
+
+ VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock,
+ int userHandle, ComponentName service) {
+ mContext = context;
+ mHandler = handler;
+ mLock = lock;
+ mUser = userHandle;
+ mComponent = service;
+ mAm = ActivityManagerNative.getDefault();
+ VoiceInteractionServiceInfo info;
+ try {
+ info = new VoiceInteractionServiceInfo(context.getPackageManager(), service);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Voice interaction service not found: " + service);
+ mInfo = null;
+ mSessionComponentName = null;
+ mValid = false;
+ return;
+ }
+ mInfo = info;
+ if (mInfo.getParseError() != null) {
+ Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError());
+ mSessionComponentName = null;
+ mValid = false;
+ return;
+ }
+ mValid = true;
+ mSessionComponentName = new ComponentName(service.getPackageName(),
+ mInfo.getSessionService());
+ }
+
+ public void startVoiceActivityLocked(int callingPid, int callingUid, Intent intent,
+ String resolvedType, Bundle args) {
+ if (mActiveSession != null) {
+ mActiveSession.cancel();
+ mActiveSession = null;
+ }
+ mActiveSession = new SessionConnection(intent, resolvedType, args);
+ intent.addCategory(Intent.CATEGORY_VOICE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+
+ public int deliverNewSessionLocked(int callingPid, int callingUid, IBinder token,
+ IVoiceInteractionSession session, IVoiceInteractor interactor) {
+ try {
+ if (mActiveSession == null || token != mActiveSession.mToken) {
+ Slog.w(TAG, "deliverNewSession does not match active session");
+ return ActivityManager.START_CANCELED;
+ }
+ mActiveSession.mSession = session;
+ mActiveSession.mInteractor = interactor;
+ return mAm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid,
+ mActiveSession.mIntent, mActiveSession.mResolvedType,
+ mActiveSession.mSession, mActiveSession.mInteractor,
+ 0, null, null, null, mUser);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Unexpected remote error", e);
+ }
+ }
+
+ public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!mValid) {
+ pw.print(" NOT VALID: ");
+ if (mInfo == null) {
+ pw.println("no info");
+ } else {
+ pw.println(mInfo.getParseError());
+ }
+ return;
+ }
+ pw.print(" mComponent="); pw.println(mComponent.flattenToShortString());
+ pw.print(" Session service="); pw.println(mInfo.getSessionService());
+ pw.print(" Settings activity="); pw.println(mInfo.getSettingsActivity());
+ pw.print(" mBound="); pw.print(mBound); pw.print(" mService="); pw.println(mService);
+ if (mActiveSession != null) {
+ pw.println(" Active session:");
+ mActiveSession.dump(" ", pw);
+ }
+ }
+
+ void startLocked() {
+ Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+ intent.setComponent(mComponent);
+ mBound = mContext.bindServiceAsUser(intent, mConnection,
+ Context.BIND_AUTO_CREATE, new UserHandle(mUser));
+ if (!mBound) {
+ Slog.w(TAG, "Failed binding to voice interaction service " + mComponent);
+ }
+ }
+
+ void shutdownLocked() {
+ if (mBound) {
+ mContext.unbindService(mConnection);
+ mBound = false;
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d5c7caa..407a8d1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1094,6 +1094,10 @@
public static final int SIM_STATE_NETWORK_LOCKED = 4;
/** SIM card state: Ready */
public static final int SIM_STATE_READY = 5;
+ /** SIM card state: SIM Card Error, Sim Card is present but faulty
+ *@hide
+ */
+ public static final int SIM_STATE_CARD_IO_ERROR = 6;
/**
* @return true if a ICC card is present
@@ -1120,6 +1124,7 @@
* @see #SIM_STATE_PUK_REQUIRED
* @see #SIM_STATE_NETWORK_LOCKED
* @see #SIM_STATE_READY
+ * @see #SIM_STATE_CARD_IO_ERROR
*/
public int getSimState() {
String prop = SystemProperties.get(TelephonyProperties.PROPERTY_SIM_STATE);
@@ -1138,6 +1143,9 @@
else if ("READY".equals(prop)) {
return SIM_STATE_READY;
}
+ else if ("CARD_IO_ERROR".equals(prop)) {
+ return SIM_STATE_CARD_IO_ERROR;
+ }
else {
return SIM_STATE_UNKNOWN;
}
diff --git a/telephony/java/com/android/internal/telephony/IccCardConstants.java b/telephony/java/com/android/internal/telephony/IccCardConstants.java
index 236bb2f..8029713 100644
--- a/telephony/java/com/android/internal/telephony/IccCardConstants.java
+++ b/telephony/java/com/android/internal/telephony/IccCardConstants.java
@@ -28,6 +28,8 @@
public static final String INTENT_VALUE_ICC_NOT_READY = "NOT_READY";
/* ABSENT means ICC is missing */
public static final String INTENT_VALUE_ICC_ABSENT = "ABSENT";
+ /* CARD_IO_ERROR means for three consecutive times there was SIM IO error */
+ static public final String INTENT_VALUE_ICC_CARD_IO_ERROR = "CARD_IO_ERROR";
/* LOCKED means ICC is locked by pin or by network */
public static final String INTENT_VALUE_ICC_LOCKED = "LOCKED";
/* READY means ICC is ready to access */
@@ -63,7 +65,8 @@
NETWORK_LOCKED,
READY,
NOT_READY,
- PERM_DISABLED;
+ PERM_DISABLED,
+ CARD_IO_ERROR;
public boolean isPinLocked() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED));
@@ -72,7 +75,7 @@
public boolean iccCardExist() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED)
|| (this == NETWORK_LOCKED) || (this == READY)
- || (this == PERM_DISABLED));
+ || (this == PERM_DISABLED) || (this == CARD_IO_ERROR));
}
}
}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index ac741e7..5c2583b 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -873,9 +873,6 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.test.hwui.TEST" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
diff --git a/tests/HwAccelerationTest/res/layout/projection_clipping.xml b/tests/HwAccelerationTest/res/layout/projection_clipping.xml
index 7caf90a..7177fc8f 100644
--- a/tests/HwAccelerationTest/res/layout/projection_clipping.xml
+++ b/tests/HwAccelerationTest/res/layout/projection_clipping.xml
@@ -6,7 +6,7 @@
<FrameLayout
android:translationX="50dp"
android:translationY="50dp"
- android:translationZ="30dp"
+ android:elevation="30dp"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@drawable/round_rect_background">
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
index 09531fd..1d209dd 100644
--- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -1,17 +1,13 @@
package com.example.renderthread;
-import android.animation.TimeInterpolator;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
-import android.os.SystemClock;
-import android.view.RenderNode;
import android.view.HardwareRenderer;
-import android.view.ThreadedRenderer;
+import android.view.RenderNodeAnimator;
import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
@@ -23,6 +19,8 @@
public class MainActivity extends Activity implements OnItemClickListener {
+ static final int TRANSLATION_Y = 1;
+ static final int DELTA_TYPE_DELTA = 1;
static final int DURATION = 400;
static final String KEY_NAME = "name";
@@ -66,82 +64,21 @@
}
}
- private static class DisplayListAnimator {
- private static final TimeInterpolator sDefaultInterpolator =
- new AccelerateDecelerateInterpolator();
-
- RenderNode mDisplayList;
- float mFromValue;
- float mDelta;
- long mDuration = DURATION * 2;
- long mStartTime;
-
- DisplayListAnimator(View view, float translateXBy) {
- mDelta = translateXBy;
- mFromValue = view.getTranslationY();
- mDisplayList = view.getDisplayList();
- }
-
- boolean animate(long currentTime) {
- if (mStartTime == 0) mStartTime = currentTime;
-
- float fraction = (float)(currentTime - mStartTime) / mDuration;
- if (fraction > 1) {
- return false;
- }
- fraction = sDefaultInterpolator.getInterpolation(fraction);
- float translation = mFromValue + (mDelta * fraction);
- mDisplayList.setTranslationY(translation);
- return fraction < 1f;
- }
- }
-
- private static class AnimationExecutor implements Runnable {
- DisplayListAnimator[] mAnimations;
- ThreadedRenderer mRenderer;
-
- AnimationExecutor(ThreadedRenderer renderer, DisplayListAnimator[] animations) {
- mRenderer = renderer;
- mAnimations = animations;
- ThreadedRenderer.postToRenderThread(this);
- }
-
- @Override
- public void run() {
- boolean hasMore = false;
- long now = SystemClock.uptimeMillis();
- for (DisplayListAnimator animator : mAnimations) {
- hasMore |= animator.animate(now);
- }
- mRenderer.repeatLastDraw();
- if (hasMore) {
- ThreadedRenderer.postToRenderThread(this);
- }
- }
-
- }
-
@Override
public void onItemClick(final AdapterView<?> adapterView, View clickedView,
int clickedPosition, long clickedId) {
int topPosition = adapterView.getFirstVisiblePosition();
int dy = adapterView.getHeight();
- final DisplayListAnimator[] animators = new DisplayListAnimator[adapterView.getChildCount()];
for (int i = 0; i < adapterView.getChildCount(); i++) {
int pos = topPosition + i;
View child = adapterView.getChildAt(i);
float delta = (pos - clickedPosition) * 1.1f;
if (delta == 0) delta = -1;
- animators[i] = new DisplayListAnimator(child, dy * delta);
+ RenderNodeAnimator animator = new RenderNodeAnimator(
+ TRANSLATION_Y, DELTA_TYPE_DELTA, dy * delta);
+ animator.setDuration(DURATION);
+ animator.start(child);
}
- adapterView.invalidate();
- adapterView.post(new Runnable() {
-
- @Override
- public void run() {
- new AnimationExecutor((ThreadedRenderer) adapterView.getHardwareRenderer(), animators);
- }
- });
//mHandler.postDelayed(mLaunchActivity, (long) (DURATION * .4));
mLaunchActivity.run();
}
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml
index bcf3ae6..0be6755 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 @@
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 @@
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 @@
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 @@
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 @@
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:durations="2000,0,2000"
+ android:sequence="one,one,three,three" />
+ <animation
+ android:durations="2000,0,2000"
+ android:sequence="two,two,two,two" />
- <animation
- android:sequence="one,one,three,three"
- android:durations="2000,0,2000"/>
- <animation
- android:sequence="two,two,two,two"
- android:durations="2000,0,2000"/>
-</vector>
+</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 09934de..b3c91a88 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 f17f67a..7aca169 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 8787b34..a4403c5 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 89748d5..207879d 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 43dda52..4a2ed90 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 0f1f149..6ebd56b 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 6bc946f..3c92d25 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 c9c8e8a..7e757a5 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 83dfbd2..69212f5 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 013254f..2dca48d 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 aba7e5f..b8af7e2 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 8897181..22ce795 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 @@
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 2c7ebbd..042173c 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 @@
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 e4cf78c..6b6f43d 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 @@
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 cec12ba..ba8ebca 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 @@
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 5fe1fb6..896a938 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 @@
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 6beb9d8..a9091ab 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 @@
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 2c1a28e..ab58c06 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 @@
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 0000000..8decca7
--- /dev/null
+++ b/tests/VoiceInteraction/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := VoiceInteraction
+
+include $(BUILD_PACKAGE)
diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml
new file mode 100644
index 0000000..ac0f701
--- /dev/null
+++ b/tests/VoiceInteraction/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<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">
+ <meta-data android:name="android.voice_interaction"
+ android:resource="@xml/interaction_service" />
+ <intent-filter>
+ <action android:name="android.service.voice.VoiceInteractionService" />
+ </intent-filter>
+ </service>
+ <service android:name="MainInteractionSessionService"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:process=":session">
+ </service>
+ <activity android:name="TestInteractionActivity" android:label="Voice Interaction Target">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.VOICE" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/VoiceInteraction/res/layout/main.xml b/tests/VoiceInteraction/res/layout/main.xml
new file mode 100644
index 0000000..3d7a418
--- /dev/null
+++ b/tests/VoiceInteraction/res/layout/main.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <Button android:id="@+id/start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/start"
+ />
+
+</LinearLayout>
+
+
diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml
new file mode 100644
index 0000000..2abf65194
--- /dev/null
+++ b/tests/VoiceInteraction/res/layout/test_interaction.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="We are interacting!"
+ />
+
+ <TextView android:id="@+id/log"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ android:layout_marginTop="10dp"
+ android:textSize="12sp"
+ android:textColor="#ffffffff"
+ />
+
+</LinearLayout>
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
new file mode 100644
index 0000000..12edb31
--- /dev/null
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <string name="start">Start!</string>
+
+</resources>
+
diff --git a/tests/VoiceInteraction/res/xml/interaction_service.xml b/tests/VoiceInteraction/res/xml/interaction_service.xml
new file mode 100644
index 0000000..45bd994d
--- /dev/null
+++ b/tests/VoiceInteraction/res/xml/interaction_service.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:sessionService="com.android.test.voiceinteraction.MainInteractionSessionService" />
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
new file mode 100644
index 0000000..008d97b
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -0,0 +1,38 @@
+/*
+ * 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), null);
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
new file mode 100644
index 0000000..0fc563b
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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";
+
+ final Bundle mArgs;
+
+ MainInteractionSession(Context context, Bundle args) {
+ super(context);
+ mArgs = args;
+ }
+
+ @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(true, null);
+ }
+
+ @Override
+ public void onCancel(Request request) {
+ Log.i(TAG, "onCancel");
+ request.sendCancelResult();
+ }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java
new file mode 100644
index 0000000..8864d71
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java
@@ -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.test.voiceinteraction;
+
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class MainInteractionSessionService extends VoiceInteractionSessionService {
+ @Override
+ public VoiceInteractionSession onNewSession(Bundle args) {
+ return new MainInteractionSession(this, args);
+ }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
new file mode 100644
index 0000000..9c772ff
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -0,0 +1,68 @@
+/*
+ * 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);
+
+ if (!isVoiceInteraction()) {
+ Log.w(TAG, "Not running as a voice interaction!");
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.test_interaction);
+
+ mInteractor = getVoiceInteractor();
+ VoiceInteractor.ConfirmationRequest req = new VoiceInteractor.ConfirmationRequest(
+ "This is a confirmation", null) {
+ @Override
+ public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ getActivity().finish();
+ }
+
+ @Override
+ public void onConfirmationResult(boolean confirmed, Bundle result) {
+ Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result);
+ getActivity().finish();
+ }
+ };
+ mInteractor.submitRequest(req);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java
new file mode 100644
index 0000000..5d212a4
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.voiceinteraction;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+public class VoiceInteractionMain extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main);
+ findViewById(R.id.start).setOnClickListener(mStartListener);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ View.OnClickListener mStartListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ startService(new Intent(VoiceInteractionMain.this, MainInteractionService.class));
+ }
+ };
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 6d9d62e..d0581f6 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -2462,7 +2462,7 @@
status_t
writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile,
- const char* startTag, const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs)
+ const Vector<String8>& startTags, const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs)
{
status_t err;
ResXMLTree tree;
@@ -2476,15 +2476,18 @@
tree.restart();
- if (startTag != NULL) {
+ if (!startTags.isEmpty()) {
bool haveStart = false;
while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
if (code != ResXMLTree::START_TAG) {
continue;
}
String8 tag(tree.getElementName(&len));
- if (tag == startTag) {
- haveStart = true;
+ const size_t numStartTags = startTags.size();
+ for (size_t i = 0; i < numStartTags; i++) {
+ if (tag == startTags[i]) {
+ haveStart = true;
+ }
}
break;
}
@@ -2571,15 +2574,17 @@
for (size_t k=0; k<K; k++) {
const sp<AaptDir>& d = dirs.itemAt(k);
const String8& dirName = d->getLeaf();
+ Vector<String8> startTags;
const char* startTag = NULL;
const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs = NULL;
if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) {
tagAttrPairs = &kLayoutTagAttrPairs;
} else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) {
- startTag = "PreferenceScreen";
+ startTags.add(String8("PreferenceScreen"));
+ startTags.add(String8("preference-headers"));
tagAttrPairs = &kXmlTagAttrPairs;
} else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) {
- startTag = "menu";
+ startTags.add(String8("menu"));
tagAttrPairs = NULL;
} else {
continue;
@@ -2592,7 +2597,7 @@
const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles();
const size_t M = files.size();
for (size_t j=0; j<M; j++) {
- err = writeProguardForXml(keep, files.valueAt(j), startTag, tagAttrPairs);
+ err = writeProguardForXml(keep, files.valueAt(j), startTags, tagAttrPairs);
if (err < 0) {
return err;
}