Merge "Updated developer groups documentation to point to Stack Overflow, also rearranged a few things." into eclair
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index 96d44b6..4491a8a 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -75,4 +75,9 @@
* when a UMS host is detected.
*/
void setAutoStartUms(boolean value);
+
+ /**
+ * Shuts down the MountService and gracefully unmounts all external media.
+ */
+ void shutdown();
}
diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java
index 3679e47..bc76180 100644
--- a/core/java/android/os/Power.java
+++ b/core/java/android/os/Power.java
@@ -17,6 +17,8 @@
package android.os;
import java.io.IOException;
+import android.os.ServiceManager;
+import android.os.IMountService;
/**
* Class that provides access to some of the power management functions.
@@ -97,5 +99,19 @@
* @throws IOException if reboot fails for some reason (eg, lack of
* permission)
*/
- public static native void reboot(String reason) throws IOException;
+ public static void reboot(String reason) throws IOException
+ {
+ IMountService mSvc = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
+
+ if (mSvc != null) {
+ try {
+ mSvc.shutdown();
+ } catch (Exception e) {
+ }
+ }
+ rebootNative(reason);
+ }
+
+ private static native void rebootNative(String reason) throws IOException ;
}
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
new file mode 100644
index 0000000..2da8f14
--- /dev/null
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2010 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.content.Context;
+
+/**
+ * Detects transformation gestures involving more than one pointer ("multitouch")
+ * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
+ * callback will notify users when a particular gesture event has occurred.
+ * This class should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * To use this class:
+ * <ul>
+ * <li>Create an instance of the {@code ScaleGestureDetector} for your
+ * {@link View}
+ * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ * callback will be executed when the events occur.
+ * </ul>
+ * @hide Pending API approval
+ */
+public class ScaleGestureDetector {
+ /**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnScaleGestureListener}.
+ *
+ * An application will receive events in the following order:
+ * <ul>
+ * <li>One {@link OnScaleGestureListener#onScaleBegin()}
+ * <li>Zero or more {@link OnScaleGestureListener#onScale()}
+ * <li>One {@link OnScaleGestureListener#onTransformEnd()}
+ * </ul>
+ */
+ public interface OnScaleGestureListener {
+ /**
+ * Responds to scaling events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should consider this event
+ * as handled. If an event was not handled, the detector
+ * will continue to accumulate movement until an event is
+ * handled. This can be useful if an application, for example,
+ * only wants to update scaling factors if the change is
+ * greater than 0.01.
+ */
+ public boolean onScale(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the beginning of a scaling gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should continue recognizing
+ * this gesture. For example, if a gesture is beginning
+ * with a focal point outside of a region where it makes
+ * sense, onScaleBegin() may return false to ignore the
+ * rest of the gesture.
+ */
+ public boolean onScaleBegin(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the end of a scale gesture. Reported by existing
+ * pointers going up. If the end of a gesture would result in a fling,
+ * {@link onTransformFling()} is called instead.
+ *
+ * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
+ * and {@link ScaleGestureDetector#getFocusY()} will return the location
+ * of the pointer remaining on the screen.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ */
+ public void onScaleEnd(ScaleGestureDetector detector);
+ }
+
+ /**
+ * A convenience class to extend when you only want to listen for a subset
+ * of scaling-related events. This implements all methods in
+ * {@link OnScaleGestureListener} but does nothing.
+ * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} and
+ * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} return
+ * {@code true}.
+ */
+ public class SimpleOnScaleGestureListener implements OnScaleGestureListener {
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ // Intentionally empty
+ }
+ }
+
+ private static final float PRESSURE_THRESHOLD = 0.67f;
+
+ private Context mContext;
+ private OnScaleGestureListener mListener;
+ private boolean mGestureInProgress;
+
+ private MotionEvent mPrevEvent;
+ private MotionEvent mCurrEvent;
+
+ private float mFocusX;
+ private float mFocusY;
+ private float mPrevFingerDiffX;
+ private float mPrevFingerDiffY;
+ private float mCurrFingerDiffX;
+ private float mCurrFingerDiffY;
+ private float mCurrLen;
+ private float mPrevLen;
+ private float mScaleFactor;
+ private float mCurrPressure;
+ private float mPrevPressure;
+ private long mTimeDelta;
+
+ public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+ mContext = context;
+ mListener = listener;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ boolean handled = true;
+
+ if (!mGestureInProgress) {
+ if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
+ action == MotionEvent.ACTION_POINTER_2_DOWN) &&
+ event.getPointerCount() >= 2) {
+ // We have a new multi-finger gesture
+
+ // Be paranoid in case we missed an event
+ reset();
+
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ setContext(event);
+ mGestureInProgress = mListener.onScaleBegin(this);
+ }
+ } else {
+ // Transform gesture in progress - attempt to handle it
+ switch (action) {
+ case MotionEvent.ACTION_POINTER_1_UP:
+ case MotionEvent.ACTION_POINTER_2_UP:
+ // Gesture ended
+ setContext(event);
+
+ // Set focus point to the remaining finger
+ int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+ >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+ mFocusX = event.getX(id);
+ mFocusY = event.getY(id);
+
+ mListener.onScaleEnd(this);
+ mGestureInProgress = false;
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mListener.onScaleEnd(this);
+ mGestureInProgress = false;
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ setContext(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit - this can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onScale(this);
+
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+ return handled;
+ }
+
+ private void setContext(MotionEvent curr) {
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ }
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ mCurrLen = -1;
+ mPrevLen = -1;
+ mScaleFactor = -1;
+
+ final MotionEvent prev = mPrevEvent;
+
+ final float px0 = prev.getX(0);
+ final float py0 = prev.getY(0);
+ final float px1 = prev.getX(1);
+ final float py1 = prev.getY(1);
+ final float cx0 = curr.getX(0);
+ final float cy0 = curr.getY(0);
+ final float cx1 = curr.getX(1);
+ final float cy1 = curr.getY(1);
+
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+
+ mFocusX = cx0 + cvx * 0.5f;
+ mFocusY = cy0 + cvy * 0.5f;
+ mTimeDelta = curr.getEventTime() - prev.getEventTime();
+ mCurrPressure = curr.getPressure(0) + curr.getPressure(1);
+ mPrevPressure = prev.getPressure(0) + prev.getPressure(1);
+ }
+
+ private void reset() {
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ }
+
+ /**
+ * Returns {@code true} if a two-finger scale gesture is in progress.
+ * @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
+ */
+ public boolean isInProgress() {
+ return mGestureInProgress;
+ }
+
+ /**
+ * Get the X coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is directly between
+ * the two pointers forming the gesture.
+ * If a gesture is ending, the focal point is the location of the
+ * remaining pointer on the screen.
+ * If {@link isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return X coordinate of the focal point in pixels.
+ */
+ public float getFocusX() {
+ return mFocusX;
+ }
+
+ /**
+ * Get the Y coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is directly between
+ * the two pointers forming the gesture.
+ * If a gesture is ending, the focal point is the location of the
+ * remaining pointer on the screen.
+ * If {@link isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return Y coordinate of the focal point in pixels.
+ */
+ public float getFocusY() {
+ return mFocusY;
+ }
+
+ /**
+ * Return the current distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
+ }
+ return mCurrLen;
+ }
+
+ /**
+ * Return the previous distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
+ }
+ return mPrevLen;
+ }
+
+ /**
+ * Return the scaling factor from the previous scale event to the current
+ * event. This value is defined as
+ * ({@link getCurrentSpan()} / {@link getPreviousSpan()}).
+ *
+ * @return The current scaling factor.
+ */
+ public float getScaleFactor() {
+ if (mScaleFactor == -1) {
+ mScaleFactor = getCurrentSpan() / getPreviousSpan();
+ }
+ return mScaleFactor;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous
+ * accepted scaling event and the current scaling event.
+ *
+ * @return Time difference since the last scaling event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mTimeDelta;
+ }
+
+ /**
+ * Return the event time of the current event being processed.
+ *
+ * @return Current event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrEvent.getEventTime();
+ }
+}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 5d89c46..9581080 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -55,12 +55,12 @@
}
}, 2));
- final float mPastX[] = new float[NUM_PAST];
- final float mPastY[] = new float[NUM_PAST];
- final long mPastTime[] = new long[NUM_PAST];
+ final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
+ final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
+ final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
- float mYVelocity;
- float mXVelocity;
+ float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
+ float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
private VelocityTracker mNext;
@@ -105,7 +105,9 @@
* Reset the velocity tracker back to its initial state.
*/
public void clear() {
- mPastTime[0] = 0;
+ for (int i = 0; i < MotionEvent.BASE_AVAIL_POINTERS; i++) {
+ mPastTime[i][0] = 0;
+ }
}
/**
@@ -120,18 +122,21 @@
public void addMovement(MotionEvent ev) {
long time = ev.getEventTime();
final int N = ev.getHistorySize();
- for (int i=0; i<N; i++) {
- addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
- ev.getHistoricalEventTime(i));
+ final int pointerCount = ev.getPointerCount();
+ for (int p = 0; p < pointerCount; p++) {
+ for (int i=0; i<N; i++) {
+ addPoint(p, ev.getHistoricalX(p, i), ev.getHistoricalY(p, i),
+ ev.getHistoricalEventTime(i));
+ }
+ addPoint(p, ev.getX(p), ev.getY(p), time);
}
- addPoint(ev.getX(), ev.getY(), time);
}
- private void addPoint(float x, float y, long time) {
+ private void addPoint(int pos, float x, float y, long time) {
int drop = -1;
int i;
if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
- final long[] pastTime = mPastTime;
+ final long[] pastTime = mPastTime[pos];
for (i=0; i<NUM_PAST; i++) {
if (pastTime[i] == 0) {
break;
@@ -146,8 +151,8 @@
drop = 0;
}
if (drop == i) drop--;
- final float[] pastX = mPastX;
- final float[] pastY = mPastY;
+ final float[] pastX = mPastX[pos];
+ final float[] pastY = mPastY[pos];
if (drop >= 0) {
if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
final int start = drop+1;
@@ -190,44 +195,48 @@
* must be positive.
*/
public void computeCurrentVelocity(int units, float maxVelocity) {
- final float[] pastX = mPastX;
- final float[] pastY = mPastY;
- final long[] pastTime = mPastTime;
-
- // Kind-of stupid.
- final float oldestX = pastX[0];
- final float oldestY = pastY[0];
- final long oldestTime = pastTime[0];
- float accumX = 0;
- float accumY = 0;
- int N=0;
- while (N < NUM_PAST) {
- if (pastTime[N] == 0) {
- break;
+ for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) {
+ final float[] pastX = mPastX[pos];
+ final float[] pastY = mPastY[pos];
+ final long[] pastTime = mPastTime[pos];
+
+ // Kind-of stupid.
+ final float oldestX = pastX[0];
+ final float oldestY = pastY[0];
+ final long oldestTime = pastTime[0];
+ float accumX = 0;
+ float accumY = 0;
+ int N=0;
+ while (N < NUM_PAST) {
+ if (pastTime[N] == 0) {
+ break;
+ }
+ N++;
}
- N++;
+ // Skip the last received event, since it is probably pretty noisy.
+ if (N > 3) N--;
+
+ for (int i=1; i < N; i++) {
+ final int dur = (int)(pastTime[i] - oldestTime);
+ if (dur == 0) continue;
+ float dist = pastX[i] - oldestX;
+ float vel = (dist/dur) * units; // pixels/frame.
+ if (accumX == 0) accumX = vel;
+ else accumX = (accumX + vel) * .5f;
+
+ dist = pastY[i] - oldestY;
+ vel = (dist/dur) * units; // pixels/frame.
+ if (accumY == 0) accumY = vel;
+ else accumY = (accumY + vel) * .5f;
+ }
+ mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
+ : Math.min(accumX, maxVelocity);
+ mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
+ : Math.min(accumY, maxVelocity);
+
+ if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
+ + mXVelocity + " N=" + N);
}
- // Skip the last received event, since it is probably pretty noisy.
- if (N > 3) N--;
-
- for (int i=1; i < N; i++) {
- final int dur = (int)(pastTime[i] - oldestTime);
- if (dur == 0) continue;
- float dist = pastX[i] - oldestX;
- float vel = (dist/dur) * units; // pixels/frame.
- if (accumX == 0) accumX = vel;
- else accumX = (accumX + vel) * .5f;
-
- dist = pastY[i] - oldestY;
- vel = (dist/dur) * units; // pixels/frame.
- if (accumY == 0) accumY = vel;
- else accumY = (accumY + vel) * .5f;
- }
- mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity);
- mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity);
-
- if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
- + mXVelocity + " N=" + N);
}
/**
@@ -237,7 +246,7 @@
* @return The previously computed X velocity.
*/
public float getXVelocity() {
- return mXVelocity;
+ return mXVelocity[0];
}
/**
@@ -247,6 +256,32 @@
* @return The previously computed Y velocity.
*/
public float getYVelocity() {
- return mYVelocity;
+ return mYVelocity[0];
+ }
+
+ /**
+ * Retrieve the last computed X velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @param pos Which pointer's velocity to return.
+ * @return The previously computed X velocity.
+ *
+ * @hide Pending API approval
+ */
+ public float getXVelocity(int pos) {
+ return mXVelocity[pos];
+ }
+
+ /**
+ * Retrieve the last computed Y velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @param pos Which pointer's velocity to return.
+ * @return The previously computed Y velocity.
+ *
+ * @hide Pending API approval
+ */
+ public float getYVelocity(int pos) {
+ return mYVelocity[pos];
}
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 6f3262a..093756d 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -411,6 +411,7 @@
*/
public void setSupportZoom(boolean support) {
mSupportZoom = support;
+ mWebView.updateMultiTouchSupport(mContext);
}
/**
@@ -425,6 +426,7 @@
*/
public void setBuiltInZoomControls(boolean enabled) {
mBuiltInZoomControls = enabled;
+ mWebView.updateMultiTouchSupport(mContext);
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 8308fd1..7d0c53b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -50,6 +50,7 @@
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
@@ -430,6 +431,18 @@
private boolean mWrapContent;
+ // whether support multi-touch
+ private static boolean mSupportMultiTouch;
+ // use the framework's ScaleGestureDetector to handle multi-touch
+ private ScaleGestureDetector mScaleDetector;
+ // minimum scale change during multi-touch zoom
+ private static float PREVIEW_SCALE_INCREMENT = 0.01f;
+
+ // the anchor point in the document space where VIEW_SIZE_CHANGED should
+ // apply to
+ private int mAnchorX;
+ private int mAnchorY;
+
/**
* Private message ids
*/
@@ -747,9 +760,20 @@
params;
frameParams.gravity = Gravity.RIGHT;
}
+ updateMultiTouchSupport(context);
+ }
+ void updateMultiTouchSupport(Context context) {
+ WebSettings settings = getSettings();
mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
+ PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
+ && settings.supportZoom() && settings.getBuiltInZoomControls();
+ if (mSupportMultiTouch && (mScaleDetector == null)) {
+ mScaleDetector = new ScaleGestureDetector(context,
+ new ScaleDetectorListener());
+ } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
+ mScaleDetector = null;
+ }
}
private void updateZoomButtonsEnabled() {
@@ -2098,6 +2122,7 @@
mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
mLastWidthSent = newWidth;
mLastHeightSent = newHeight;
+ mAnchorX = mAnchorY = 0;
return true;
}
return false;
@@ -3698,17 +3723,10 @@
private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
- // MultiTouch handling
- private static boolean mSupportMultiTouch;
+ private class ScaleDetectorListener implements
+ ScaleGestureDetector.OnScaleGestureListener {
- private double mPinchDistance;
- private int mAnchorX;
- private int mAnchorY;
-
- private boolean doMultiTouch(MotionEvent ev) {
- int action = ev.getAction();
-
- if ((action & 0xff) == MotionEvent.ACTION_POINTER_DOWN) {
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
// cancel the single touch handling
cancelTouch();
// reset the zoom overview mode so that the page won't auto grow
@@ -3718,29 +3736,22 @@
if (inEditingMode() && nativeFocusCandidateIsPassword()) {
mWebTextView.setInPassword(false);
}
- // start multi (2-pointer) touch
- mPreviewZoomOnly = true;
- float x0 = ev.getX(0);
- float y0 = ev.getY(0);
- float x1 = ev.getX(1);
- float y1 = ev.getY(1);
- mLastTouchTime = ev.getEventTime();
- mPinchDistance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
- * (y0 - y1));
- mZoomCenterX = mZoomCenterY = 0;
- } else if ((action & 0xff) == MotionEvent.ACTION_POINTER_UP) {
- mPreviewZoomOnly = false;
- // for testing only, default don't reflow now
- boolean reflowNow = !getSettings().getPluginsEnabled();
- // force zoom after mPreviewZoomOnly is set to false so that the new
- // view size will be passed to the WebKit
- if (reflowNow && (mZoomCenterX != 0) && (mZoomCenterY != 0)) {
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ if (mPreviewZoomOnly) {
+ mPreviewZoomOnly = false;
mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+ // for testing only, default don't reflow now
+ boolean reflowNow = !getSettings().getPluginsEnabled();
+ // force zoom after mPreviewZoomOnly is set to false so that the
+ // new view size will be passed to the WebKit
+ setNewZoomScale(mActualScale, reflowNow, true);
+ // call invalidate() to draw without zoom filter
+ invalidate();
}
- setNewZoomScale(mActualScale, reflowNow, true);
- // call invalidate() to draw without zoom filter
- invalidate();
// adjust the edit text view if needed
if (inEditingMode()) {
adjustTextView(true);
@@ -3749,44 +3760,29 @@
// may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it
// may trigger the unwanted fling.
mTouchMode = TOUCH_PINCH_DRAG;
- // action indicates which pointer is UP. Use the other one as drag's
- // starting position.
- int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
- >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
- startTouch(ev.getX(id), ev.getY(id), ev.getEventTime());
- } else if (action == MotionEvent.ACTION_MOVE) {
- float x0 = ev.getX(0);
- float y0 = ev.getY(0);
- float x1 = ev.getX(1);
- float y1 = ev.getY(1);
- double distance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
- * (y0 - y1));
- float scale = (float) (Math.round(distance / mPinchDistance
+ startTouch(detector.getFocusX(), detector.getFocusY(),
+ mLastTouchTime);
+ }
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ float scale = (float) (Math.round(detector.getScaleFactor()
* mActualScale * 100) / 100.0);
- long time = ev.getEventTime();
- // add distance/time checking to avoid the minor shift right before
- // lifting the fingers after a pause
- if (Math.abs(scale - mActualScale) >= 0.01f
- && ((time - mLastTouchTime) < 300 || Math.abs(distance
- - mPinchDistance) > (10 * mDefaultScale))) {
+ if (Math.abs(scale - mActualScale) >= PREVIEW_SCALE_INCREMENT) {
+ mPreviewZoomOnly = true;
// limit the scale change per step
if (scale > mActualScale) {
scale = Math.min(scale, mActualScale * 1.25f);
} else {
scale = Math.max(scale, mActualScale * 0.8f);
}
- mZoomCenterX = (x0 + x1) / 2;
- mZoomCenterY = (y0 + y1) / 2;
+ mZoomCenterX = detector.getFocusX();
+ mZoomCenterY = detector.getFocusY();
setNewZoomScale(scale, false, false);
invalidate();
- mPinchDistance = distance;
- mLastTouchTime = time;
+ return true;
}
- } else {
- Log.w(LOGTAG, action + " should not happen during doMultiTouch");
return false;
}
- return true;
}
@Override
@@ -3800,9 +3796,12 @@
+ mTouchMode);
}
- if (mSupportMultiTouch && getSettings().supportZoom()
+ // FIXME: we may consider to give WebKit an option to handle multi-touch
+ // events later.
+ if (mSupportMultiTouch && mMinZoomScale < mMaxZoomScale
&& ev.getPointerCount() > 1) {
- return doMultiTouch(ev);
+ mLastTouchTime = ev.getEventTime();
+ return mScaleDetector.onTouchEvent(ev);
}
int action = ev.getAction();
@@ -5149,7 +5148,8 @@
}
if (mInitialScaleInPercent > 0) {
setNewZoomScale(mInitialScaleInPercent / 100.0f,
- true, false);
+ mInitialScaleInPercent != mTextWrapScale * 100,
+ false);
} else if (restoreState.mViewScale > 0) {
mTextWrapScale = restoreState.mTextWrapScale;
setNewZoomScale(restoreState.mViewScale, false,
@@ -5157,14 +5157,15 @@
} else {
mInZoomOverview = useWideViewport
&& settings.getLoadWithOverviewMode();
+ float scale;
if (mInZoomOverview) {
- setNewZoomScale((float) viewWidth
- / WebViewCore.DEFAULT_VIEWPORT_WIDTH,
- true, false);
+ scale = (float) viewWidth
+ / WebViewCore.DEFAULT_VIEWPORT_WIDTH;
} else {
- setNewZoomScale(restoreState.mTextWrapScale,
- true, false);
+ scale = restoreState.mTextWrapScale;
}
+ setNewZoomScale(scale, Math.abs(scale
+ - mTextWrapScale) >= 0.01f, false);
}
setContentScrollTo(restoreState.mScrollX,
restoreState.mScrollY);
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 27b67d1..c3817fb 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1518,7 +1518,7 @@
} else if (mViewportWidth > 0) {
width = Math.max(w, mViewportWidth);
} else {
- width = Math.max(w, nativeGetContentMinPrefWidth());
+ width = Math.max(w, textwrapWidth);
}
}
nativeSetSize(width, width == w ? h : Math.round((float) width * h / w),
diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index 2060cf8..c110f95 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -32,6 +32,7 @@
import android.os.Power;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.IMountService;
import com.android.internal.telephony.ITelephony;
import android.util.Log;
@@ -189,6 +190,10 @@
final IBluetooth bluetooth =
IBluetooth.Stub.asInterface(ServiceManager.checkService(
BluetoothAdapter.BLUETOOTH_SERVICE));
+
+ final IMountService mount =
+ IMountService.Stub.asInterface(
+ ServiceManager.checkService("mount"));
try {
bluetoothOff = bluetooth == null ||
@@ -241,6 +246,17 @@
SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
}
+ // Shutdown MountService to ensure media is in a safe state
+ try {
+ if (mount != null) {
+ mount.shutdown();
+ } else {
+ Log.w(TAG, "MountService unavailable for shutdown");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception during MountService shutdown", e);
+ }
+
//shutdown power
Log.i(TAG, "Performing low-level shutdown...");
Power.shutdown();
diff --git a/core/jni/android_os_Power.cpp b/core/jni/android_os_Power.cpp
index df5edba..a46c2dd 100644
--- a/core/jni/android_os_Power.cpp
+++ b/core/jni/android_os_Power.cpp
@@ -105,7 +105,7 @@
{ "setLastUserActivityTimeout", "(J)I", (void*)setLastUserActivityTimeout },
{ "setScreenState", "(Z)I", (void*)setScreenState },
{ "shutdown", "()V", (void*)android_os_Power_shutdown },
- { "reboot", "(Ljava/lang/String;)V", (void*)android_os_Power_reboot },
+ { "rebootNative", "(Ljava/lang/String;)V", (void*)android_os_Power_reboot },
};
int register_android_os_Power(JNIEnv *env)
diff --git a/docs/html/sdk/download.jd b/docs/html/sdk/download.jd
index 47505e6..029de21 100644
--- a/docs/html/sdk/download.jd
+++ b/docs/html/sdk/download.jd
@@ -58,10 +58,8 @@
<h2>Thank you for downloading the Android SDK!</h2>
<p>Your download should be underway. If not, <a id="click-download">click here to start the download</a>.</p>
<p>To set up your Android development environment, please read the guide to
- <a href="installing.html">Installing the Android SDK</a>.
- Once you have completed the installation, see the
- <a href="/guide/index.html">Dev Guide</a> for documentation about
- developing Android applications.</p>
+ <a href="installing.html">Installing the Android SDK</a> and ensure that your development
+ machine meets the system requirements linked on that page.</p>
</div>
<script type="text/javascript">
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 262ac8d..d364277 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -107,9 +107,5 @@
include $(BUILD_SHARED_LIBRARY)
-# Include the subdirectories ====================
-include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk,\
- java \
- ))
endif #simulator
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 23888ff..de8df39 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -776,6 +776,17 @@
glDrawTexfOES(x, y, z, w, h);
}
+static void SC_drawSpriteScreenspaceCropped(float x, float y, float z, float w, float h,
+ float cx0, float cy0, float cx1, float cy1)
+{
+ GET_TLS();
+ rsc->setupCheck();
+
+ GLint crop[4] = {cx0, cy0, cx1, cy1};
+ glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+ glDrawTexfOES(x, y, z, w, h);
+}
+
static void SC_drawSprite(float x, float y, float z, float w, float h)
{
GET_TLS();
@@ -1271,6 +1282,8 @@
"void", "(float x, float y, float z, float w, float h)" },
{ "drawSpriteScreenspace", (void *)&SC_drawSpriteScreenspace,
"void", "(float x, float y, float z, float w, float h)" },
+ { "drawSpriteScreenspaceCropped", (void *)&SC_drawSpriteScreenspaceCropped,
+ "void", "(float x, float y, float z, float w, float h, float cx0, float cy0, float cx1, float cy1)" },
{ "drawLine", (void *)&SC_drawLine,
"void", "(float x1, float y1, float z1, float x2, float y2, float z2)" },
{ "drawPoint", (void *)&SC_drawPoint,
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 78215b0..5c0f352 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -111,6 +111,7 @@
public int mType;
public int mRadio;
public int mPriority;
+ public NetworkInfo.State mLastState;
public NetworkAttributes(String init) {
String fragments[] = init.split(",");
mName = fragments[0].toLowerCase();
@@ -131,6 +132,7 @@
mType = ConnectivityManager.TYPE_MOBILE_HIPRI;
}
mPriority = Integer.parseInt(fragments[2]);
+ mLastState = NetworkInfo.State.UNKNOWN;
}
public boolean isDefault() {
return (mType == mRadio);
@@ -1214,9 +1216,22 @@
switch (msg.what) {
case NetworkStateTracker.EVENT_STATE_CHANGED:
info = (NetworkInfo) msg.obj;
+ int type = info.getType();
+ NetworkInfo.State state = info.getState();
+ if (mNetAttributes[type].mLastState == state) {
+ if (DBG) {
+ // TODO - remove this after we validate the dropping doesn't break anything
+ Log.d(TAG, "Dropping ConnectivityChange for " +
+ info.getTypeName() + ": " +
+ state + "/" + info.getDetailedState());
+ }
+ return;
+ }
+ mNetAttributes[type].mLastState = state;
+
if (DBG) Log.d(TAG, "ConnectivityChange for " +
info.getTypeName() + ": " +
- info.getState() + "/" + info.getDetailedState());
+ state + "/" + info.getDetailedState());
// Connectivity state changed:
// [31-13] Reserved for future use
@@ -1234,10 +1249,9 @@
if (info.getDetailedState() ==
NetworkInfo.DetailedState.FAILED) {
handleConnectionFailure(info);
- } else if (info.getState() ==
- NetworkInfo.State.DISCONNECTED) {
+ } else if (state == NetworkInfo.State.DISCONNECTED) {
handleDisconnect(info);
- } else if (info.getState() == NetworkInfo.State.SUSPENDED) {
+ } else if (state == NetworkInfo.State.SUSPENDED) {
// TODO: need to think this over.
// the logic here is, handle SUSPENDED the same as
// DISCONNECTED. The only difference being we are
@@ -1246,7 +1260,7 @@
// opportunity to handle DISCONNECTED and SUSPENDED
// differently, or not.
handleDisconnect(info);
- } else if (info.getState() == NetworkInfo.State.CONNECTED) {
+ } else if (state == NetworkInfo.State.CONNECTED) {
handleConnect(info);
}
break;
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 204389e..ddf7c56 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -108,13 +108,86 @@
BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ String action = intent.getAction();
+
+ if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
Thread thread = new Thread(mListener, MountListener.class.getName());
thread.start();
}
}
};
+ public void shutdown() {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.SHUTDOWN)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires SHUTDOWN permission");
+ }
+
+ Log.d(TAG, "Shutting down");
+ String state = Environment.getExternalStorageState();
+
+ if (state.equals(Environment.MEDIA_SHARED)) {
+ /*
+ * If the media is currently shared, unshare it.
+ * XXX: This is still dangerous!. We should not
+ * be rebooting at *all* if UMS is enabled, since
+ * the UMS host could have dirty FAT cache entries
+ * yet to flush.
+ */
+ try {
+ setMassStorageEnabled(false);
+ } catch (Exception e) {
+ Log.e(TAG, "ums disable failed", e);
+ }
+ } else if (state.equals(Environment.MEDIA_CHECKING)) {
+ /*
+ * If the media is being checked, then we need to wait for
+ * it to complete before being able to proceed.
+ */
+ // XXX: @hackbod - Should we disable the ANR timer here?
+ int retries = 30;
+ while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException iex) {
+ Log.e(TAG, "Interrupted while waiting for media", iex);
+ break;
+ }
+ state = Environment.getExternalStorageState();
+ }
+ if (retries == 0) {
+ Log.e(TAG, "Timed out waiting for media to check");
+ }
+ }
+
+ if (state.equals(Environment.MEDIA_MOUNTED)) {
+ /*
+ * If the media is mounted, then gracefully unmount it.
+ */
+ try {
+ String m = Environment.getExternalStorageDirectory().toString();
+ unmountMedia(m);
+
+ int retries = 12;
+ while (!state.equals(Environment.MEDIA_UNMOUNTED) && (retries-- >=0)) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException iex) {
+ Log.e(TAG, "Interrupted while waiting for media", iex);
+ break;
+ }
+ state = Environment.getExternalStorageState();
+ }
+ if (retries == 0) {
+ Log.e(TAG, "Timed out waiting for media to unmount");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "external storage unmount failed", e);
+ }
+ }
+ }
+
/**
* @return true if USB mass storage support is enabled.
*/
diff --git a/telephony/java/com/android/internal/telephony/TelephonyEventLog.java b/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
index cdce488..0f15cb6 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
@@ -32,4 +32,5 @@
public static final int EVENT_LOG_PDP_NETWORK_DROP = 50109;
public static final int EVENT_LOG_CDMA_DATA_SETUP_FAILED = 50110;
public static final int EVENT_LOG_CDMA_DATA_DROP = 50111;
+ public static final int EVENT_LOG_GSM_RAT_SWITCHED = 50112;
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 8140654..3bf76d4 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -38,6 +38,7 @@
import android.provider.Telephony.Intents;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.Config;
@@ -848,6 +849,21 @@
cellLoc = newCellLoc;
newCellLoc = tcl;
+
+ // Add an event log when network type switched
+ // TODO: we may add filtering to reduce the event logged,
+ // i.e. check preferred network setting, only switch to 2G, etc
+ if (hasNetworkTypeChanged) {
+ int cid = -1;
+ GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
+ if (loc != null) cid = loc.getCid();
+ EventLog.List val = new EventLog.List(cid, networkType, newNetworkType);
+ EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_GSM_RAT_SWITCHED, val);
+ Log.d(LOG_TAG,
+ "RAT switched " + networkTypeToString(networkType) + " -> "
+ + networkTypeToString(newNetworkType) + " at cell " + cid);
+ }
+
gprsState = newGPRSState;
networkType = newNetworkType;
diff --git a/tests/TransformTest/Android.mk b/tests/TransformTest/Android.mk
new file mode 100644
index 0000000..2d3637d
--- /dev/null
+++ b/tests/TransformTest/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TransformTest
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/TransformTest/AndroidManifest.xml b/tests/TransformTest/AndroidManifest.xml
new file mode 100644
index 0000000..5c9995f
--- /dev/null
+++ b/tests/TransformTest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.transform">
+ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="7" />
+ <application android:label="TransformTest">
+ <activity android:name="TransformTestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/TransformTest/res/drawable/logo.png b/tests/TransformTest/res/drawable/logo.png
new file mode 100644
index 0000000..4d717a8
--- /dev/null
+++ b/tests/TransformTest/res/drawable/logo.png
Binary files differ
diff --git a/tests/TransformTest/res/values/strings.xml b/tests/TransformTest/res/values/strings.xml
new file mode 100644
index 0000000..a0eb81f
--- /dev/null
+++ b/tests/TransformTest/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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="act_title">TransformTest</string>
+</resources>
diff --git a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
new file mode 100644
index 0000000..31ee120
--- /dev/null
+++ b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2008 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.google.android.test.transform;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.widget.LinearLayout;
+
+public class TransformTestActivity extends Activity {
+ public TransformTestActivity() {
+ super();
+ init(false);
+ }
+
+ public TransformTestActivity(boolean noCompat) {
+ super();
+ init(noCompat);
+ }
+
+ public void init(boolean noCompat) {
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.setTitle(R.string.act_title);
+ LinearLayout root = new LinearLayout(this);
+ root.setOrientation(LinearLayout.VERTICAL);
+
+ TransformView view = new TransformView(getApplicationContext());
+ Drawable drawable = getResources().getDrawable(R.drawable.logo);
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicWidth());
+ view.setDrawable(drawable);
+
+ root.addView(view);
+ setContentView(root);
+ }
+
+ private class TransformView extends View {
+ private Drawable mDrawable;
+ private float mPosX;
+ private float mPosY;
+ private float mScale = 1.f;
+ private Matrix mMatrix;
+ private ScaleGestureDetector mDetector;
+
+ private float mLastX;
+ private float mLastY;
+
+ private class Listener implements ScaleGestureDetector.OnScaleGestureListener {
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ float scale = detector.getScaleFactor();
+
+ Log.d("ttest", "Scale: " + scale);
+
+ // Limit the scale so our object doesn't get too big or disappear
+ if (mScale * scale > 0.1f) {
+ if (mScale * scale < 10.f) {
+ mScale *= scale;
+ } else {
+ mScale = 10.f;
+ }
+ } else {
+ mScale = 0.1f;
+ }
+
+ Log.d("ttest", "mScale: " + mScale + " mPos: (" + mPosX + ", " + mPosY + ")");
+
+ float sizeX = mDrawable.getIntrinsicWidth()/2;
+ float sizeY = mDrawable.getIntrinsicHeight()/2;
+ float centerX = detector.getFocusX();
+ float centerY = detector.getFocusY();
+ float diffX = centerX - mPosX;
+ float diffY = centerY - mPosY;
+ diffX = diffX*scale - diffX;
+ diffY = diffY*scale - diffY;
+ mPosX -= diffX;
+ mPosY -= diffY;
+ mMatrix.reset();
+ mMatrix.postTranslate(-sizeX, -sizeY);
+ mMatrix.postScale(mScale, mScale);
+ mMatrix.postTranslate(mPosX, mPosY);
+
+ invalidate();
+
+ return true;
+ }
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mLastX = detector.getFocusX();
+ mLastY = detector.getFocusY();
+ }
+ }
+
+ public TransformView(Context context) {
+ super(context);
+ mMatrix = new Matrix();
+ mDetector = new ScaleGestureDetector(context, new Listener());
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ mPosX = metrics.widthPixels/2;
+ mPosY = metrics.heightPixels/2;
+ }
+
+ public void setDrawable(Drawable d) {
+ mDrawable = d;
+
+ float sizeX = mDrawable.getIntrinsicWidth()/2;
+ float sizeY = mDrawable.getIntrinsicHeight()/2;
+ mMatrix.reset();
+ mMatrix.postTranslate(-sizeX, -sizeY);
+ mMatrix.postScale(mScale, mScale);
+ mMatrix.postTranslate(mPosX, mPosY);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ mDetector.onTouchEvent(event);
+
+ // Handling single finger pan
+ if (!mDetector.isInProgress()) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLastX = event.getX();
+ mLastY = event.getY();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ final float x = event.getX();
+ final float y = event.getY();
+ mPosX += x - mLastX;
+ mPosY += y - mLastY;
+ mLastX = x;
+ mLastY = y;
+
+ float sizeX = mDrawable.getIntrinsicWidth()/2;
+ float sizeY = mDrawable.getIntrinsicHeight()/2;
+
+ mMatrix.reset();
+ mMatrix.postTranslate(-sizeX, -sizeY);
+ mMatrix.postScale(mScale, mScale);
+ mMatrix.postTranslate(mPosX, mPosY);
+ invalidate();
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int saveCount = canvas.getSaveCount();
+ canvas.save();
+ canvas.concat(mMatrix);
+ mDrawable.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index fdcada4..02b46aa 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1745,31 +1745,33 @@
fprintf(stderr, "ERROR: %s\n", error.string());
return -1;
}
- // asdf --> package.asdf
- // .asdf .a.b --> package.asdf package.a.b
- // asdf.adsf --> asdf.asdf
- String8 rule("-keep class ");
- const char* p = name.string();
- const char* q = strchr(p, '.');
- if (p == q) {
- rule += pkg;
- rule += name;
- } else if (q == NULL) {
- rule += pkg;
- rule += ".";
- rule += name;
- } else {
- rule += name;
+ if (name.length() > 0) {
+ // asdf --> package.asdf
+ // .asdf .a.b --> package.asdf package.a.b
+ // asdf.adsf --> asdf.asdf
+ String8 rule("-keep class ");
+ const char* p = name.string();
+ const char* q = strchr(p, '.');
+ if (p == q) {
+ rule += pkg;
+ rule += name;
+ } else if (q == NULL) {
+ rule += pkg;
+ rule += ".";
+ rule += name;
+ } else {
+ rule += name;
+ }
+
+ String8 location = tag;
+ location += " ";
+ location += assFile->getSourceFile();
+ char lineno[20];
+ sprintf(lineno, ":%d", tree.getLineNumber());
+ location += lineno;
+
+ keep->add(rule, location);
}
-
- String8 location = tag;
- location += " ";
- location += assFile->getSourceFile();
- char lineno[20];
- sprintf(lineno, ":%d", tree.getLineNumber());
- location += lineno;
-
- keep->add(rule, location);
}
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
index ff1b295..35f022e 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
@@ -20,6 +20,7 @@
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import javax.imageio.ImageIO;
@@ -33,6 +34,12 @@
mImage = ImageIO.read(input);
}
+ public Bitmap(InputStream is) throws IOException {
+ super(1, true, null, -1);
+
+ mImage = ImageIO.read(is);
+ }
+
Bitmap(BufferedImage image) {
super(1, true, null, -1);
mImage = image;
@@ -237,4 +244,35 @@
return createBitmap(colors, 0, width, width, height, config);
}
+ public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,
+ int dstHeight, boolean filter) {
+ Matrix m;
+ synchronized (Bitmap.class) {
+ // small pool of just 1 matrix
+ m = sScaleMatrix;
+ sScaleMatrix = null;
+ }
+
+ if (m == null) {
+ m = new Matrix();
+ }
+
+ final int width = src.getWidth();
+ final int height = src.getHeight();
+ final float sx = dstWidth / (float)width;
+ final float sy = dstHeight / (float)height;
+ m.setScale(sx, sy);
+ Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
+
+ synchronized (Bitmap.class) {
+ // do we need to check for null? why not just assign everytime?
+ if (sScaleMatrix == null) {
+ sScaleMatrix = m;
+ }
+ }
+
+ return b;
+ }
+
+
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
new file mode 100644
index 0000000..e978fe8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2010 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.graphics;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import java.io.BufferedInputStream;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Creates Bitmap objects from various sources, including files, streams,
+ * and byte-arrays.
+ */
+public class BitmapFactory {
+ public static class Options {
+ /**
+ * Create a default Options object, which if left unchanged will give
+ * the same result from the decoder as if null were passed.
+ */
+ public Options() {
+ inDither = true;
+ inScaled = true;
+ }
+
+ /**
+ * If set to true, the decoder will return null (no bitmap), but
+ * the out... fields will still be set, allowing the caller to query
+ * the bitmap without having to allocate the memory for its pixels.
+ */
+ public boolean inJustDecodeBounds;
+
+ /**
+ * If set to a value > 1, requests the decoder to subsample the original
+ * image, returning a smaller image to save memory. The sample size is
+ * the number of pixels in either dimension that correspond to a single
+ * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
+ * an image that is 1/4 the width/height of the original, and 1/16 the
+ * number of pixels. Any value <= 1 is treated the same as 1. Note: the
+ * decoder will try to fulfill this request, but the resulting bitmap
+ * may have different dimensions that precisely what has been requested.
+ * Also, powers of 2 are often faster/easier for the decoder to honor.
+ */
+ public int inSampleSize;
+
+ /**
+ * If this is non-null, the decoder will try to decode into this
+ * internal configuration. If it is null, or the request cannot be met,
+ * the decoder will try to pick the best matching config based on the
+ * system's screen depth, and characteristics of the original image such
+ * as if it has per-pixel alpha (requiring a config that also does).
+ */
+ public Bitmap.Config inPreferredConfig;
+
+ /**
+ * If dither is true, the decoder will attempt to dither the decoded
+ * image.
+ */
+ public boolean inDither;
+
+ /**
+ * The pixel density to use for the bitmap. This will always result
+ * in the returned bitmap having a density set for it (see
+ * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)). In addition,
+ * if {@link #inScaled} is set (which it is by default} and this
+ * density does not match {@link #inTargetDensity}, then the bitmap
+ * will be scaled to the target density before being returned.
+ *
+ * <p>If this is 0,
+ * {@link BitmapFactory#decodeResource(Resources, int)},
+ * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
+ * and {@link BitmapFactory#decodeResourceStream}
+ * will fill in the density associated with the resource. The other
+ * functions will leave it as-is and no density will be applied.
+ *
+ * @see #inTargetDensity
+ * @see #inScreenDensity
+ * @see #inScaled
+ * @see Bitmap#setDensity(int)
+ * @see android.util.DisplayMetrics#densityDpi
+ */
+ public int inDensity;
+
+ /**
+ * The pixel density of the destination this bitmap will be drawn to.
+ * This is used in conjunction with {@link #inDensity} and
+ * {@link #inScaled} to determine if and how to scale the bitmap before
+ * returning it.
+ *
+ * <p>If this is 0,
+ * {@link BitmapFactory#decodeResource(Resources, int)},
+ * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
+ * and {@link BitmapFactory#decodeResourceStream}
+ * will fill in the density associated the Resources object's
+ * DisplayMetrics. The other
+ * functions will leave it as-is and no scaling for density will be
+ * performed.
+ *
+ * @see #inDensity
+ * @see #inScreenDensity
+ * @see #inScaled
+ * @see android.util.DisplayMetrics#densityDpi
+ */
+ public int inTargetDensity;
+
+ /**
+ * The pixel density of the actual screen that is being used. This is
+ * purely for applications running in density compatibility code, where
+ * {@link #inTargetDensity} is actually the density the application
+ * sees rather than the real screen density.
+ *
+ * <p>By setting this, you
+ * allow the loading code to avoid scaling a bitmap that is currently
+ * in the screen density up/down to the compatibility density. Instead,
+ * if {@link #inDensity} is the same as {@link #inScreenDensity}, the
+ * bitmap will be left as-is. Anything using the resulting bitmap
+ * must also used {@link Bitmap#getScaledWidth(int)
+ * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
+ * Bitmap.getScaledHeight} to account for any different between the
+ * bitmap's density and the target's density.
+ *
+ * <p>This is never set automatically for the caller by
+ * {@link BitmapFactory} itself. It must be explicitly set, since the
+ * caller must deal with the resulting bitmap in a density-aware way.
+ *
+ * @see #inDensity
+ * @see #inTargetDensity
+ * @see #inScaled
+ * @see android.util.DisplayMetrics#densityDpi
+ */
+ public int inScreenDensity;
+
+ /**
+ * When this flag is set, if {@link #inDensity} and
+ * {@link #inTargetDensity} are not 0, the
+ * bitmap will be scaled to match {@link #inTargetDensity} when loaded,
+ * rather than relying on the graphics system scaling it each time it
+ * is drawn to a Canvas.
+ *
+ * <p>This flag is turned on by default and should be turned off if you need
+ * a non-scaled version of the bitmap. Nine-patch bitmaps ignore this
+ * flag and are always scaled.
+ */
+ public boolean inScaled;
+
+ /**
+ * If this is set to true, then the resulting bitmap will allocate its
+ * pixels such that they can be purged if the system needs to reclaim
+ * memory. In that instance, when the pixels need to be accessed again
+ * (e.g. the bitmap is drawn, getPixels() is called), they will be
+ * automatically re-decoded.
+ *
+ * For the re-decode to happen, the bitmap must have access to the
+ * encoded data, either by sharing a reference to the input
+ * or by making a copy of it. This distinction is controlled by
+ * inInputShareable. If this is true, then the bitmap may keep a shallow
+ * reference to the input. If this is false, then the bitmap will
+ * explicitly make a copy of the input data, and keep that. Even if
+ * sharing is allowed, the implementation may still decide to make a
+ * deep copy of the input data.
+ */
+ public boolean inPurgeable;
+
+ /**
+ * This field works in conjuction with inPurgeable. If inPurgeable is
+ * false, then this field is ignored. If inPurgeable is true, then this
+ * field determines whether the bitmap can share a reference to the
+ * input data (inputstream, array, etc.) or if it must make a deep copy.
+ */
+ public boolean inInputShareable;
+
+ /**
+ * Normally bitmap allocations count against the dalvik heap, which
+ * means they help trigger GCs when a lot have been allocated. However,
+ * in rare cases, the caller may want to allocate the bitmap outside of
+ * that heap. To request that, set inNativeAlloc to true. In these
+ * rare instances, it is solely up to the caller to ensure that OOM is
+ * managed explicitly by calling bitmap.recycle() as soon as such a
+ * bitmap is no longer needed.
+ *
+ * @hide pending API council approval
+ */
+ public boolean inNativeAlloc;
+
+ /**
+ * The resulting width of the bitmap, set independent of the state of
+ * inJustDecodeBounds. However, if there is an error trying to decode,
+ * outWidth will be set to -1.
+ */
+ public int outWidth;
+
+ /**
+ * The resulting height of the bitmap, set independent of the state of
+ * inJustDecodeBounds. However, if there is an error trying to decode,
+ * outHeight will be set to -1.
+ */
+ public int outHeight;
+
+ /**
+ * If known, this string is set to the mimetype of the decoded image.
+ * If not know, or there is an error, it is set to null.
+ */
+ public String outMimeType;
+
+ /**
+ * Temp storage to use for decoding. Suggest 16K or so.
+ */
+ public byte[] inTempStorage;
+
+ private native void requestCancel();
+
+ /**
+ * Flag to indicate that cancel has been called on this object. This
+ * is useful if there's an intermediary that wants to first decode the
+ * bounds and then decode the image. In that case the intermediary
+ * can check, inbetween the bounds decode and the image decode, to see
+ * if the operation is canceled.
+ */
+ public boolean mCancel;
+
+ /**
+ * This can be called from another thread while this options object is
+ * inside a decode... call. Calling this will notify the decoder that
+ * it should cancel its operation. This is not guaranteed to cancel
+ * the decode, but if it does, the decoder... operation will return
+ * null, or if inJustDecodeBounds is true, will set outWidth/outHeight
+ * to -1
+ */
+ public void requestCancelDecode() {
+ mCancel = true;
+ requestCancel();
+ }
+ }
+
+ /**
+ * Decode a file path into a bitmap. If the specified file name is null,
+ * or cannot be decoded into a bitmap, the function returns null.
+ *
+ * @param pathName complete path name for the file to be decoded.
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeFile(String pathName, Options opts) {
+ Bitmap bm = null;
+ InputStream stream = null;
+ try {
+ stream = new FileInputStream(pathName);
+ bm = decodeStream(stream, null, opts);
+ } catch (Exception e) {
+ /* do nothing.
+ If the exception happened on open, bm will be null.
+ */
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // do nothing here
+ }
+ }
+ }
+ return bm;
+ }
+
+ /**
+ * Decode a file path into a bitmap. If the specified file name is null,
+ * or cannot be decoded into a bitmap, the function returns null.
+ *
+ * @param pathName complete path name for the file to be decoded.
+ * @return the resulting decoded bitmap, or null if it could not be decoded.
+ */
+ public static Bitmap decodeFile(String pathName) {
+ return decodeFile(pathName, null);
+ }
+
+ /**
+ * Decode a new Bitmap from an InputStream. This InputStream was obtained from
+ * resources, which we pass to be able to scale the bitmap accordingly.
+ */
+ public static Bitmap decodeResourceStream(Resources res, TypedValue value,
+ InputStream is, Rect pad, Options opts) {
+
+ if (opts == null) {
+ opts = new Options();
+ }
+
+ if (opts.inDensity == 0 && value != null) {
+ final int density = value.density;
+ if (density == TypedValue.DENSITY_DEFAULT) {
+ opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (density != TypedValue.DENSITY_NONE) {
+ opts.inDensity = density;
+ }
+ }
+
+ if (opts.inTargetDensity == 0 && res != null) {
+ opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
+ }
+
+ return decodeStream(is, pad, opts);
+ }
+
+ /**
+ * Synonym for opening the given resource and calling
+ * {@link #decodeResourceStream}.
+ *
+ * @param res The resources object containing the image data
+ * @param id The resource id of the image data
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeResource(Resources res, int id, Options opts) {
+ Bitmap bm = null;
+ InputStream is = null;
+
+ try {
+ final TypedValue value = new TypedValue();
+ is = res.openRawResource(id, value);
+
+ bm = decodeResourceStream(res, value, is, null, opts);
+ } catch (Exception e) {
+ /* do nothing.
+ If the exception happened on open, bm will be null.
+ If it happened on close, bm is still valid.
+ */
+ } finally {
+ try {
+ if (is != null) is.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ return bm;
+ }
+
+ /**
+ * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}
+ * will null Options.
+ *
+ * @param res The resources object containing the image data
+ * @param id The resource id of the image data
+ * @return The decoded bitmap, or null if the image could not be decode.
+ */
+ public static Bitmap decodeResource(Resources res, int id) {
+ return decodeResource(res, id, null);
+ }
+
+ /**
+ * Decode an immutable bitmap from the specified byte array.
+ *
+ * @param data byte array of compressed image data
+ * @param offset offset into imageData for where the decoder should begin
+ * parsing.
+ * @param length the number of bytes, beginning at offset, to parse
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
+ if ((offset | length) < 0 || data.length < offset + length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ // FIXME: implement as needed, but it's unlikely that this is needed in the context of the bridge.
+ return null;
+ //return nativeDecodeByteArray(data, offset, length, opts);
+ }
+
+ /**
+ * Decode an immutable bitmap from the specified byte array.
+ *
+ * @param data byte array of compressed image data
+ * @param offset offset into imageData for where the decoder should begin
+ * parsing.
+ * @param length the number of bytes, beginning at offset, to parse
+ * @return The decoded bitmap, or null if the image could not be decode.
+ */
+ public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
+ return decodeByteArray(data, offset, length, null);
+ }
+
+ /**
+ * Decode an input stream into a bitmap. If the input stream is null, or
+ * cannot be used to decode a bitmap, the function returns null.
+ * The stream's position will be where ever it was after the encoded data
+ * was read.
+ *
+ * @param is The input stream that holds the raw data to be decoded into a
+ * bitmap.
+ * @param outPadding If not null, return the padding rect for the bitmap if
+ * it exists, otherwise set padding to [-1,-1,-1,-1]. If
+ * no bitmap is returned (null) then padding is
+ * unchanged.
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
+ // we don't throw in this case, thus allowing the caller to only check
+ // the cache, and not force the image to be decoded.
+ if (is == null) {
+ return null;
+ }
+
+ // we need mark/reset to work properly
+
+ if (!is.markSupported()) {
+ is = new BufferedInputStream(is, 16 * 1024);
+ }
+
+ // so we can call reset() if a given codec gives up after reading up to
+ // this many bytes. FIXME: need to find out from the codecs what this
+ // value should be.
+ is.mark(1024);
+
+ Bitmap bm;
+
+ if (is instanceof AssetManager.AssetInputStream) {
+ // FIXME: log this.
+ return null;
+ } else {
+ // pass some temp storage down to the native code. 1024 is made up,
+ // but should be large enough to avoid too many small calls back
+ // into is.read(...) This number is not related to the value passed
+ // to mark(...) above.
+ try {
+ bm = new Bitmap(is);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ return finishDecode(bm, outPadding, opts);
+ }
+
+ private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
+ if (bm == null || opts == null) {
+ return bm;
+ }
+
+ final int density = opts.inDensity;
+ if (density == 0) {
+ return bm;
+ }
+
+ bm.setDensity(density);
+ final int targetDensity = opts.inTargetDensity;
+ if (targetDensity == 0 || density == targetDensity
+ || density == opts.inScreenDensity) {
+ return bm;
+ }
+
+ byte[] np = bm.getNinePatchChunk();
+ final boolean isNinePatch = false; //np != null && NinePatch.isNinePatchChunk(np);
+ if (opts.inScaled || isNinePatch) {
+ float scale = targetDensity / (float)density;
+ // TODO: This is very inefficient and should be done in native by Skia
+ final Bitmap oldBitmap = bm;
+ bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
+ (int) (bm.getHeight() * scale + 0.5f), true);
+ oldBitmap.recycle();
+
+ if (isNinePatch) {
+ //np = nativeScaleNinePatch(np, scale, outPadding);
+ bm.setNinePatchChunk(np);
+ }
+ bm.setDensity(targetDensity);
+ }
+
+ return bm;
+ }
+
+ /**
+ * Decode an input stream into a bitmap. If the input stream is null, or
+ * cannot be used to decode a bitmap, the function returns null.
+ * The stream's position will be where ever it was after the encoded data
+ * was read.
+ *
+ * @param is The input stream that holds the raw data to be decoded into a
+ * bitmap.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeStream(InputStream is) {
+ return decodeStream(is, null, null);
+ }
+
+ /**
+ * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
+ * return null. The position within the descriptor will not be changed when
+ * this returns, so the descriptor can be used again as-is.
+ *
+ * @param fd The file descriptor containing the bitmap data to decode
+ * @param outPadding If not null, return the padding rect for the bitmap if
+ * it exists, otherwise set padding to [-1,-1,-1,-1]. If
+ * no bitmap is returned (null) then padding is
+ * unchanged.
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return the decoded bitmap, or null
+ */
+ public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {
+ return null;
+
+ /* FIXME: implement as needed
+ try {
+ if (MemoryFile.isMemoryFile(fd)) {
+ int mappedlength = MemoryFile.getMappedSize(fd);
+ MemoryFile file = new MemoryFile(fd, mappedlength, "r");
+ InputStream is = file.getInputStream();
+ Bitmap bm = decodeStream(is, outPadding, opts);
+ return finishDecode(bm, outPadding, opts);
+ }
+ } catch (IOException ex) {
+ // invalid filedescriptor, no need to call nativeDecodeFileDescriptor()
+ return null;
+ }
+ //Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+ //return finishDecode(bm, outPadding, opts);
+ */
+ }
+
+ /**
+ * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
+ * return null. The position within the descriptor will not be changed when
+ * this returns, so the descriptor can be used again as is.
+ *
+ * @param fd The file descriptor containing the bitmap data to decode
+ * @return the decoded bitmap, or null
+ */
+ public static Bitmap decodeFileDescriptor(FileDescriptor fd) {
+ return decodeFileDescriptor(fd, null, null);
+ }
+}
+
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
index 8bf7fcc..ad3974c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
@@ -16,6 +16,8 @@
package android.graphics;
+import java.awt.Paint;
+
public class BitmapShader extends Shader {
// we hold on just for the GC, since our native counterpart is using it
@@ -31,11 +33,16 @@
public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) {
mBitmap = bitmap;
}
-
+
//---------- Custom methods
-
+
public Bitmap getBitmap() {
return mBitmap;
}
+
+ @Override
+ Paint getJavaPaint() {
+ return null;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index 4986c77..9f4dfd0 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -125,13 +125,16 @@
}
Shader shader = paint.getShader();
- if (shader instanceof LinearGradient) {
- g.setPaint(((LinearGradient)shader).getPaint());
- } else {
- if (mLogger != null && shader != null) {
- mLogger.warning(String.format(
- "Shader '%1$s' is not supported in the Layout Editor.",
- shader.getClass().getCanonicalName()));
+ if (shader != null) {
+ java.awt.Paint shaderPaint = shader.getJavaPaint();
+ if (shaderPaint != null) {
+ g.setPaint(shaderPaint);
+ } else {
+ if (mLogger != null) {
+ mLogger.warning(String.format(
+ "Shader '%1$s' is not supported in the Layout Editor.",
+ shader.getClass().getCanonicalName()));
+ }
}
}
@@ -236,10 +239,15 @@
*/
@Override
public int save() {
+ // get the current save count
+ int count = mGraphicsStack.size();
+
+ // create a new graphics and add it to the stack
Graphics2D g = (Graphics2D)getGraphics2d().create();
mGraphicsStack.push(g);
- return mGraphicsStack.size() - 1;
+ // return the old save count
+ return count;
}
/* (non-Javadoc)
@@ -274,10 +282,9 @@
*/
@Override
public int getSaveCount() {
- return mGraphicsStack.size() - 1;
+ return mGraphicsStack.size();
}
-
/* (non-Javadoc)
* @see android.graphics.Canvas#clipRect(float, float, float, float, android.graphics.Region.Op)
*/
@@ -405,7 +412,7 @@
g.setColor(new Color(color));
- getGraphics2d().fillRect(0, 0, getWidth(), getHeight());
+ g.fillRect(0, 0, getWidth(), getHeight());
g.setComposite(composite);
@@ -953,10 +960,6 @@
*/
@Override
public void setMatrix(Matrix matrix) {
- // since SetMatrix *replaces* all the other transformation, we have to restore/save
- restore();
- save();
-
// get the new current graphics
Graphics2D g = getGraphics2d();
@@ -968,6 +971,27 @@
}
}
+ /* (non-Javadoc)
+ * @see android.graphics.Canvas#concat(android.graphics.Matrix)
+ */
+ @Override
+ public void concat(Matrix matrix) {
+ // get the current top graphics2D object.
+ Graphics2D g = getGraphics2d();
+
+ // get its current matrix
+ AffineTransform currentTx = g.getTransform();
+ // get the AffineTransform of the given matrix
+ AffineTransform matrixTx = matrix.getTransform();
+
+ // combine them so that the given matrix is applied after.
+ currentTx.preConcatenate(matrixTx);
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ g.setTransform(currentTx);
+ }
+
+
// --------------------
/* (non-Javadoc)
@@ -1008,15 +1032,6 @@
}
/* (non-Javadoc)
- * @see android.graphics.Canvas#concat(android.graphics.Matrix)
- */
- @Override
- public void concat(Matrix matrix) {
- // TODO Auto-generated method stub
- super.concat(matrix);
- }
-
- /* (non-Javadoc)
* @see android.graphics.Canvas#drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint)
*/
@Override
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
index 968a597..863d64a 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
@@ -16,6 +16,8 @@
package android.graphics;
+import java.awt.Paint;
+
/** A subclass of shader that returns the composition of two other shaders, combined by
an {@link android.graphics.Xfermode} subclass.
*/
@@ -42,5 +44,10 @@
public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
// FIXME Implement shader
}
+
+ @Override
+ Paint getJavaPaint() {
+ return null;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
index 1a0dc05..945a539 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
@@ -19,53 +19,302 @@
import java.awt.GradientPaint;
import java.awt.Color;
import java.awt.Paint;
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
public class LinearGradient extends Shader {
-
- private GradientPaint mGradientPaint;
- /** Create a shader that draws a linear gradient along a line.
- @param x0 The x-coordinate for the start of the gradient line
- @param y0 The y-coordinate for the start of the gradient line
- @param x1 The x-coordinate for the end of the gradient line
- @param y1 The y-coordinate for the end of the gradient line
- @param colors The colors to be distributed along the gradient line
- @param positions May be null. The relative positions [0..1] of
- each corresponding color in the colors array. If this is null,
- the the colors are distributed evenly along the gradient line.
- @param tile The Shader tiling mode
- */
- public LinearGradient(float x0, float y0, float x1, float y1,
- int colors[], float positions[], TileMode tile) {
+ private Paint mJavaPaint;
+
+ /**
+ * Create a shader that draws a linear gradient along a line.
+ *
+ * @param x0 The x-coordinate for the start of the gradient line
+ * @param y0 The y-coordinate for the start of the gradient line
+ * @param x1 The x-coordinate for the end of the gradient line
+ * @param y1 The y-coordinate for the end of the gradient line
+ * @param colors The colors to be distributed along the gradient line
+ * @param positions May be null. The relative positions [0..1] of each
+ * corresponding color in the colors array. If this is null, the
+ * the colors are distributed evenly along the gradient line.
+ * @param tile The Shader tiling mode
+ */
+ public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
+ TileMode tile) {
if (colors.length < 2) {
throw new IllegalArgumentException("needs >= 2 number of colors");
}
if (positions != null && colors.length != positions.length) {
throw new IllegalArgumentException("color and position arrays must be of equal length");
}
-
- // FIXME implement multi color linear gradient
+
+ if (colors.length == 2) { // for 2 colors: use the Java implementation
+ // The hasAlpha flag in Color() is only used to enforce alpha to 0xFF if false.
+ // If true the alpha is read from the int.
+ mJavaPaint = new GradientPaint(x0, y0, new Color(colors[0], true /* hasalpha */),
+ x1, y1, new Color(colors[1], true /* hasalpha */), tile != TileMode.CLAMP);
+ } else {
+ mJavaPaint = new MultiPointLinearGradientPaint(x0, y0, x1, y1, colors, positions, tile);
+ }
}
- /** Create a shader that draws a linear gradient along a line.
- @param x0 The x-coordinate for the start of the gradient line
- @param y0 The y-coordinate for the start of the gradient line
- @param x1 The x-coordinate for the end of the gradient line
- @param y1 The y-coordinate for the end of the gradient line
- @param color0 The color at the start of the gradient line.
- @param color1 The color at the end of the gradient line.
- @param tile The Shader tiling mode
- */
- public LinearGradient(float x0, float y0, float x1, float y1,
- int color0, int color1, TileMode tile) {
- mGradientPaint = new GradientPaint(x0, y0, new Color(color0, true /* hasalpha */),
- x1,y1, new Color(color1, true /* hasalpha */), tile != TileMode.CLAMP);
+ /**
+ * Create a shader that draws a linear gradient along a line.
+ *
+ * @param x0 The x-coordinate for the start of the gradient line
+ * @param y0 The y-coordinate for the start of the gradient line
+ * @param x1 The x-coordinate for the end of the gradient line
+ * @param y1 The y-coordinate for the end of the gradient line
+ * @param color0 The color at the start of the gradient line.
+ * @param color1 The color at the end of the gradient line.
+ * @param tile The Shader tiling mode
+ */
+ public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
+ TileMode tile) {
+ // The hasAlpha flag in Color() is only used to enforce alpha to 0xFF if false.
+ // If true the alpha is read from the int.
+ mJavaPaint = new GradientPaint(x0, y0, new Color(color0, true /* hasalpha */), x1, y1,
+ new Color(color1, true /* hasalpha */), tile != TileMode.CLAMP);
}
-
- //---------- Custom Methods
-
- public Paint getPaint() {
- return mGradientPaint;
+
+ // ---------- Custom Methods
+
+ @Override
+ public Paint getJavaPaint() {
+ return mJavaPaint;
+ }
+
+ private static class MultiPointLinearGradientPaint implements Paint {
+ private final static int GRADIENT_SIZE = 100;
+
+ private final float mX0;
+ private final float mY0;
+ private final float mDx;
+ private final float mDy;
+ private final float mDSize2;
+ private final int[] mColors;
+ private final float[] mPositions;
+ private final TileMode mTile;
+ private int[] mGradient;
+
+ public MultiPointLinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
+ float positions[], TileMode tile) {
+ mX0 = x0;
+ mY0 = y0;
+ mDx = x1 - x0;
+ mDy = y1 - y0;
+ mDSize2 = mDx * mDx + mDy * mDy;
+
+ mColors = colors;
+ mPositions = positions;
+ mTile = tile;
+ }
+
+ public PaintContext createContext(ColorModel cm, Rectangle deviceBounds,
+ Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+ prepareColors();
+ return new MultiPointLinearGradientPaintContext(cm, deviceBounds,
+ userBounds, xform, hints);
+ }
+
+ public int getTransparency() {
+ return TRANSLUCENT;
+ }
+
+ private synchronized void prepareColors() {
+ if (mGradient == null) {
+ // actually create an array with an extra size, so that we can really go
+ // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
+ mGradient = new int[GRADIENT_SIZE+1];
+
+ int prevPos = 0;
+ int nextPos = 1;
+ for (int i = 0 ; i <= GRADIENT_SIZE ; i++) {
+ // compute current position
+ float currentPos = (float)i/GRADIENT_SIZE;
+ while (currentPos > mPositions[nextPos]) {
+ prevPos = nextPos++;
+ }
+
+ float percent = (currentPos - mPositions[prevPos]) /
+ (mPositions[nextPos] - mPositions[prevPos]);
+
+ mGradient[i] = getColor(mColors[prevPos], mColors[nextPos], percent);
+ }
+ }
+ }
+
+ /**
+ * Returns the color between c1, and c2, based on the percent of the distance
+ * between c1 and c2.
+ */
+ private int getColor(int c1, int c2, float percent) {
+ int a = getChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
+ int r = getChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
+ int g = getChannel((c1 >> 8) & 0xFF, (c2 >> 8) & 0xFF, percent);
+ int b = getChannel((c1 ) & 0xFF, (c2 ) & 0xFF, percent);
+ return a << 24 | r << 16 | g << 8 | b;
+ }
+
+ /**
+ * Returns the channel value between 2 values based on the percent of the distance between
+ * the 2 values..
+ */
+ private int getChannel(int c1, int c2, float percent) {
+ return c1 + (int)((percent * (c2-c1)) + .5);
+ }
+
+ private class MultiPointLinearGradientPaintContext implements PaintContext {
+
+ private ColorModel mColorModel;
+ private final Rectangle mDeviceBounds;
+ private final Rectangle2D mUserBounds;
+ private final AffineTransform mXform;
+ private final RenderingHints mHints;
+
+ public MultiPointLinearGradientPaintContext(ColorModel cm, Rectangle deviceBounds,
+ Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+ mColorModel = cm;
+ // FIXME: so far all this is always the same rect gotten in getRaster with an indentity matrix?
+ mDeviceBounds = deviceBounds;
+ mUserBounds = userBounds;
+ mXform = xform;
+ mHints = hints;
+ }
+
+ public void dispose() {
+ }
+
+ public ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ public Raster getRaster(int x, int y, int w, int h) {
+ SampleModel sampleModel = mColorModel.createCompatibleSampleModel(w, h);
+ WritableRaster raster = Raster.createWritableRaster(sampleModel,
+ new java.awt.Point(x, y));
+
+ DataBuffer data = raster.getDataBuffer();
+
+ if (mDx == 0) { // vertical gradient
+ // compute first column and copy to all other columns
+ int index = 0;
+ for (int iy = 0 ; iy < h ; iy++) {
+ int color = getColor(iy + y, mY0, mDy);
+ for (int ix = 0 ; ix < w ; ix++) {
+ data.setElem(index++, color);
+ }
+ }
+ } else if (mDy == 0) { // horizontal
+ // compute first line in a tmp array and copy to all lines
+ int[] line = new int[w];
+ for (int ix = 0 ; ix < w ; ix++) {
+ line[ix] = getColor(ix + x, mX0, mDx);
+ }
+
+ int index = 0;
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ data.setElem(index++, line[ix]);
+ }
+ }
+ } else {
+ int index = 0;
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ data.setElem(index++, getColor(ix + x, iy + y));
+ }
+ }
+ }
+
+ return raster;
+ }
+ }
+
+ /** Returns a color for the easy vertical/horizontal mode */
+ private int getColor(float absPos, float refPos, float refSize) {
+ float pos = (absPos - refPos) / refSize;
+
+ return getIndexFromPos(pos);
+ }
+
+ /**
+ * Returns a color for an arbitrary point.
+ */
+ private int getColor(float x, float y) {
+ // find the x position on the gradient vector.
+ float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
+ // from it get the position relative to the vector
+ float pos = (float) ((_x - mX0) / mDx);
+
+ return getIndexFromPos(pos);
+ }
+
+ /**
+ * Returns the color based on the position in the gradient.
+ * <var>pos</var> can be anything, even < 0 or > > 1, as the gradient
+ * will use {@link TileMode} value to convert it into a [0,1] value.
+ */
+ private int getIndexFromPos(float pos) {
+ if (pos < 0.f) {
+ switch (mTile) {
+ case CLAMP:
+ pos = 0.f;
+ break;
+ case REPEAT:
+ // remove the integer part to stay in the [0,1] range
+ // careful: this is a negative value, so use ceil instead of floor
+ pos = pos - (float)Math.ceil(pos);
+ break;
+ case MIRROR:
+ // get the integer and the decimal part
+ // careful: this is a negative value, so use ceil instead of floor
+ int intPart = (int)Math.ceil(pos);
+ pos = pos - intPart;
+ // 0 -> -1 : mirrored order
+ // -1 -> -2: normal order
+ // etc..
+ // this means if the intpart is even we invert
+ if ((intPart % 2) == 0) {
+ pos = 1.f - pos;
+ }
+ break;
+ }
+ } else if (pos > 1f) {
+ switch (mTile) {
+ case CLAMP:
+ pos = 1.f;
+ break;
+ case REPEAT:
+ // remove the integer part to stay in the [0,1] range
+ pos = pos - (float)Math.floor(pos);
+ break;
+ case MIRROR:
+ // get the integer and the decimal part
+ int intPart = (int)Math.floor(pos);
+ pos = pos - intPart;
+ // 0 -> 1 : normal order
+ // 1 -> 2: mirrored
+ // etc..
+ // this means if the intpart is odd we invert
+ if ((intPart % 2) == 1) {
+ pos = 1.f - pos;
+ }
+ break;
+ }
+ }
+
+ int index = (int)((pos * GRADIENT_SIZE) + .5);
+
+ return mGradient[index];
+ }
}
}
-
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix.java b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
index 3974e08..522415c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
@@ -87,8 +87,12 @@
}
public AffineTransform getTransform() {
- return new AffineTransform(mValues[0], mValues[1], mValues[2],
- mValues[3], mValues[4], mValues[5]);
+ // the AffineTransform constructor takes the value in a different order
+ // for a matrix [ 0 1 2 ]
+ // [ 3 4 5 ]
+ // the order is 0, 3, 1, 4, 2, 5...
+ return new AffineTransform(mValues[0], mValues[3], mValues[1],
+ mValues[4], mValues[2], mValues[5]);
}
public boolean hasPerspective() {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java
index f3af133..312dab3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java
@@ -59,23 +59,14 @@
private final FontRenderContext mFontContext = new FontRenderContext(
new AffineTransform(), true, true);
- @SuppressWarnings("hiding")
public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG;
- @SuppressWarnings("hiding")
public static final int FILTER_BITMAP_FLAG = _Original_Paint.FILTER_BITMAP_FLAG;
- @SuppressWarnings("hiding")
public static final int DITHER_FLAG = _Original_Paint.DITHER_FLAG;
- @SuppressWarnings("hiding")
public static final int UNDERLINE_TEXT_FLAG = _Original_Paint.UNDERLINE_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int STRIKE_THRU_TEXT_FLAG = _Original_Paint.STRIKE_THRU_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int FAKE_BOLD_TEXT_FLAG = _Original_Paint.FAKE_BOLD_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int LINEAR_TEXT_FLAG = _Original_Paint.LINEAR_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int SUBPIXEL_TEXT_FLAG = _Original_Paint.SUBPIXEL_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int DEV_KERN_TEXT_FLAG = _Original_Paint.DEV_KERN_TEXT_FLAG;
public static class FontMetrics extends _Original_Paint.FontMetrics {
diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
index 61b693a..13848c5 100644
--- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
@@ -16,6 +16,8 @@
package android.graphics;
+import java.awt.Paint;
+
public class RadialGradient extends Shader {
/** Create a shader that draws a radial gradient given the center and radius.
@@ -58,5 +60,11 @@
}
// FIXME Implement shader
}
+
+ @Override
+ Paint getJavaPaint() {
+ // TODO Auto-generated method stub
+ return null;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader.java b/tools/layoutlib/bridge/src/android/graphics/Shader.java
index 3a9fda5..0cc5940 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Shader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Shader.java
@@ -16,14 +16,16 @@
package android.graphics;
+
+
/**
* Shader is the based class for objects that return horizontal spans of colors
* during drawing. A subclass of Shader is installed in a Paint calling
* paint.setShader(shader). After that any object (other than a bitmap) that is
* drawn with that paint will get its color(s) from the shader.
*/
-public class Shader {
-
+public abstract class Shader {
+
private final Matrix mMatrix = new Matrix();
public enum TileMode {
@@ -41,7 +43,7 @@
* mirror images so that adjacent images always seam
*/
MIRROR (2);
-
+
TileMode(int nativeInt) {
this.nativeInt = nativeInt;
}
@@ -57,7 +59,7 @@
if (localM != null) {
localM.set(mMatrix);
}
-
+
return !mMatrix.isIdentity();
}
@@ -73,4 +75,9 @@
mMatrix.reset();
}
}
+
+ /**
+ * Returns a java.awt.Paint object matching this shader.
+ */
+ abstract java.awt.Paint getJavaPaint();
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
index e79e970..21d8244 100644
--- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
@@ -16,6 +16,8 @@
package android.graphics;
+import java.awt.Paint;
+
public class SweepGradient extends Shader {
/**
@@ -41,7 +43,7 @@
throw new IllegalArgumentException(
"color and position arrays must be of equal length");
}
-
+
// FIXME Implement shader
}
@@ -56,5 +58,11 @@
public SweepGradient(float cx, float cy, int color0, int color1) {
// FIXME Implement shader
}
+
+ @Override
+ Paint getJavaPaint() {
+ // TODO Auto-generated method stub
+ return null;
+ }
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 5a13b0b..2623570 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -43,6 +43,7 @@
public final static String[] RENAMED_CLASSES =
new String[] {
"android.graphics.Bitmap", "android.graphics._Original_Bitmap",
+ "android.graphics.BitmapFactory", "android.graphics._Original_BitmapFactory",
"android.graphics.BitmapShader", "android.graphics._Original_BitmapShader",
"android.graphics.Canvas", "android.graphics._Original_Canvas",
"android.graphics.ComposeShader", "android.graphics._Original_ComposeShader",