Merge "Keep screen on when in car mode and the device is powered."
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 91b1c4e..5fb2aae 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -29,6 +29,7 @@
import android.database.SQLException;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -217,6 +218,13 @@
return ContentProvider.this.openAssetFile(uri, mode);
}
+ /**
+ * @hide
+ */
+ public Bundle call(String method, String request, Bundle args) {
+ return ContentProvider.this.call(method, request, args);
+ }
+
private void enforceReadPermission(Uri uri) {
final int uid = Binder.getCallingUid();
if (uid == mMyUid) {
@@ -748,4 +756,18 @@
}
return results;
}
-}
\ No newline at end of file
+
+ /**
+ * @hide -- until interface has proven itself
+ *
+ * Call an provider-defined method. This can be used to implement
+ * interfaces that are cheaper than using a Cursor.
+ *
+ * @param method Method name to call. Opaque to framework.
+ * @param request Nullable String argument passed to method.
+ * @param args Nullable Bundle argument passed to method.
+ */
+ public Bundle call(String method, String request, Bundle args) {
+ return null;
+ }
+}
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index bacb684..81b8055 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -26,6 +26,7 @@
import android.database.IContentObserver;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
@@ -222,6 +223,21 @@
}
return true;
}
+
+ case CALL_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+
+ String method = data.readString();
+ String stringArg = data.readString();
+ Bundle args = data.readBundle();
+
+ Bundle responseBundle = call(method, stringArg, args);
+
+ reply.writeNoException();
+ reply.writeBundle(responseBundle);
+ return true;
+ }
}
} catch (Exception e) {
DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -485,6 +501,22 @@
return fd;
}
+ public Bundle call(String method, String request, Bundle args)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(method);
+ data.writeString(request);
+ data.writeBundle(args);
+
+ mRemote.transact(IContentProvider.CALL_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ return reply.readBundle();
+ }
+
private IBinder mRemote;
}
-
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 1b0ef34..bcef75e 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -736,7 +736,7 @@
* @hide
*/
public final IContentProvider acquireProvider(String name) {
- if(name == null) {
+ if (name == null) {
return null;
}
return acquireProvider(mContext, name);
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 1b0ca58..67e7581 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -22,10 +22,11 @@
import android.database.IBulkCursor;
import android.database.IContentObserver;
import android.net.Uri;
-import android.os.RemoteException;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import java.io.FileNotFoundException;
import java.util.ArrayList;
@@ -58,6 +59,17 @@
throws RemoteException, FileNotFoundException;
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
+ /**
+ * @hide -- until interface has proven itself
+ *
+ * Call an provider-defined method. This can be used to implement
+ * interfaces that are cheaper than using a Cursor.
+ *
+ * @param method Method name to call. Opaque to framework.
+ * @param request Nullable String argument passed to method.
+ * @param args Nullable Bundle argument passed to method.
+ */
+ public Bundle call(String method, String request, Bundle args) throws RemoteException;
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
@@ -71,4 +83,5 @@
static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13;
static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19;
+ static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20;
}
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index d6fe107..0ec1c74 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -132,6 +132,45 @@
}
/**
+ * Make a Bundle for a single key/value pair.
+ *
+ * @hide
+ */
+ public static Bundle forPair(String key, String value) {
+ // TODO: optimize this case.
+ Bundle b = new Bundle(1);
+ b.putString(key, value);
+ return b;
+ }
+
+ /**
+ * TODO: optimize this later (getting just the value part of a Bundle
+ * with a single pair) once Bundle.forPair() above is implemented
+ * with a special single-value Map implementation/serialization.
+ *
+ * Note: value in single-pair Bundle may be null.
+ *
+ * @hide
+ */
+ public String getPairValue() {
+ unparcel();
+ int size = mMap.size();
+ if (size > 1) {
+ Log.w(LOG_TAG, "getPairValue() used on Bundle with multiple pairs.");
+ }
+ if (size == 0) {
+ return null;
+ }
+ Object o = mMap.values().iterator().next();
+ try {
+ return (String) o;
+ } catch (ClassCastException e) {
+ typeWarning("getPairValue()", o, "String", e);
+ return null;
+ }
+ }
+
+ /**
* Changes the ClassLoader this Bundle uses when instantiating objects.
*
* @param loader An explicit ClassLoader to use when instantiating objects
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7df509f..726f98a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -27,6 +27,7 @@
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -492,6 +493,16 @@
// End of Intent actions for Settings
/**
+ * @hide - Private call() method on SettingsProvider to read from 'system' table.
+ */
+ public static final String CALL_METHOD_GET_SYSTEM = "GET_system";
+
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'secure' table.
+ */
+ public static final String CALL_METHOD_GET_SECURE = "GET_secure";
+
+ /**
* Activity Extra: Limit available options in launched activity based on the given authority.
* <p>
* This can be passed as an extra field in an Activity Intent with one or more syncable content
@@ -544,23 +555,36 @@
}
}
+ // Thread-safe.
private static class NameValueCache {
private final String mVersionSystemProperty;
private final Uri mUri;
- // Must synchronize(mValues) to access mValues and mValuesVersion.
+ private static final String[] SELECT_VALUE =
+ new String[] { Settings.NameValueTable.VALUE };
+ private static final String NAME_EQ_PLACEHOLDER = "name=?";
+
+ // Must synchronize on 'this' to access mValues and mValuesVersion.
private final HashMap<String, String> mValues = new HashMap<String, String>();
private long mValuesVersion = 0;
- public NameValueCache(String versionSystemProperty, Uri uri) {
+ // Initially null; set lazily and held forever. Synchronized on 'this'.
+ private IContentProvider mContentProvider = null;
+
+ // The method we'll call (or null, to not use) on the provider
+ // for the fast path of retrieving settings.
+ private final String mCallCommand;
+
+ public NameValueCache(String versionSystemProperty, Uri uri, String callCommand) {
mVersionSystemProperty = versionSystemProperty;
mUri = uri;
+ mCallCommand = callCommand;
}
public String getString(ContentResolver cr, String name) {
long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0);
- synchronized (mValues) {
+ synchronized (this) {
if (mValuesVersion != newValuesVersion) {
if (LOCAL_LOGV) {
Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " +
@@ -576,17 +600,47 @@
}
}
+ IContentProvider cp = null;
+ synchronized (this) {
+ cp = mContentProvider;
+ if (cp == null) {
+ cp = mContentProvider = cr.acquireProvider(mUri.getAuthority());
+ }
+ }
+
+ // Try the fast path first, not using query(). If this
+ // fails (alternate Settings provider that doesn't support
+ // this interface?) then we fall back to the query/table
+ // interface.
+ if (mCallCommand != null) {
+ try {
+ Bundle b = cp.call(mCallCommand, name, null);
+ if (b != null) {
+ String value = b.getPairValue();
+ synchronized (this) {
+ mValues.put(name, value);
+ }
+ return value;
+ }
+ // If the response Bundle is null, we fall through
+ // to the query interface below.
+ } catch (RemoteException e) {
+ // Not supported by the remote side? Fall through
+ // to query().
+ }
+ }
+
Cursor c = null;
try {
- c = cr.query(mUri, new String[] { Settings.NameValueTable.VALUE },
- Settings.NameValueTable.NAME + "=?", new String[]{name}, null);
+ c = cp.query(mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+ new String[]{name}, null);
if (c == null) {
Log.w(TAG, "Can't get key " + name + " from " + mUri);
return null;
}
String value = c.moveToNext() ? c.getString(0) : null;
- synchronized (mValues) {
+ synchronized (this) {
mValues.put(name, value);
}
if (LOCAL_LOGV) {
@@ -594,7 +648,7 @@
name + " = " + (value == null ? "(null)" : value));
}
return value;
- } catch (SQLException e) {
+ } catch (RemoteException e) {
Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
return null; // Return null, but don't cache it.
} finally {
@@ -611,7 +665,8 @@
public static final class System extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
- private static volatile NameValueCache mNameValueCache = null;
+ // Populated lazily, guarded by class object:
+ private static NameValueCache sNameValueCache = null;
private static final HashSet<String> MOVED_TO_SECURE;
static {
@@ -660,10 +715,11 @@
+ " to android.provider.Settings.Secure, returning read-only value.");
return Secure.getString(resolver, name);
}
- if (mNameValueCache == null) {
- mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
+ if (sNameValueCache == null) {
+ sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI,
+ CALL_METHOD_GET_SYSTEM);
}
- return mNameValueCache.getString(resolver, name);
+ return sNameValueCache.getString(resolver, name);
}
/**
@@ -1905,7 +1961,8 @@
public static final class Secure extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
- private static volatile NameValueCache mNameValueCache = null;
+ // Populated lazily, guarded by class object:
+ private static NameValueCache sNameValueCache = null;
/**
* Look up a name in the database.
@@ -1914,10 +1971,11 @@
* @return the corresponding value, or null if not present
*/
public synchronized static String getString(ContentResolver resolver, String name) {
- if (mNameValueCache == null) {
- mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
+ if (sNameValueCache == null) {
+ sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI,
+ CALL_METHOD_GET_SECURE);
}
- return mNameValueCache.getString(resolver, name);
+ return sNameValueCache.getString(resolver, name);
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6a95831..b8d71b9 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -24,12 +24,17 @@
import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Interpolator;
+import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
import android.net.Uri;
@@ -70,7 +75,7 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView;
-import android.widget.Scroller;
+import android.widget.OverScroller;
import android.widget.Toast;
import android.widget.ZoomButtonsController;
import android.widget.ZoomControls;
@@ -455,7 +460,9 @@
// time for the longest scroll animation
private static final int MAX_DURATION = 750; // milliseconds
private static final int SLIDE_TITLE_DURATION = 500; // milliseconds
- private Scroller mScroller;
+ private OverScroller mScroller;
+ private boolean mInOverScrollMode = false;
+ private static Paint mOverScrollBackground;
private boolean mWrapContent;
private static final int MOTIONLESS_FALSE = 0;
@@ -810,7 +817,7 @@
mViewManager = new ViewManager(this);
mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
mDatabase = WebViewDatabase.getInstance(context);
- mScroller = new Scroller(context);
+ mScroller = new OverScroller(context);
mZoomButtonsController = new ZoomButtonsController(this);
mZoomButtonsController.setOnZoomListener(mZoomListener);
@@ -1024,7 +1031,8 @@
* Return the amount of the titlebarview (if any) that is visible
*/
private int getVisibleTitleHeight() {
- return Math.max(getTitleHeight() - mScrollY, 0);
+ // need to restrict mScrollY due to over scroll
+ return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
}
/*
@@ -1867,11 +1875,13 @@
// Expects x in view coordinates
private int pinLocX(int x) {
+ if (mInOverScrollMode) return x;
return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
}
// Expects y in view coordinates
private int pinLocY(int y) {
+ if (mInOverScrollMode) return y;
int titleH = getTitleHeight();
// if the titlebar is still visible, just pin against 0
if (y <= titleH) {
@@ -2269,6 +2279,24 @@
scrollBar.draw(canvas);
}
+ @Override
+ protected void onOverscrolled(int scrollX, int scrollY, boolean clampedX,
+ boolean clampedY) {
+ mInOverScrollMode = false;
+ int maxX = computeMaxScrollX();
+ if (Math.abs(mMinZoomScale - mMaxZoomScale) < 0.01f && maxX == 0) {
+ // do not over scroll x if the page can't be zoomed and it just fits
+ // the screen
+ scrollX = pinLocX(scrollX);
+ } else if (scrollX < 0 || scrollX > maxX) {
+ mInOverScrollMode = true;
+ }
+ if (scrollY < 0 || scrollY > computeMaxScrollY()) {
+ mInOverScrollMode = true;
+ }
+ super.scrollTo(scrollX, scrollY);
+ }
+
/**
* Get the url for the current page. This is not always the same as the url
* passed to WebViewClient.onPageStarted because although the load for
@@ -2611,13 +2639,14 @@
if (mScroller.computeScrollOffset()) {
int oldX = mScrollX;
int oldY = mScrollY;
- mScrollX = mScroller.getCurrX();
- mScrollY = mScroller.getCurrY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
postInvalidate(); // So we draw again
- if (oldX != mScrollX || oldY != mScrollY) {
- // as onScrollChanged() is not called, sendOurVisibleRect()
- // needs to be call explicitly
- sendOurVisibleRect();
+ if (oldX != x || oldY != y) {
+ overscrollBy(x - oldX, y - oldY, oldX, oldY,
+ computeMaxScrollX(), computeMaxScrollY(),
+ getViewWidth() / 3, getViewHeight() / 3);
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
}
} else {
super.computeScroll();
@@ -3028,8 +3057,13 @@
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (child == mTitleBar) {
// When drawing the title bar, move it horizontally to always show
- // at the top of the WebView.
+ // at the top of the WebView. While overscroll, stick the title bar
+ // on the top otherwise we may have two during loading, one is drawn
+ // here, another is drawn by the Browser.
mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft());
+ if (mScrollY <= 0) {
+ mTitleBar.offsetTopAndBottom(mScrollY - mTitleBar.getTop());
+ }
}
return super.drawChild(canvas, child, drawingTime);
}
@@ -3057,6 +3091,29 @@
}
int saveCount = canvas.save();
+ if (mInOverScrollMode) {
+ if (mOverScrollBackground == null) {
+ mOverScrollBackground = new Paint();
+ Bitmap bm = BitmapFactory.decodeResource(
+ mContext.getResources(),
+ com.android.internal.R.drawable.pattern_underwear);
+ mOverScrollBackground.setShader(new BitmapShader(bm,
+ Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
+ }
+ int top = getTitleHeight();
+ // first draw the background and anchor to the top of the view
+ canvas.save();
+ canvas.translate(mScrollX, mScrollY);
+ canvas.clipRect(-mScrollX, top - mScrollY,
+ computeHorizontalScrollRange() - mScrollX, top
+ + computeVerticalScrollRange() - mScrollY,
+ Region.Op.DIFFERENCE);
+ canvas.drawPaint(mOverScrollBackground);
+ canvas.restore();
+ // next clip the region for the content
+ canvas.clipRect(0, top, computeHorizontalScrollRange(), top
+ + computeVerticalScrollRange());
+ }
if (mTitleBar != null) {
canvas.translate(0, (int) mTitleBar.getHeight());
}
@@ -3074,12 +3131,12 @@
canvas.restoreToCount(saveCount);
// Now draw the shadow.
- if (mTitleBar != null) {
- int y = mScrollY + getVisibleTitleHeight();
+ int titleH = getVisibleTitleHeight();
+ if (mTitleBar != null && titleH == 0) {
int height = (int) (5f * getContext().getResources()
.getDisplayMetrics().density);
- mTitleShadow.setBounds(mScrollX, y, mScrollX + getWidth(),
- y + height);
+ mTitleShadow.setBounds(mScrollX, mScrollY, mScrollX + getWidth(),
+ mScrollY + height);
mTitleShadow.draw(canvas);
}
if (AUTO_REDRAW_HACK && mAutoRedraw) {
@@ -4680,18 +4737,6 @@
}
// do pan
- int newScrollX = pinLocX(mScrollX + deltaX);
- int newDeltaX = newScrollX - mScrollX;
- if (deltaX != newDeltaX) {
- deltaX = newDeltaX;
- fDeltaX = (float) newDeltaX;
- }
- int newScrollY = pinLocY(mScrollY + deltaY);
- int newDeltaY = newScrollY - mScrollY;
- if (deltaY != newDeltaY) {
- deltaY = newDeltaY;
- fDeltaY = (float) newDeltaY;
- }
boolean done = false;
boolean keepScrollBarsVisible = false;
if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
@@ -4736,7 +4781,9 @@
}
}
if ((deltaX | deltaY) != 0) {
- scrollBy(deltaX, deltaY);
+ overscrollBy(deltaX, deltaY, mScrollX, mScrollY,
+ computeMaxScrollX(), computeMaxScrollY(),
+ getViewWidth() / 3, getViewHeight() / 3);
if (deltaX != 0) {
mLastTouchX = x;
}
@@ -4819,8 +4866,8 @@
Log.w(LOGTAG, "Miss a drag as we are waiting for" +
" WebCore's response for touch down.");
if (mFullScreenHolder == null
- && (computeHorizontalScrollExtent() < computeHorizontalScrollRange()
- || computeVerticalScrollExtent() < computeVerticalScrollRange())) {
+ && (computeMaxScrollX() > 0
+ || computeMaxScrollY() > 0)) {
// remove the pending TOUCH_EVENT and send a
// cancel
mWebViewCore
@@ -4866,6 +4913,12 @@
mVelocityTracker.addMovement(ev);
doFling();
break;
+ } else {
+ if (mScroller.springback(mScrollX, mScrollY, 0,
+ computeMaxScrollX(), 0,
+ computeMaxScrollY())) {
+ invalidate();
+ }
}
mLastVelocity = 0;
WebViewCore.resumePriority();
@@ -4886,6 +4939,12 @@
}
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ if (mScroller.springback(mScrollX, mScrollY, 0,
+ computeMaxScrollX(), 0, computeMaxScrollY())) {
+ invalidate();
+ }
+ }
break;
}
}
@@ -5204,16 +5263,18 @@
}
}
+ private int computeMaxScrollX() {
+ return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+ }
+
private int computeMaxScrollY() {
- int maxContentH = computeVerticalScrollRange() + getTitleHeight();
- return Math.max(maxContentH - getViewHeightWithTitle(), getTitleHeight());
+ return Math.max(computeVerticalScrollRange() + getTitleHeight()
+ - getViewHeightWithTitle(), getTitleHeight());
}
public void flingScroll(int vx, int vy) {
- int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
- int maxY = computeMaxScrollY();
-
- mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY);
+ mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
+ computeMaxScrollY(), getViewWidth() / 3, getViewHeight() / 3);
invalidate();
}
@@ -5221,7 +5282,7 @@
if (mVelocityTracker == null) {
return;
}
- int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+ int maxX = computeMaxScrollX();
int maxY = computeMaxScrollY();
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
@@ -5243,6 +5304,10 @@
}
if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
WebViewCore.resumePriority();
+ if (mScroller.springback(mScrollX, mScrollY, 0, computeMaxScrollX(),
+ 0, computeMaxScrollY())) {
+ invalidate();
+ }
return;
}
float currentVelocity = mScroller.getCurrVelocity();
@@ -5270,7 +5335,8 @@
mLastVelY = vy;
mLastVelocity = (float) Math.hypot(vx, vy);
- mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
+ mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY,
+ getViewWidth() / 3, getViewHeight() / 3);
// TODO: duration is calculated based on velocity, if the range is
// small, the animation will stop before duration is up. We may
// want to calculate how long the animation is going to run to precisely
diff --git a/core/res/res/drawable/pattern_underwear.png b/core/res/res/drawable/pattern_underwear.png
new file mode 100644
index 0000000..651212f
--- /dev/null
+++ b/core/res/res/drawable/pattern_underwear.png
Binary files differ
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index db802d3..4f1146b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -30,9 +30,11 @@
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteQueryBuilder;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
import android.provider.DrmStore;
@@ -48,6 +50,8 @@
private static final String TABLE_FAVORITES = "favorites";
private static final String TABLE_OLD_FAVORITES = "old_favorites";
+ private static final String[] COLUMN_VALUE = new String[] { "value" };
+
protected DatabaseHelper mOpenHelper;
private BackupManager mBackupManager;
@@ -220,6 +224,44 @@
}
}
+ /**
+ * Fast path that avoids the use of chatty remoted Cursors.
+ */
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) {
+ return lookupValue("system", request);
+ }
+
+ if (Settings.CALL_METHOD_GET_SECURE.equals(method)) {
+ return lookupValue("secure", request);
+ }
+ return null;
+ }
+
+ // Looks up value 'key' in 'table' and returns either a single-pair Bundle,
+ // possibly with a null value, or null on failure.
+ private Bundle lookupValue(String table, String key) {
+ // TODO: avoid database lookup and serve from in-process cache.
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor cursor = null;
+ try {
+ cursor = db.query(table, COLUMN_VALUE, "name=?", new String[]{key},
+ null, null, null, null);
+ if (cursor != null && cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ String value = cursor.getString(0);
+ return Bundle.forPair("value", value);
+ }
+ } catch (SQLiteException e) {
+ Log.w(TAG, "settings lookup error", e);
+ return null;
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return Bundle.forPair("value", null);
+ }
+
@Override
public Cursor query(Uri url, String[] select, String where, String[] whereArgs, String sort) {
SqlArguments args = new SqlArguments(url, where, whereArgs);
diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
index 4078622..3fd71c8 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -32,6 +32,7 @@
import android.database.IBulkCursor;
import android.database.IContentObserver;
import android.net.Uri;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -113,6 +114,15 @@
return MockContentProvider.this.update(url, values, selection, selectionArgs);
}
+ /**
+ * @hide
+ */
+ @SuppressWarnings("unused")
+ public Bundle call(String method, String request, Bundle args)
+ throws RemoteException {
+ return MockContentProvider.this.call(method, request, args);
+ }
+
public IBinder asBinder() {
throw new UnsupportedOperationException();
}
@@ -205,6 +215,14 @@
}
/**
+ * @hide
+ */
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ throw new UnsupportedOperationException("unimplemented mock method call");
+ }
+
+ /**
* Returns IContentProvider which calls back same methods in this class.
* By overriding this class, we avoid the mechanism hidden behind ContentProvider
* (IPC, etc.)
diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
index 7c0a1e2..0be5bea 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -27,6 +27,7 @@
import android.database.IBulkCursor;
import android.database.IContentObserver;
import android.net.Uri;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -38,7 +39,7 @@
* {@link java.lang.UnsupportedOperationException}. Tests can extend this class to
* implement behavior needed for tests.
*
- * @hide - @hide because this exposes bulkQuery(), which must also be hidden.
+ * @hide - @hide because this exposes bulkQuery() and call(), which must also be hidden.
*/
public class MockIContentProvider implements IContentProvider {
public int bulkInsert(Uri url, ContentValues[] initialValues) {
@@ -93,6 +94,11 @@
throw new UnsupportedOperationException("unimplemented mock method");
}
+ public Bundle call(String method, String request, Bundle args)
+ throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
public IBinder asBinder() {
throw new UnsupportedOperationException("unimplemented mock method");
}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index ea021d8..b7580b3 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -447,7 +447,7 @@
static bool applyFileOverlay(Bundle *bundle,
const sp<AaptAssets>& assets,
- const sp<ResourceTypeSet>& baseSet,
+ sp<ResourceTypeSet> *baseSet,
const char *resType)
{
if (bundle->getVerbose()) {
@@ -475,13 +475,16 @@
if (bundle->getVerbose()) {
printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string());
}
- size_t baseIndex = baseSet->indexOfKey(overlaySet->keyAt(overlayIndex));
+ size_t baseIndex = UNKNOWN_ERROR;
+ if (baseSet->get() != NULL) {
+ baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex));
+ }
if (baseIndex < UNKNOWN_ERROR) {
// look for same flavor. For a given file (strings.xml, for example)
// there may be a locale specific or other flavors - we want to match
// the same flavor.
sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
- sp<AaptGroup> baseGroup = baseSet->valueAt(baseIndex);
+ sp<AaptGroup> baseGroup = (*baseSet)->valueAt(baseIndex);
DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
overlayGroup->getFiles();
@@ -520,8 +523,12 @@
assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
}
} else {
+ if (baseSet->get() == NULL) {
+ *baseSet = new ResourceTypeSet();
+ assets->getResources()->add(String8(resType), *baseSet);
+ }
// this group doesn't exist (a file that's only in the overlay)
- baseSet->add(overlaySet->keyAt(overlayIndex),
+ (*baseSet)->add(overlaySet->keyAt(overlayIndex),
overlaySet->valueAt(overlayIndex));
// make sure all flavors are defined in the resources.
sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
@@ -751,13 +758,13 @@
current = current->getOverlay();
}
// apply the overlay files to the base set
- if (!applyFileOverlay(bundle, assets, drawables, "drawable") ||
- !applyFileOverlay(bundle, assets, layouts, "layout") ||
- !applyFileOverlay(bundle, assets, anims, "anim") ||
- !applyFileOverlay(bundle, assets, xmls, "xml") ||
- !applyFileOverlay(bundle, assets, raws, "raw") ||
- !applyFileOverlay(bundle, assets, colors, "color") ||
- !applyFileOverlay(bundle, assets, menus, "menu")) {
+ if (!applyFileOverlay(bundle, assets, &drawables, "drawable") ||
+ !applyFileOverlay(bundle, assets, &layouts, "layout") ||
+ !applyFileOverlay(bundle, assets, &anims, "anim") ||
+ !applyFileOverlay(bundle, assets, &xmls, "xml") ||
+ !applyFileOverlay(bundle, assets, &raws, "raw") ||
+ !applyFileOverlay(bundle, assets, &colors, "color") ||
+ !applyFileOverlay(bundle, assets, &menus, "menu")) {
return UNKNOWN_ERROR;
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 0b531c2..1f9d152 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2369,7 +2369,7 @@
if (configSet.find(region) == configSet.end()) {
if (configSet.count(defaultLocale) == 0) {
fprintf(stdout, "aapt: warning: "
- "*** string '%s' has no default or required localization "
+ "**** string '%s' has no default or required localization "
"for '%s' in %s\n",
String8(nameIter->first).string(),
config.string(),