diff options
49 files changed, 1143 insertions, 363 deletions
diff --git a/api/current.xml b/api/current.xml index 978b96e432db..cbccf93e5f64 100644 --- a/api/current.xml +++ b/api/current.xml @@ -95457,6 +95457,32 @@ visibility="public" > </method> +<method name="hasPermission" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.hardware.usb.UsbDevice"> +</parameter> +</method> +<method name="hasPermission" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="accessory" type="android.hardware.usb.UsbAccessory"> +</parameter> +</method> <method name="isFunctionEnabled" return="boolean" abstract="false" @@ -95509,6 +95535,36 @@ <parameter name="device" type="android.hardware.usb.UsbDevice"> </parameter> </method> +<method name="requestPermission" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.hardware.usb.UsbDevice"> +</parameter> +<parameter name="pi" type="android.app.PendingIntent"> +</parameter> +</method> +<method name="requestPermission" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="accessory" type="android.hardware.usb.UsbAccessory"> +</parameter> +<parameter name="pi" type="android.app.PendingIntent"> +</parameter> +</method> <field name="ACTION_USB_ACCESSORY_ATTACHED" type="java.lang.String" transient="false" @@ -95586,6 +95642,17 @@ visibility="public" > </field> +<field name="EXTRA_PERMISSION_GRANTED" + type="java.lang.String" + transient="false" + volatile="false" + value=""permission"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="USB_CONFIGURATION" type="java.lang.String" transient="false" @@ -267323,7 +267390,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="arg0" type="T"> +<parameter name="t" type="T"> </parameter> </method> </interface> diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 539e94601049..cc1f81c5ec00 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -401,10 +401,10 @@ class ContextImpl extends Context { return new UiModeManager(); }}); - registerService(USB_SERVICE, new StaticServiceFetcher() { - public Object createStaticService() { + registerService(USB_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(USB_SERVICE); - return new UsbManager(IUsbManager.Stub.asInterface(b)); + return new UsbManager(ctx, IUsbManager.Stub.asInterface(b)); }}); registerService(VIBRATOR_SERVICE, new ServiceFetcher() { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 440cb5406b85..efe263336063 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1150,8 +1150,12 @@ public class DevicePolicyManager { * fail (most commonly returning {@link #ENCRYPTION_STATUS_ACTIVE}). * * <p>This policy controls encryption of the secure (application data) storage area. Data - * written to other areas (e.g. the directory returned by - * {@link android.os.Environment#getExternalStorageDirectory()} may or may not be encrypted. + * written to other storage areas may or may not be encrypted, and this policy does not require + * or control the encryption of any other storage areas. + * There is one exception: If {@link android.os.Environment#isExternalStorageEmulated()} is + * {@code true}, then the directory returned by + * {@link android.os.Environment#getExternalStorageDirectory()} must be written to disk + * within the encrypted storage area. * * <p>Important Note: On some devices, it is possible to encrypt storage without requiring * the user to create a device PIN or Password. In this case, the storage is encrypted, but diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index be65bdb40389..c79a4583f8a8 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -16,6 +16,7 @@ package android.hardware.usb; +import android.app.PendingIntent; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; import android.os.Bundle; @@ -50,6 +51,25 @@ interface IUsbManager */ void setAccessoryPackage(in UsbAccessory accessory, String packageName); + /* Returns true if the caller has permission to access the device. */ + boolean hasDevicePermission(in UsbDevice device); + + /* Returns true if the caller has permission to access the accessory. */ + boolean hasAccessoryPermission(in UsbAccessory accessory); + + /* Requests permission for the given package to access the device. + * Will display a system dialog to query the user if permission + * had not already been given. + */ + void requestDevicePermission(in UsbDevice device, String packageName, in PendingIntent pi); + + /* Requests permission for the given package to access the accessory. + * Will display a system dialog to query the user if permission + * had not already been given. Result is returned via pi. + */ + void requestAccessoryPermission(in UsbAccessory accessory, String packageName, + in PendingIntent pi); + /* Grants permission for the given UID to access the device */ void grantDevicePermission(in UsbDevice device, int uid); diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 668317909a7f..9f1b8eaab2d7 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -17,6 +17,8 @@ package android.hardware.usb; +import android.app.PendingIntent; +import android.content.Context; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -176,12 +178,24 @@ public class UsbManager { */ public static final String EXTRA_ACCESSORY = "accessory"; - private IUsbManager mService; + /** + * Name of extra added to the {@link android.app.PendingIntent} + * passed into + * {#requestPermission(android.content.Context, android.hardware.usb.UsbDevice, android.app.PendingIntent)} + * or + * {#requestPermission(android.content.Context, android.hardware.usb.UsbAccessory, android.app.PendingIntent)} + * containing a boolean value indicating whether the user granted permission or not. + */ + public static final String EXTRA_PERMISSION_GRANTED = "permission"; + + private final Context mContext; + private final IUsbManager mService; /** * {@hide} */ - public UsbManager(IUsbManager service) { + public UsbManager(Context context, IUsbManager service) { + mContext = context; mService = service; } @@ -245,7 +259,7 @@ public class UsbManager { return new UsbAccessory[] { accessory }; } } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getAccessoryList" , e); + Log.e(TAG, "RemoteException in getAccessoryList", e); return null; } } @@ -260,11 +274,87 @@ public class UsbManager { try { return mService.openAccessory(accessory); } catch (RemoteException e) { - Log.e(TAG, "RemoteException in openAccessory" , e); + Log.e(TAG, "RemoteException in openAccessory", e); return null; } } + /** + * Returns true if the caller has permission to access the device. + * + * @param device to check permissions for + * @return true if caller has permission + */ + public boolean hasPermission(UsbDevice device) { + try { + return mService.hasDevicePermission(device); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in hasPermission", e); + return false; + } + } + + /** + * Returns true if the caller has permission to access the accessory. + * + * @param accessory to check permissions for + * @return true if caller has permission + */ + public boolean hasPermission(UsbAccessory accessory) { + try { + return mService.hasAccessoryPermission(accessory); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in hasPermission", e); + return false; + } + } + + /** + * Requests permission for the given package to access the device. + * This may result in a system dialog being displayed to the user + * if permission had not already been granted. + * Success or failure is returned via the {@link android.app.PendingIntent} pi. + * The following extras will be added to pi: + * <ul> + * <li> {@link #EXTRA_DEVICE} containing the device passed into this call + * <li> {@link #EXTRA_PERMISSION_GRANTED} containing boolean indicating whether + * permission was granted by the user + * </ul> + * + * @param device to request permissions for + * @param pi PendingIntent for returning result + */ + public void requestPermission(UsbDevice device, PendingIntent pi) { + try { + mService.requestDevicePermission(device, mContext.getPackageName(), pi); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in requestPermission", e); + } + } + + /** + * Requests permission for the given package to access the accessory. + * This may result in a system dialog being displayed to the user + * if permission had not already been granted. + * Success or failure is returned via the {@link android.app.PendingIntent} pi. + * The following extras will be added to pi: + * <ul> + * <li> {@link #EXTRA_ACCESSORY} containing the accessory passed into this call + * <li> {@link #EXTRA_PERMISSION_GRANTED} containing boolean indicating whether + * permission was granted by the user + * </ul> + * + * @param accessory to request permissions for + * @param pi PendingIntent for returning result + */ + public void requestPermission(UsbAccessory accessory, PendingIntent pi) { + try { + mService.requestAccessoryPermission(accessory, mContext.getPackageName(), pi); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in requestPermission", e); + } + } + private static File getFunctionEnableFile(String function) { return new File("/sys/class/usb_composite/" + function + "/enable"); } diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index ec5030c7f5db..e308c2c48a8d 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -423,9 +423,16 @@ public class Environment { /** * Returns whether the device has an external storage device which is - * emulated. If true, the device does not have real external storage - * and certain system services such as the package manager use this + * emulated. If true, the device does not have real external storage, and the directory + * returned by {@link #getExternalStorageDirectory()} will be allocated using a portion of + * the internal storage system. + * + * <p>Certain system services, such as the package manager, use this * to determine where to install an application. + * + * <p>Emulated external storage may also be encrypted - see + * {@link android.app.admin.DevicePolicyManager#setStorageEncryption( + * android.content.ComponentName, boolean)} for additional details. */ public static boolean isExternalStorageEmulated() { if (mIsExternalStorageEmulated == null) { diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index c078c08d2e61..0cf7ae60efa3 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -609,7 +609,7 @@ public abstract class HardwareRenderer { DisplayList displayList = view.getDisplayList(); if (displayList != null) { if (canvas.drawDisplayList(displayList, mRedrawClip)) { - if (mRedrawClip.isEmpty()) { + if (mRedrawClip.isEmpty() || view.getParent() == null) { view.invalidate(); } else { view.getParent().invalidateChild(view, mRedrawClip); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6ef680bacfb6..f9692dad0a30 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3680,7 +3680,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // flag coming from the child that initiated the invalidate if (view != null) { if ((view.mViewFlags & FADING_EDGE_MASK) != 0 && - view.getSolidColor() == 0 && !view.isOpaque()) { + view.getSolidColor() == 0) { opaqueFlag = DIRTY; } if ((view.mPrivateFlags & DIRTY_MASK) != DIRTY) { diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java index 2dac7e91151a..3cb5e24ec26c 100644 --- a/core/java/android/webkit/DebugFlags.java +++ b/core/java/android/webkit/DebugFlags.java @@ -31,7 +31,7 @@ class DebugFlags { public static final boolean CACHE_MANAGER = false; public static final boolean CALLBACK_PROXY = false; public static final boolean COOKIE_MANAGER = false; - public static final boolean COOKIE_SYNC_MANAGER = true; + public static final boolean COOKIE_SYNC_MANAGER = false; public static final boolean FRAME_LOADER = false; public static final boolean J_WEB_CORE_JAVA_BRIDGE = false;// HIGHLY VERBOSE public static final boolean LOAD_LISTENER = false; @@ -41,7 +41,7 @@ class DebugFlags { public static final boolean URL_UTIL = false; public static final boolean WEB_BACK_FORWARD_LIST = false; public static final boolean WEB_SETTINGS = false; - public static final boolean WEB_SYNC_MANAGER = true; + public static final boolean WEB_SYNC_MANAGER = false; public static final boolean WEB_TEXT_VIEW = false; public static final boolean WEB_VIEW = false; public static final boolean WEB_VIEW_CORE = false; diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index eb7269b16e70..9b1f157d9c72 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -3017,7 +3017,8 @@ public class WebView extends AbsoluteLayout } /** - * Start an ActionMode for finding text in this WebView. + * Start an ActionMode for finding text in this WebView. Only works if this + * WebView is attached to the view system. * @param text If non-null, will be the initial text to search for. * Otherwise, the last String searched for in this WebView will * be used to start. @@ -3027,7 +3028,7 @@ public class WebView extends AbsoluteLayout */ public boolean showFindDialog(String text, boolean showIme) { FindActionModeCallback callback = new FindActionModeCallback(mContext); - if (startActionMode(callback) == null) { + if (getParent() == null || startActionMode(callback) == null) { // Could not start the action mode, so end Find on page return false; } @@ -4035,15 +4036,10 @@ public class WebView extends AbsoluteLayout } } - void setBaseLayer(int layer, Rect invalRect, boolean showVisualIndciator) { + void setBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator) { if (mNativeClass == 0) return; - if (invalRect == null) { - Rect rect = new Rect(0, 0, mContentWidth, mContentHeight); - nativeSetBaseLayer(layer, rect, showVisualIndciator); - } else { - nativeSetBaseLayer(layer, invalRect, showVisualIndciator); - } + nativeSetBaseLayer(layer, invalRegion, showVisualIndicator); } private void onZoomAnimationStart() { @@ -5570,7 +5566,7 @@ public class WebView extends AbsoluteLayout ted.mReprocess = mDeferTouchProcess; ted.mNativeLayer = nativeScrollableLayer( contentX, contentY, ted.mNativeLayerRect, null); - ted.mDontEnqueueResult = true; + ted.mSequence = mTouchEventQueue.nextTouchSequence(); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); if (mDeferTouchProcess) { // still needs to set them for compute deltaX/Y @@ -5617,7 +5613,7 @@ public class WebView extends AbsoluteLayout ted.mReprocess = mDeferTouchProcess; ted.mNativeLayer = mScrollingLayer; ted.mNativeLayerRect.set(mScrollingLayerRect); - ted.mDontEnqueueResult = true; + ted.mSequence = mTouchEventQueue.nextTouchSequence(); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); mLastSentTouchTime = eventTime; if (mDeferTouchProcess) { @@ -5799,7 +5795,7 @@ public class WebView extends AbsoluteLayout ted.mReprocess = mDeferTouchProcess; ted.mNativeLayer = mScrollingLayer; ted.mNativeLayerRect.set(mScrollingLayerRect); - ted.mDontEnqueueResult = true; + ted.mSequence = mTouchEventQueue.nextTouchSequence(); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); } mLastTouchUpTime = eventTime; @@ -5822,7 +5818,7 @@ public class WebView extends AbsoluteLayout ted.mNativeLayer = nativeScrollableLayer( contentX, contentY, ted.mNativeLayerRect, null); - ted.mDontEnqueueResult = true; + ted.mSequence = mTouchEventQueue.nextTouchSequence(); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); } else if (mPreventDefault != PREVENT_DEFAULT_YES){ mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY); @@ -6040,7 +6036,7 @@ public class WebView extends AbsoluteLayout ted.mAction = MotionEvent.ACTION_CANCEL; ted.mNativeLayer = nativeScrollableLayer( x, y, ted.mNativeLayerRect, null); - ted.mDontEnqueueResult = true; + ted.mSequence = mTouchEventQueue.nextTouchSequence(); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); mPreventDefault = PREVENT_DEFAULT_IGNORE; } @@ -7524,7 +7520,7 @@ public class WebView extends AbsoluteLayout ted.mNativeLayer = nativeScrollableLayer( ted.mPoints[0].x, ted.mPoints[0].y, ted.mNativeLayerRect, null); - ted.mDontEnqueueResult = true; + ted.mSequence = mTouchEventQueue.nextTouchSequence(); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); } else if (mPreventDefault != PREVENT_DEFAULT_YES) { mTouchMode = TOUCH_DONE_MODE; @@ -7570,7 +7566,7 @@ public class WebView extends AbsoluteLayout case NEW_PICTURE_MSG_ID: { // called for new content final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj; - setBaseLayer(draw.mBaseLayer, draw.mInvalRegion.getBounds(), + setBaseLayer(draw.mBaseLayer, draw.mInvalRegion, getSettings().getShowVisualIndicator()); final Point viewSize = draw.mViewSize; WebViewCore.ViewState viewState = draw.mViewState; @@ -7740,9 +7736,12 @@ public class WebView extends AbsoluteLayout break; } TouchEventData ted = (TouchEventData) msg.obj; - if (!ted.mDontEnqueueResult) { - mTouchEventQueue.enqueueTouchEvent(ted); - } + + // WebCore is responding to us; remove pending timeout. + // It will be re-posted when needed. + removeMessages(PREVENT_DEFAULT_TIMEOUT); + + mTouchEventQueue.enqueueTouchEvent(ted); break; case REQUEST_KEYBOARD: @@ -8612,7 +8611,7 @@ public class WebView extends AbsoluteLayout private native void nativeSetFindIsEmpty(); private native void nativeSetFindIsUp(boolean isUp); private native void nativeSetHeightCanMeasure(boolean measure); - private native void nativeSetBaseLayer(int layer, Rect invalRect, + private native void nativeSetBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator); private native void nativeShowCursorTimed(); private native void nativeReplaceBaseContent(int content); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index f367b93820de..b920a30fd2a9 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -827,7 +827,6 @@ final class WebViewCore { Rect mNativeLayerRect = new Rect(); long mSequence; boolean mNativeResult; - boolean mDontEnqueueResult; } static class GeolocationPermissionsData { diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index f27ced89cbf8..942425af8e3b 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -16,6 +16,9 @@ package android.webkit; +import java.util.LinkedList; +import java.util.Queue; + import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Canvas; @@ -23,6 +26,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.SystemClock; +import android.util.FloatMath; import android.util.Log; import android.view.ScaleGestureDetector; import android.view.View; @@ -112,6 +116,24 @@ class ZoomManager { private float mZoomCenterY; /* + * Similar to mZoomCenterX(Y), these track the focus point of the scale + * gesture. The difference is these get updated every time when onScale is + * invoked no matter if a zooming really happens. + */ + private float mFocusX; + private float mFocusY; + + /* + * mFocusMovement keeps track of the total movement that the focus point + * has been through. Comparing to the difference of mCurrlen and mPrevLen, + * it determines if the gesture is for panning or zooming or both. + */ + private static final int FOCUS_QUEUE_SIZE = 5; + private float mFocusMovementSum; + private Queue<Float> mFocusMovementQueue; + + + /* * These values represent the point around which the screen should be * centered after zooming. In other words it is used to determine the center * point of the visible document after the page has finished zooming. This @@ -196,6 +218,8 @@ class ZoomManager { * viewport size is. */ setZoomOverviewWidth(WebView.DEFAULT_VIEWPORT_WIDTH); + + mFocusMovementQueue = new LinkedList<Float>(); } /** @@ -715,10 +739,11 @@ class ZoomManager { } private class ScaleDetectorListener implements ScaleGestureDetector.OnScaleGestureListener { - public boolean onScaleBegin(ScaleGestureDetector detector) { mInitialZoomOverview = false; dismissZoomPicker(); + mFocusMovementSum = 0; + mFocusMovementQueue.clear(); mWebView.mViewManager.startZoom(); mWebView.onPinchToZoomAnimationStart(); return true; @@ -729,6 +754,29 @@ class ZoomManager { float scale = Math.max( computeScaleWithLimits(detector.getScaleFactor() * mActualScale), getZoomOverviewScale()); + + float prevFocusX = mFocusX; + float prevFocusY = mFocusY; + mFocusX = detector.getFocusX(); + mFocusY = detector.getFocusY(); + float focusDelta = (prevFocusX == 0 && prevFocusY == 0) ? 0 : + FloatMath.sqrt((mFocusX - prevFocusX) * (mFocusX - prevFocusX) + + (mFocusY - prevFocusY) * (mFocusY - prevFocusY)); + mFocusMovementSum += focusDelta; + mFocusMovementQueue.add(focusDelta); + if (mFocusMovementQueue.size() > FOCUS_QUEUE_SIZE) { + mFocusMovementSum -= mFocusMovementQueue.remove(); + } + float deltaSpan = Math.abs(detector.getCurrentSpan() - detector.getPreviousSpan()); + + // If the user moves the fingers but keeps the same distance between them, + // we should do panning only. + if (mFocusMovementSum > deltaSpan) { + mFocusMovementSum = 0; + mFocusMovementQueue.clear(); + return true; + } + if (mPinchToZoomAnimating || willScaleTriggerZoom(scale)) { mPinchToZoomAnimating = true; // limit the scale change per step diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index d92588cbc646..17b3bdad7115 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -477,8 +477,7 @@ public class HorizontalScrollView extends FrameLayout { break; case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); - final float x = ev.getX(index); - mLastMotionX = x; + mLastMotionX = ev.getX(index); mActivePointerId = ev.getPointerId(index); break; } @@ -1446,6 +1445,7 @@ public class HorizontalScrollView extends FrameLayout { super.setOverScrollMode(mode); } + @SuppressWarnings({"SuspiciousNameCombination"}) @Override public void draw(Canvas canvas) { super.draw(canvas); diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 9002b1ddaf8a..72b70bc1a119 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1039,7 +1039,7 @@ public class PopupWindow { * * @return true if the popup is translated upwards to fit on screen */ - boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, + private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, int yoff) { anchor.getLocationInWindow(mDrawingLocation); diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 2d164fd75812..6088654a78c1 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -83,6 +83,7 @@ public class SearchView extends LinearLayout { private CursorAdapter mSuggestionsAdapter; private View mSearchButton; private View mSubmitButton; + private View mSearchPlate; private View mSubmitArea; private ImageView mCloseButton; private View mSearchEditFrame; @@ -190,6 +191,7 @@ public class SearchView extends LinearLayout { mQueryTextView.setSearchView(this); mSearchEditFrame = findViewById(R.id.search_edit_frame); + mSearchPlate = findViewById(R.id.search_plate); mSubmitArea = findViewById(R.id.submit_area); mSubmitButton = findViewById(R.id.search_go_btn); mCloseButton = (ImageView) findViewById(R.id.search_close_btn); @@ -258,6 +260,7 @@ public class SearchView extends LinearLayout { mSearchable = searchable; if (mSearchable != null) { updateSearchAutoComplete(); + updateQueryHint(); } // Cache the voice search capability mVoiceButtonEnabled = hasVoiceSearch(); @@ -575,19 +578,19 @@ public class SearchView extends LinearLayout { } private void updateSubmitButton(boolean hasText) { - mSubmitButton.setVisibility( - isSubmitAreaEnabled() ? (hasText ? VISIBLE : INVISIBLE) : GONE); + int visibility = GONE; + if (isSubmitAreaEnabled() && hasFocus() && (hasText || !mVoiceButtonEnabled)) { + visibility = VISIBLE; + } + mSubmitButton.setVisibility(visibility); } private void updateSubmitArea() { int visibility = GONE; - if (isSubmitAreaEnabled()) { - if (mSubmitButton.getVisibility() == VISIBLE - || mVoiceButton.getVisibility() == VISIBLE) { - visibility = VISIBLE; - } else { - visibility = INVISIBLE; - } + if (isSubmitAreaEnabled() + && (mSubmitButton.getVisibility() == VISIBLE + || mVoiceButton.getVisibility() == VISIBLE)) { + visibility = VISIBLE; } mSubmitArea.setVisibility(visibility); } @@ -601,6 +604,11 @@ public class SearchView extends LinearLayout { mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET); } + private void updateFocusedState(boolean focused) { + mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET); + mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET); + } + private void setImeVisibility(boolean visible) { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); @@ -851,16 +859,11 @@ public class SearchView extends LinearLayout { * Update the visibility of the voice button. There are actually two voice search modes, * either of which will activate the button. * @param empty whether the search query text field is empty. If it is, then the other - * criteria apply to make the voice button visible. Otherwise the voice button will not - * be visible - i.e., if the user has typed a query, remove the voice button. + * criteria apply to make the voice button visible. */ private void updateVoiceButton(boolean empty) { - // If the voice button is to be visible, show it - // Else, make it gone if the submit button is enabled, otherwise invisible to - // avoid losing the real-estate - int visibility = mSubmitButtonEnabled ? GONE : INVISIBLE; - - if (mVoiceButtonEnabled && !isIconified() && empty) { + int visibility = GONE; + if (mVoiceButtonEnabled && !isIconified() && (empty || !mSubmitButtonEnabled)) { visibility = VISIBLE; mSubmitButton.setVisibility(GONE); } @@ -958,7 +961,8 @@ public class SearchView extends LinearLayout { } void onTextFocusChanged() { - updateCloseButton(); + updateViewsVisibility(isIconified()); + updateFocusedState(mQueryTextView.hasFocus()); } private boolean onItemClicked(int position, int actionKey, String actionMsg) { diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index bab469b01cf9..21c61bdf67ab 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -531,6 +531,8 @@ public class StackView extends AdapterViewAnimator { @Override protected void dispatchDraw(Canvas canvas) { + boolean expandClipRegion = false; + canvas.getClipBounds(stackInvalidateRect); final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -540,12 +542,22 @@ public class StackView extends AdapterViewAnimator { child.getAlpha() == 0f || child.getVisibility() != VISIBLE) { lp.resetInvalidateRect(); } - stackInvalidateRect.union(lp.getInvalidateRect()); + Rect childInvalidateRect = lp.getInvalidateRect(); + if (!childInvalidateRect.isEmpty()) { + expandClipRegion = true; + stackInvalidateRect.union(childInvalidateRect); + } + } + + // We only expand the clip bounds if necessary. + if (expandClipRegion) { + canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect(stackInvalidateRect, Region.Op.UNION); + super.dispatchDraw(canvas); + canvas.restore(); + } else { + super.dispatchDraw(canvas); } - canvas.save(Canvas.CLIP_SAVE_FLAG); - canvas.clipRect(stackInvalidateRect, Region.Op.UNION); - super.dispatchDraw(canvas); - canvas.restore(); } private void onLayout() { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 09c1ac5e916a..13b9285f6fd5 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4498,8 +4498,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ canvas.restore(); - - updateCursorControllerPositions(); } private void updateCursorsPositions() { @@ -4557,15 +4555,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ protected void updateCursorControllerPositions() { - // No need to create the controllers if they were not already - if (mInsertionPointCursorController != null && - mInsertionPointCursorController.isShowing()) { - mInsertionPointCursorController.updatePosition(); - } - if (mSelectionModifierCursorController != null && - mSelectionModifierCursorController.isShowing()) { - mSelectionModifierCursorController.updatePosition(); - } + // TODO remove } @Override @@ -7356,14 +7346,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (isTextEditable() || mTextIsSelectable) { - if (mScrollX != oldScrollX || mScrollY != oldScrollY) { + if (mScrollX != oldScrollX || mScrollY != oldScrollY) { // TODO remove // Hide insertion anchor while scrolling. Leave selection. - hideInsertionPointCursorController(); - // No need to create the controller, since there is nothing to update. - if (mSelectionModifierCursorController != null && - mSelectionModifierCursorController.isShowing()) { - mSelectionModifierCursorController.updatePosition(); - } + hideInsertionPointCursorController(); // TODO any motion should hide it } if (touchIsFinished) { @@ -7373,7 +7358,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener handled |= imm != null && imm.showSoftInput(this, 0); } - boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect(); if (!selectAllGotFocus && hasSelection()) { startSelectionActionMode(); @@ -8653,26 +8637,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private class HandleView extends View implements ViewTreeObserver.OnScrollChangedListener { + private class HandleView extends View implements ViewTreeObserver.OnPreDrawListener { private Drawable mDrawable; - private final ScrollingPopupWindow mContainer; - private int mPositionX; - private int mPositionY; + private final PopupWindow mContainer; + // Position with respect to the parent TextView + private int mPositionX, mPositionY; private final CursorController mController; private boolean mIsDragging; - private float mTouchToWindowOffsetX; - private float mTouchToWindowOffsetY; + // Offset from touch position to mPosition + private float mTouchToWindowOffsetX, mTouchToWindowOffsetY; private float mHotspotX; // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up private float mTouchOffsetY; // Where the touch position should be on the handle to ensure a maximum cursor visibility private float mIdealVerticalOffset; - private int mLastParentX; - private int mLastParentY; + // Parent's (TextView) position in window + private int mLastParentX, mLastParentY; private float mDownPositionX, mDownPositionY; + // PopupWindow container absolute position with respect to the enclosing window private int mContainerPositionX, mContainerPositionY; - private long mTouchTimer; + // Visible or not (scrolled off screen), whether or not this handle should be visible + private boolean mIsActive = false; + // The insertion handle can have an associated PastePopupMenu private boolean mIsInsertionHandle = false; + // Used to detect taps on the insertion handle, which will affect the PastePopupMenu + private long mTouchTimer; private PastePopupMenu mPastePopupWindow; // Touch-up filter: number of previous positions remembered @@ -8684,12 +8673,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mPreviousOffsetIndex = 0; private int mNumberPreviousOffsets = 0; - public void startTouchUpFilter(int offset) { + private void startTouchUpFilter(int offset) { mNumberPreviousOffsets = 0; addPositionToTouchUpFilter(offset); } - public void addPositionToTouchUpFilter(int offset) { + private void addPositionToTouchUpFilter(int offset) { if (mNumberPreviousOffsets > 0 && mPreviousOffsets[mPreviousOffsetIndex] == offset) { // Make sure only actual changes of position are recorded. @@ -8702,7 +8691,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mNumberPreviousOffsets++; } - public void filterOnTouchUp() { + private void filterOnTouchUp() { final long now = SystemClock.uptimeMillis(); int i = 0; int index = mPreviousOffsetIndex; @@ -8725,16 +8714,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public HandleView(CursorController controller, int pos) { super(TextView.this.mContext); mController = controller; - mContainer = new ScrollingPopupWindow(TextView.this.mContext, null, + mContainer = new PopupWindow(TextView.this.mContext, null, com.android.internal.R.attr.textSelectHandleWindowStyle); mContainer.setSplitTouchEnabled(true); mContainer.setClippingEnabled(false); mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); + mContainer.setContentView(this); - setOrientation(pos); + setPosition(pos); } - public void setOrientation(int pos) { + private void setPosition(int pos) { int handleWidth; switch (pos) { case LEFT: { @@ -8774,38 +8764,48 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } final int handleHeight = mDrawable.getIntrinsicHeight(); - mTouchOffsetY = -0.3f * handleHeight; mIdealVerticalOffset = 0.7f * handleHeight; + invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mDrawable.getIntrinsicWidth(), - mDrawable.getIntrinsicHeight()); + setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); } public void show() { - if (!isPositionVisible()) { - hide(); - return; - } - mContainer.setContentView(this); - mContainerPositionX = mPositionX; - mContainerPositionY = mPositionY - TextView.this.getHeight(); - mContainer.showAsDropDown(TextView.this, mContainerPositionX, mContainerPositionY); + updateContainerPosition(); + if (isShowing()) { + mContainer.update(mContainerPositionX, mContainerPositionY, + mRight - mLeft, mBottom - mTop); - // Hide paste view when handle is moved on screen. - hidePastePopupWindow(); + hidePastePopupWindow(); + } else { + mContainer.showAtLocation(TextView.this, 0, + mContainerPositionX, mContainerPositionY); + + mIsActive = true; + + ViewTreeObserver vto = TextView.this.getViewTreeObserver(); + vto.addOnPreDrawListener(this); + } } - public void hide() { + private void dismiss() { mIsDragging = false; mContainer.dismiss(); hidePastePopupWindow(); + } + + public void hide() { + dismiss(); + + mIsActive = false; + ViewTreeObserver vto = TextView.this.getViewTreeObserver(); - vto.removeOnScrollChangedListener(this); + vto.removeOnPreDrawListener(this); } public boolean isShowing() { @@ -8856,44 +8856,59 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void moveTo(int x, int y) { mPositionX = x - TextView.this.mScrollX; mPositionY = y - TextView.this.mScrollY; - if (isPositionVisible()) { - int[] coords = null; - if (mContainer.isShowing()) { - final int containerPositionX = mPositionX; - final int containerPositionY = mPositionY - TextView.this.getHeight(); - - if (containerPositionX != mContainerPositionX || - containerPositionY != mContainerPositionY) { - mContainerPositionX = containerPositionX; - mContainerPositionY = containerPositionY; - - mContainer.update(TextView.this, mContainerPositionX, mContainerPositionY, - mRight - mLeft, mBottom - mTop); - - // Hide paste popup window as soon as a scroll occurs. - hidePastePopupWindow(); - } - } else { - show(); + + if (mIsDragging) { + TextView.this.getLocationInWindow(mTempCoords); + if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) { + mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX; + mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY; + mLastParentX = mTempCoords[0]; + mLastParentY = mTempCoords[1]; } + // Hide paste popup window as soon as the handle is dragged. + hidePastePopupWindow(); + } + } - if (mIsDragging) { - if (coords == null) { - coords = mTempCoords; - TextView.this.getLocationInWindow(coords); + /** + * Updates the global container's position. + * @return whether or not the position has actually changed + */ + private boolean updateContainerPosition() { + // TODO Prevent this using different HandleView subclasses + mController.updateOffset(this, mController.getCurrentOffset(this)); + TextView.this.getLocationInWindow(mTempCoords); + final int containerPositionX = mTempCoords[0] + mPositionX; + final int containerPositionY = mTempCoords[1] + mPositionY; + + if (containerPositionX != mContainerPositionX || + containerPositionY != mContainerPositionY) { + mContainerPositionX = containerPositionX; + mContainerPositionY = containerPositionY; + return true; + } + return false; + } + + public boolean onPreDraw() { + if (updateContainerPosition()) { + if (isPositionVisible()) { + mContainer.update(mContainerPositionX, mContainerPositionY, + mRight - mLeft, mBottom - mTop); + + if (mIsActive && !isShowing()) { + show(); } - if (coords[0] != mLastParentX || coords[1] != mLastParentY) { - mTouchToWindowOffsetX += coords[0] - mLastParentX; - mTouchToWindowOffsetY += coords[1] - mLastParentY; - mLastParentX = coords[0]; - mLastParentY = coords[1]; + } else { + if (isShowing()) { + dismiss(); } - // Hide paste popup window as soon as the handle is dragged. - hidePastePopupWindow(); } - } else { - hide(); + + // Hide paste popup as soon as the view is scrolled or moved + hidePastePopupWindow(); } + return true; } @Override @@ -8979,7 +8994,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mIsDragging; } - void positionAtCursor(final int offset) { + void positionAtCursor(int offset) { addPositionToTouchUpFilter(offset); final int width = mDrawable.getIntrinsicWidth(); final int height = mDrawable.getIntrinsicHeight(); @@ -9013,50 +9028,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mPastePopupWindow.hide(); } } - - /** - * A popup window, attached to a view, and that listens to scroll events in its anchors' - * view hierarchy, so that it is automatically moved on such events. - */ - private class ScrollingPopupWindow extends PopupWindow { - - private int[] mDrawingLocations = new int[2]; - - public ScrollingPopupWindow(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, - int xoff, int yoff) { - anchor.getLocationInWindow(mDrawingLocations); - p.x = mDrawingLocations[0] + xoff; - p.y = mDrawingLocations[1] + anchor.getHeight() + yoff; - - // Hide paste popup as soon as the view is scrolled. - hidePastePopupWindow(); - - if (!isPositionVisible()) { - dismiss(); - onHandleBecomeInvisible(); - } - - return false; - } - } - - public void onScrollChanged() { - if (isPositionVisible()) { - show(); - ViewTreeObserver vto = TextView.this.getViewTreeObserver(); - vto.removeOnScrollChangedListener(this); - } - } - - public void onHandleBecomeInvisible() { - ViewTreeObserver vto = TextView.this.getViewTreeObserver(); - vto.addOnScrollChangedListener(this); - } } private class InsertionPointCursorController implements CursorController { @@ -9074,7 +9045,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public void show(int delayBeforePaste) { - updatePosition(); + getHandle().show(); hideDelayed(); removePastePopupCallback(); final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime; @@ -9213,7 +9184,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mEndHandle == null) mEndHandle = new HandleView(this, HandleView.RIGHT); mIsShowing = true; - updatePosition(); mStartHandle.show(); mEndHandle.show(); diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index a41b348af629..586ba87707bf 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -43,6 +43,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.Window; import android.widget.AdapterView; +import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; @@ -957,4 +958,60 @@ public class ActionBarView extends ViewGroup { } } } + + private static class HomeView extends FrameLayout { + private View mUpView; + private View mIconView; + + public HomeView(Context context) { + this(context, null); + } + + public HomeView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + mUpView = findViewById(com.android.internal.R.id.up); + mIconView = (ImageView) findViewById(com.android.internal.R.id.home); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0); + final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams(); + int width = upLp.leftMargin + mUpView.getMeasuredWidth() + upLp.rightMargin; + int height = upLp.topMargin + mUpView.getMeasuredHeight() + upLp.bottomMargin; + measureChildWithMargins(mIconView, widthMeasureSpec, width, heightMeasureSpec, 0); + final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams(); + width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin; + height = Math.max(height, + iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin); + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int vCenter = (b - t) / 2; + int width = r - l; + if (mUpView.getVisibility() != GONE) { + final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams(); + final int upHeight = mUpView.getMeasuredHeight(); + final int upWidth = mUpView.getMeasuredWidth(); + final int upTop = t + vCenter - upHeight / 2; + mUpView.layout(l, upTop, l + upWidth, upTop + upHeight); + final int upOffset = upLp.leftMargin + upWidth + upLp.rightMargin; + width -= upOffset; + l += upOffset; + } + final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams(); + final int iconHeight = mIconView.getMeasuredHeight(); + final int iconWidth = mIconView.getMeasuredWidth(); + final int hCenter = (r - l) / 2; + final int iconLeft = l + iconLp.leftMargin + hCenter - iconWidth / 2; + final int iconTop = t + iconLp.topMargin + vCenter - iconHeight / 2; + mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight); + } + } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0ad174fbf38f..c684e7e7b871 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1379,12 +1379,6 @@ android:excludeFromRecents="true"> </activity> - <activity android:name="com.android.server.usb.UsbResolverActivity" - android:theme="@style/Theme.Holo.Dialog.Alert" - android:finishOnCloseSystemDialogs="true" - android:excludeFromRecents="true"> - </activity> - <service android:name="com.android.server.LoadAverageService" android:exported="true" /> diff --git a/core/res/res/drawable-hdpi/text_cursor_holo_dark.9.png b/core/res/res/drawable-hdpi/text_cursor_holo_dark.9.png Binary files differindex b9435b645117..ae77fa0cadbc 100644 --- a/core/res/res/drawable-hdpi/text_cursor_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/text_cursor_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/text_cursor_holo_light.9.png b/core/res/res/drawable-hdpi/text_cursor_holo_light.9.png Binary files differindex 477d820cc965..c6bdfcc6a3bd 100644 --- a/core/res/res/drawable-hdpi/text_cursor_holo_light.9.png +++ b/core/res/res/drawable-hdpi/text_cursor_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png b/core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png Binary files differindex b9435b645117..ae77fa0cadbc 100644 --- a/core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/text_cursor_holo_light.9.png b/core/res/res/drawable-mdpi/text_cursor_holo_light.9.png Binary files differindex 477d820cc965..c6bdfcc6a3bd 100644 --- a/core/res/res/drawable-mdpi/text_cursor_holo_light.9.png +++ b/core/res/res/drawable-mdpi/text_cursor_holo_light.9.png diff --git a/core/res/res/layout/action_bar_home.xml b/core/res/res/layout/action_bar_home.xml index 7867577a9442..c82f91d7dfd8 100644 --- a/core/res/res/layout/action_bar_home.xml +++ b/core/res/res/layout/action_bar_home.xml @@ -14,14 +14,14 @@ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:background="?android:attr/selectableItemBackground" - android:orientation="horizontal"> +<view xmlns:android="http://schemas.android.com/apk/res/android" + class="com.android.internal.widget.ActionBarView$HomeView" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="?android:attr/selectableItemBackground" > <ImageView android:id="@android:id/up" android:src="?android:attr/homeAsUpIndicator" - android:layout_gravity="top|left" + android:layout_gravity="center_vertical|left" android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -33,4 +33,4 @@ android:paddingRight="16dip" android:layout_gravity="center" android:scaleType="center" /> -</LinearLayout> +</view> diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml index 93b6deb6e57e..c52b73f874d2 100644 --- a/core/res/res/layout/search_view.xml +++ b/core/res/res/layout/search_view.xml @@ -54,6 +54,10 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical" + android:layout_marginLeft="4dip" + android:layout_marginRight="4dip" + android:layout_marginTop="4dip" + android:layout_marginBottom="4dip" android:orientation="horizontal"> <!-- Inner layout contains the app icon, button(s) and EditText --> diff --git a/core/res/res/raw-xlarge/incognito_mode_start_page.html b/core/res/res/raw-xlarge/incognito_mode_start_page.html new file mode 100644 index 000000000000..492658d29484 --- /dev/null +++ b/core/res/res/raw-xlarge/incognito_mode_start_page.html @@ -0,0 +1,24 @@ +<html> + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/> + <title>New incognito tab</title> + </head> + <body> + <p><strong>You've gone incognito</strong>. Pages you view in this tab + won't appear in your browser history or search history, and they won't + leave other traces, like cookies, on your device after you close the + incognito tab. Any files you download or bookmarks you create will be + preserved, however.</p> + + <p><strong>Going incognito doesn't affect the behavior of other people, + servers, or software. Be wary of:</strong></p> + + <ul> + <li>Websites that collect or share information about you</li> + <li>Internet service providers or employers that track the pages you visit</li> + <li>Malicious software that tracks your keystrokes in exchange for free smileys</li> + <li>Surveillance by secret agents</li> + <li>People standing behind you</li> + </ul> + </body> +</html> diff --git a/docs/html/images/avd-manager.png b/docs/html/images/avd-manager.png Binary files differindex 69ce972f8dbc..c33d8a885177 100644 --- a/docs/html/images/avd-manager.png +++ b/docs/html/images/avd-manager.png diff --git a/docs/html/images/billing_package.png b/docs/html/images/billing_package.png Binary files differindex ec04c2d98153..951e117d4446 100755..100644 --- a/docs/html/images/billing_package.png +++ b/docs/html/images/billing_package.png diff --git a/docs/html/images/developing/adt-props-isLib.png b/docs/html/images/developing/adt-props-isLib.png Binary files differindex 18bdb3392380..49c911175b96 100644 --- a/docs/html/images/developing/adt-props-isLib.png +++ b/docs/html/images/developing/adt-props-isLib.png diff --git a/docs/html/images/developing/adt-props-libRef.png b/docs/html/images/developing/adt-props-libRef.png Binary files differindex e61df51424e4..73bccbd0e6d4 100644 --- a/docs/html/images/developing/adt-props-libRef.png +++ b/docs/html/images/developing/adt-props-libRef.png diff --git a/docs/html/images/licensing_add_library.png b/docs/html/images/licensing_add_library.png Binary files differindex 90b4435ab017..3bbe6d5c6efc 100644 --- a/docs/html/images/licensing_add_library.png +++ b/docs/html/images/licensing_add_library.png diff --git a/docs/html/images/licensing_gapis_8.png b/docs/html/images/licensing_gapis_8.png Binary files differindex 43ad26233261..480d98992143 100644 --- a/docs/html/images/licensing_gapis_8.png +++ b/docs/html/images/licensing_gapis_8.png diff --git a/docs/html/images/licensing_package.png b/docs/html/images/licensing_package.png Binary files differindex 5da5632672b6..eb2c5cfd3f2c 100644 --- a/docs/html/images/licensing_package.png +++ b/docs/html/images/licensing_package.png diff --git a/media/java/android/mtp/MtpClient.java b/media/java/android/mtp/MtpClient.java index 40e2f9beed36..d25dcb9f5a68 100644 --- a/media/java/android/mtp/MtpClient.java +++ b/media/java/android/mtp/MtpClient.java @@ -16,6 +16,7 @@ package android.mtp; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -41,6 +42,9 @@ public class MtpClient { private static final String TAG = "MtpClient"; + private static final String ACTION_USB_PERMISSION = + "android.mtp.MtpClient.action.USB_PERMISSION"; + private final Context mContext; private final UsbManager mUsbManager; private final ArrayList<Listener> mListeners = new ArrayList<Listener>(); @@ -49,29 +53,47 @@ public class MtpClient { // mDevices is also used for synchronization in this class. private final HashMap<String, MtpDevice> mDevices = new HashMap<String, MtpDevice>(); + private final PendingIntent mPermissionIntent; + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); UsbDevice usbDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); String deviceName = usbDevice.getDeviceName(); synchronized (mDevices) { MtpDevice mtpDevice = mDevices.get(deviceName); - if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { + if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { if (mtpDevice == null) { mtpDevice = openDeviceLocked(usbDevice); } if (mtpDevice != null) { - mDevices.put(deviceName, mtpDevice); for (Listener listener : mListeners) { listener.deviceAdded(mtpDevice); } } - } else if (mtpDevice != null) { - mDevices.remove(deviceName); - for (Listener listener : mListeners) { - listener.deviceRemoved(mtpDevice); + } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { + if (mtpDevice != null) { + mDevices.remove(deviceName); + for (Listener listener : mListeners) { + listener.deviceRemoved(mtpDevice); + } + } + } else if (ACTION_USB_PERMISSION.equals(action)) { + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + Log.d(TAG, "ACTION_USB_PERMISSION: " + permission); + if (permission) { + if (mtpDevice == null) { + mtpDevice = openDeviceLocked(usbDevice); + } + if (mtpDevice != null) { + for (Listener listener : mListeners) { + listener.deviceAdded(mtpDevice); + } + } } } } @@ -126,10 +148,11 @@ public class MtpClient { public MtpClient(Context context) { mContext = context; mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE); - + mPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + filter.addAction(ACTION_USB_PERMISSION); context.registerReceiver(mUsbReceiver, filter); } @@ -142,9 +165,14 @@ public class MtpClient { */ private MtpDevice openDeviceLocked(UsbDevice usbDevice) { if (isCamera(usbDevice)) { - MtpDevice mtpDevice = new MtpDevice(usbDevice); - if (mtpDevice.open(mUsbManager)) { - return mtpDevice; + if (!mUsbManager.hasPermission(usbDevice)) { + mUsbManager.requestPermission(usbDevice, mPermissionIntent); + } else { + MtpDevice mtpDevice = new MtpDevice(usbDevice); + if (mtpDevice.open(mUsbManager)) { + mDevices.put(usbDevice.getDeviceName(), mtpDevice); + return mtpDevice; + } } } return null; @@ -218,13 +246,8 @@ public class MtpClient { // Query the USB manager since devices might have attached // before we added our listener. for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { - String deviceName = usbDevice.getDeviceName(); - MtpDevice mtpDevice = mDevices.get(deviceName); - if (mtpDevice == null) { - mtpDevice = openDeviceLocked(usbDevice); - } - if (mtpDevice != null) { - mDevices.put(deviceName, mtpDevice); + if (mDevices.get(usbDevice.getDeviceName()) == null) { + openDeviceLocked(usbDevice); } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index d3d175089680..fee245f4b456 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -8,6 +8,7 @@ <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.GET_TASKS" /> + <uses-permission android:name="android.permission.MANAGE_USB" /> <application android:persistent="true" @@ -39,5 +40,22 @@ android:exported="true"> </activity> + <!-- started from UsbDeviceSettingsManager --> + <activity android:name=".usb.UsbPermissionActivity" + android:exported="true" + android:permission="android.permission.MANAGE_USB" + android:theme="@*android:style/Theme.Holo.Dialog.Alert" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true"> + </activity> + + <!-- started from UsbDeviceSettingsManager --> + <activity android:name=".usb.UsbResolverActivity" + android:exported="true" + android:permission="android.permission.MANAGE_USB" + android:theme="@*android:style/Theme.Holo.Dialog.Alert" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true"> + </activity> </application> </manifest> diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_recent_item.xml b/packages/SystemUI/res/layout-xlarge/status_bar_recent_item.xml index bfa6c36a096e..3f172e68f3f8 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar_recent_item.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar_recent_item.xml @@ -38,8 +38,8 @@ android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" - android:layout_marginLeft="123dip" - android:layout_marginTop="16dip" + android:layout_marginLeft="131dip" + android:layout_marginTop="13dip" android:maxWidth="@dimen/status_bar_recents_thumbnail_max_width" android:maxHeight="@dimen/status_bar_recents_thumbnail_max_height" android:adjustViewBounds="true" diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-xlarge/status_bar_recent_panel.xml index eda19b742d17..42940be6f1b8 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar_recent_panel.xml @@ -51,7 +51,7 @@ android:stackFromBottom="true" android:fadingEdge="vertical" android:scrollbars="none" - android:fadingEdgeLength="30dip" + android:fadingEdgeLength="20dip" android:listSelector="@drawable/recents_thumbnail_bg_selector" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ebd48e7b68af..becad6aa24f0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -115,4 +115,11 @@ <!-- Label of a toggle switch to disable use of the physical keyboard in favor of the IME. [CHAR LIMIT=25] --> <string name="status_bar_use_physical_keyboard">Use physical keyboard</string> + + <!-- Prompt for the USB device permission dialog [CHAR LIMIT=80] --> + <string name="usb_device_permission_prompt">Allow the application %1$s to access the USB device?</string> + + <!-- Prompt for the USB accessory permission dialog [CHAR LIMIT=80] --> + <string name="usb_accessory_permission_prompt">Allow the application %1$s to access the USB accessory?</string> + </resources> diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDisconnectedReceiver.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDisconnectedReceiver.java new file mode 100644 index 000000000000..1edebbbacd79 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDisconnectedReceiver.java @@ -0,0 +1,67 @@ +/* + * 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 com.android.systemui.usb; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +// This class is used to close UsbPermissionsActivity and UsbResolverActivity +// if their device/accessory is disconnected while the dialog is still open +class UsbDisconnectedReceiver extends BroadcastReceiver { + private final Activity mActivity; + private UsbDevice mDevice; + private UsbAccessory mAccessory; + + public UsbDisconnectedReceiver(Activity activity, UsbDevice device) { + mActivity = activity; + mDevice = device; + + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED); + activity.registerReceiver(this, filter); + } + + public UsbDisconnectedReceiver(Activity activity, UsbAccessory accessory) { + mActivity = activity; + mAccessory = accessory; + + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_ACCESSORY_DETACHED); + activity.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { + UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (device != null && device.equals(mDevice)) { + mActivity.finish(); + } + } else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) { + UsbAccessory accessory = + (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + if (accessory != null && accessory.equals(mAccessory)) { + mActivity.finish(); + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java new file mode 100644 index 000000000000..f1784df0e425 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.usb; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.hardware.usb.IUsbManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.TextView; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +import com.android.systemui.R; + +public class UsbPermissionActivity extends AlertActivity + implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener { + + private static final String TAG = "UsbPermissionActivity"; + + private CheckBox mAlwaysCheck; + private TextView mClearDefaultHint; + private UsbDevice mDevice; + private UsbAccessory mAccessory; + private PendingIntent mPendingIntent; + private String mPackageName; + private int mUid; + private boolean mPermissionGranted; + private UsbDisconnectedReceiver mDisconnectedReceiver; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + Intent intent = getIntent(); + mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + mPendingIntent = (PendingIntent)intent.getParcelableExtra(Intent.EXTRA_INTENT); + mUid = intent.getIntExtra("uid", 0); + mPackageName = intent.getStringExtra("package"); + + PackageManager packageManager = getPackageManager(); + ApplicationInfo aInfo; + try { + aInfo = packageManager.getApplicationInfo(mPackageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "unable to look up package name", e); + finish(); + return; + } + String appName = aInfo.loadLabel(packageManager).toString(); + + final AlertController.AlertParams ap = mAlertParams; + ap.mIcon = aInfo.loadIcon(packageManager); + ap.mTitle = appName; + if (mDevice == null) { + ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName); + mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); + } else { + ap.mMessage = getString(R.string.usb_device_permission_prompt, appName); + mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice); + } + ap.mPositiveButtonText = getString(com.android.internal.R.string.ok); + ap.mNegativeButtonText = getString(com.android.internal.R.string.cancel); + ap.mPositiveButtonListener = this; + ap.mNegativeButtonListener = this; + + // add "always use" checkbox + LayoutInflater inflater = (LayoutInflater)getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); + mAlwaysCheck = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse); + mAlwaysCheck.setText(com.android.internal.R.string.alwaysUse); + mAlwaysCheck.setOnCheckedChangeListener(this); + mClearDefaultHint = (TextView)ap.mView.findViewById( + com.android.internal.R.id.clearDefaultHint); + mClearDefaultHint.setVisibility(View.GONE); + + setupAlert(); + + } + + @Override + public void onDestroy() { + IBinder b = ServiceManager.getService(USB_SERVICE); + IUsbManager service = IUsbManager.Stub.asInterface(b); + + // send response via pending intent + Intent intent = new Intent(); + try { + if (mDevice != null) { + intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); + if (mPermissionGranted) { + service.grantDevicePermission(mDevice, mUid); + if (mAlwaysCheck.isChecked()) { + service.setDevicePackage(mDevice, mPackageName); + } + } + } + if (mAccessory != null) { + intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); + if (mPermissionGranted) { + service.grantAccessoryPermission(mAccessory, mUid); + if (mAlwaysCheck.isChecked()) { + service.setAccessoryPackage(mAccessory, mPackageName); + } + } + } + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, mPermissionGranted); + mPendingIntent.send(this, 0, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "PendingIntent was cancelled"); + } catch (RemoteException e) { + Log.e(TAG, "IUsbService connection failed", e); + } + + if (mDisconnectedReceiver != null) { + unregisterReceiver(mDisconnectedReceiver); + } + super.onDestroy(); + } + + public void onClick(DialogInterface dialog, int which) { + if (which == AlertDialog.BUTTON_POSITIVE) { + mPermissionGranted = true; + } + finish(); + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mClearDefaultHint == null) return; + + if(isChecked) { + mClearDefaultHint.setVisibility(View.VISIBLE); + } else { + mClearDefaultHint.setVisibility(View.GONE); + } + } +} diff --git a/services/java/com/android/server/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java index e8a09a5cca71..84d73dd9301d 100644 --- a/services/java/com/android/server/usb/UsbResolverActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.usb; +package com.android.systemui.usb; import com.android.internal.app.ResolverActivity; @@ -39,6 +39,10 @@ public class UsbResolverActivity extends ResolverActivity { public static final String TAG = "UsbResolverActivity"; public static final String EXTRA_RESOLVE_INFOS = "rlist"; + private UsbDevice mDevice; + private UsbAccessory mAccessory; + private UsbDisconnectedReceiver mDisconnectedReceiver; + @Override protected void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); @@ -50,7 +54,6 @@ public class UsbResolverActivity extends ResolverActivity { } Intent target = (Intent)targetParcelable; ArrayList<ResolveInfo> rList = intent.getParcelableArrayListExtra(EXTRA_RESOLVE_INFOS); - Log.d(TAG, "rList.size() " + rList.size()); CharSequence title = getResources().getText(com.android.internal.R.string.chooseUsbActivity); super.onCreate(savedInstanceState, target, title, null, rList, true, /* Set alwaysUseOption to true to enable "always use this app" checkbox. */ @@ -58,6 +61,27 @@ public class UsbResolverActivity extends ResolverActivity { This is necessary because this activity is needed for the user to allow the application permission to access the device */ ); + + mDevice = (UsbDevice)target.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (mDevice != null) { + mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice); + } else { + mAccessory = (UsbAccessory)target.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + if (mAccessory == null) { + Log.e(TAG, "no device or accessory"); + finish(); + return; + } + mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); + } + } + + @Override + protected void onDestroy() { + if (mDisconnectedReceiver != null) { + unregisterReceiver(mDisconnectedReceiver); + } + super.onDestroy(); } protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) { @@ -65,28 +89,24 @@ public class UsbResolverActivity extends ResolverActivity { IBinder b = ServiceManager.getService(USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); int uid = ri.activityInfo.applicationInfo.uid; - String action = intent.getAction(); - if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { - UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (mDevice != null) { // grant permission for the device - service.grantDevicePermission(device, uid); + service.grantDevicePermission(mDevice, uid); // set or clear default setting if (alwaysCheck) { - service.setDevicePackage(device, ri.activityInfo.packageName); + service.setDevicePackage(mDevice, ri.activityInfo.packageName); } else { - service.setDevicePackage(device, null); + service.setDevicePackage(mDevice, null); } - } else if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action)) { - UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra( - UsbManager.EXTRA_ACCESSORY); + } else if (mAccessory != null) { // grant permission for the accessory - service.grantAccessoryPermission(accessory, uid); + service.grantAccessoryPermission(mAccessory, uid); // set or clear default setting if (alwaysCheck) { - service.setAccessoryPackage(accessory, ri.activityInfo.packageName); + service.setAccessoryPackage(mAccessory, ri.activityInfo.packageName); } else { - service.setAccessoryPackage(accessory, null); + service.setAccessoryPackage(mAccessory, null); } } diff --git a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java index 2f22fe1a5d7c..29e6f94d5971 100644 --- a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java +++ b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java @@ -16,11 +16,13 @@ package com.android.server.usb; +import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; @@ -602,50 +604,20 @@ class UsbDeviceSettingsManager { } public void deviceAttached(UsbDevice device) { - Intent deviceIntent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); - deviceIntent.putExtra(UsbManager.EXTRA_DEVICE, device); - deviceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ArrayList<ResolveInfo> matches; String defaultPackage; synchronized (mLock) { - matches = getDeviceMatchesLocked(device, deviceIntent); + matches = getDeviceMatchesLocked(device, intent); // Launch our default activity directly, if we have one. // Otherwise we will start the UsbResolverActivity to allow the user to choose. defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device)); } - int count = matches.size(); - // don't show the resolver activity if there are no choices available - if (count == 0) return; - - if (defaultPackage != null) { - for (int i = 0; i < count; i++) { - ResolveInfo rInfo = matches.get(i); - if (rInfo.activityInfo != null && - defaultPackage.equals(rInfo.activityInfo.packageName)) { - try { - deviceIntent.setComponent(new ComponentName( - defaultPackage, rInfo.activityInfo.name)); - mContext.startActivity(deviceIntent); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "startActivity failed", e); - } - return; - } - } - } - - Intent intent = new Intent(mContext, UsbResolverActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - intent.putExtra(Intent.EXTRA_INTENT, deviceIntent); - intent.putParcelableArrayListExtra(UsbResolverActivity.EXTRA_RESOLVE_INFOS, matches); - try { - mContext.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "unable to start UsbResolverActivity"); - } + resolveActivity(intent, matches, defaultPackage, device, null); } public void deviceDetached(UsbDevice device) { @@ -656,49 +628,86 @@ class UsbDeviceSettingsManager { } public void accessoryAttached(UsbAccessory accessory) { - Intent accessoryIntent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); - accessoryIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); - accessoryIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ArrayList<ResolveInfo> matches; String defaultPackage; synchronized (mLock) { - matches = getAccessoryMatchesLocked(accessory, accessoryIntent); + matches = getAccessoryMatchesLocked(accessory, intent); // Launch our default activity directly, if we have one. // Otherwise we will start the UsbResolverActivity to allow the user to choose. defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)); } + resolveActivity(intent, matches, defaultPackage, null, accessory); + } + + private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches, + String defaultPackage, UsbDevice device, UsbAccessory accessory) { int count = matches.size(); // don't show the resolver activity if there are no choices available if (count == 0) return; - if (defaultPackage != null) { + ResolveInfo defaultRI = null; + if (count == 1 && defaultPackage == null) { + // Check to see if our single choice is on the system partition. + // If so, treat it as our default without calling UsbResolverActivity + ResolveInfo rInfo = matches.get(0); + if (rInfo.activityInfo != null && + rInfo.activityInfo.applicationInfo != null && + (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + defaultRI = rInfo; + int uid = rInfo.activityInfo.applicationInfo.uid; + // grant permission + if (device != null) { + grantDevicePermission(device, uid); + } else if (accessory != null) { + grantAccessoryPermission(accessory, uid); + } + } + } + + if (defaultRI == null && defaultPackage != null) { + // look for default activity for (int i = 0; i < count; i++) { ResolveInfo rInfo = matches.get(i); if (rInfo.activityInfo != null && defaultPackage.equals(rInfo.activityInfo.packageName)) { - try { - accessoryIntent.setComponent(new ComponentName( - defaultPackage, rInfo.activityInfo.name)); - mContext.startActivity(accessoryIntent); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "startActivity failed", e); - } - return; + defaultRI = rInfo; + break; } } } - Intent intent = new Intent(mContext, UsbResolverActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (defaultRI != null) { + // start default activity directly + try { + intent.setComponent( + new ComponentName(defaultRI.activityInfo.packageName, + defaultRI.activityInfo.name)); + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "startActivity failed", e); + } + } else { + long identity = Binder.clearCallingIdentity(); - intent.putExtra(Intent.EXTRA_INTENT, accessoryIntent); - intent.putParcelableArrayListExtra(UsbResolverActivity.EXTRA_RESOLVE_INFOS, matches); - try { - mContext.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "unable to start UsbResolverActivity"); + // start UsbResolverActivity so user can choose an activity + Intent resolverIntent = new Intent(); + resolverIntent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbResolverActivity"); + resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + resolverIntent.putExtra(Intent.EXTRA_INTENT, intent); + resolverIntent.putParcelableArrayListExtra("rlist", matches); + try { + mContext.startActivity(resolverIntent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "unable to start UsbResolverActivity"); + } finally { + Binder.restoreCallingIdentity(identity); + } } } @@ -709,40 +718,121 @@ class UsbDeviceSettingsManager { mContext.sendBroadcast(intent); } - public void checkPermission(UsbDevice device) { - if (device == null) return; + public boolean hasPermission(UsbDevice device) { synchronized (mLock) { - ArrayList<DeviceFilter> filterList = mDevicePermissionMap.get(Binder.getCallingUid()); + ArrayList<DeviceFilter> filterList = + mDevicePermissionMap.get(Binder.getCallingUid()); if (filterList != null) { int count = filterList.size(); for (int i = 0; i < count; i++) { DeviceFilter filter = filterList.get(i); if (filter.equals(device)) { // permission allowed - return; + return true; } } } } - throw new SecurityException("User has not given permission to device " + device); + return false; } - public void checkPermission(UsbAccessory accessory) { - if (accessory == null) return; + public boolean hasPermission(UsbAccessory accessory) { synchronized (mLock) { - ArrayList<AccessoryFilter> filterList = mAccessoryPermissionMap.get(Binder.getCallingUid()); + ArrayList<AccessoryFilter> filterList = + mAccessoryPermissionMap.get(Binder.getCallingUid()); if (filterList != null) { int count = filterList.size(); for (int i = 0; i < count; i++) { AccessoryFilter filter = filterList.get(i); if (filter.equals(accessory)) { // permission allowed - return; + return true; } } } } - throw new SecurityException("User has not given permission to accessory " + accessory); + return false; + } + + public void checkPermission(UsbDevice device) { + if (!hasPermission(device)) { + throw new SecurityException("User has not given permission to device " + device); + } + } + + public void checkPermission(UsbAccessory accessory) { + if (!hasPermission(accessory)) { + throw new SecurityException("User has not given permission to accessory " + accessory); + } + } + + private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) { + int uid = Binder.getCallingUid(); + + // compare uid with packageName to foil apps pretending to be someone else + try { + ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0); + if (aInfo.uid != uid) { + throw new IllegalArgumentException("package " + packageName + + " does not match caller's uid " + uid); + } + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("package " + packageName + " not found"); + } + + long identity = Binder.clearCallingIdentity(); + intent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbPermissionActivity"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Intent.EXTRA_INTENT, pi); + intent.putExtra("package", packageName); + intent.putExtra("uid", uid); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "unable to start UsbPermissionActivity"); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) { + Intent intent = new Intent(); + + // respond immediately if permission has already been granted + if (hasPermission(device)) { + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); + try { + pi.send(mContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + + // start UsbPermissionActivity so user can choose an activity + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + requestPermissionDialog(intent, packageName, pi); + } + + public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { + Intent intent = new Intent(); + + // respond immediately if permission has already been granted + if (hasPermission(accessory)) { + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); + try { + pi.send(mContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + requestPermissionDialog(intent, packageName, pi); } public void setDevicePackage(UsbDevice device, String packageName) { diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index 8170c61b5ec5..d0a24920943b 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -16,6 +16,7 @@ package com.android.server.usb; +import android.app.PendingIntent; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -454,6 +455,24 @@ public class UsbService extends IUsbManager.Stub { mDeviceManager.setAccessoryPackage(accessory, packageName); } + public boolean hasDevicePermission(UsbDevice device) { + return mDeviceManager.hasPermission(device); + } + + public boolean hasAccessoryPermission(UsbAccessory accessory) { + return mDeviceManager.hasPermission(accessory); + } + + public void requestDevicePermission(UsbDevice device, String packageName, + PendingIntent pi) { + mDeviceManager.requestPermission(device, packageName, pi); + } + + public void requestAccessoryPermission(UsbAccessory accessory, String packageName, + PendingIntent pi) { + mDeviceManager.requestPermission(accessory, packageName, pi); + } + public void grantDevicePermission(UsbDevice device, int uid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); mDeviceManager.grantDevicePermission(device, uid); diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index eed41a0c0e9a..8ccfbbab68c7 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -4791,13 +4791,17 @@ public class WindowManagerService extends IWindowManager.Stub if (maxLayer < ws.mAnimLayer) { maxLayer = ws.mAnimLayer; } - final Rect wf = ws.mFrame; - final Rect cr = ws.mContentInsets; - int left = wf.left + cr.left; - int top = wf.top + cr.top; - int right = wf.right - cr.right; - int bottom = wf.bottom - cr.bottom; - frame.union(left, top, right, bottom); + + // Don't include wallpaper in bounds calculation + if (!ws.mIsWallpaper) { + final Rect wf = ws.mFrame; + final Rect cr = ws.mContentInsets; + int left = wf.left + cr.left; + int top = wf.top + cr.top; + int right = wf.right - cr.right; + int bottom = wf.bottom - cr.bottom; + frame.union(left, top, right, bottom); + } } Binder.restoreCallingIdentity(ident); @@ -5460,8 +5464,9 @@ public class WindowManagerService extends IWindowManager.Stub shortSize = (int)(shortSize/dm.density); // These semi-magic numbers define our compatibility modes for - // applications with different screens. Don't change unless you - // make sure to test lots and lots of apps! + // applications with different screens. These are guarantees to + // app developers about the space they can expect for a particular + // configuration. DO NOT CHANGE! if (longSize < 470) { // This is shorter than an HVGA normal density screen (which // is 480 pixels on its long side). @@ -5469,12 +5474,12 @@ public class WindowManagerService extends IWindowManager.Stub | Configuration.SCREENLAYOUT_LONG_NO; } else { // What size is this screen screen? - if (longSize >= 800 && shortSize >= 600) { - // SVGA or larger screens at medium density are the point + if (longSize >= 960 && shortSize >= 720) { + // 1.5xVGA or larger screens at medium density are the point // at which we consider it to be an extra large screen. mScreenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE; - } else if (longSize >= 530 && shortSize >= 400) { - // SVGA or larger screens at high density are the point + } else if (longSize >= 640 && shortSize >= 480) { + // VGA or larger screens at medium density are the point // at which we consider it to be a large screen. mScreenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE; } else { diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 3be3b1b03aa8..bd4e7875630f 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -727,14 +727,14 @@ void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, }; if (wmActions & WM_ACTION_GO_TO_SLEEP) { -#ifdef DEBUG_INPUT_DISPATCHER_POLICY +#if DEBUG_INPUT_DISPATCHER_POLICY LOGD("handleInterceptActions: Going to sleep."); #endif android_server_PowerManagerService_goToSleep(when); } if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) { -#ifdef DEBUG_INPUT_DISPATCHER_POLICY +#if DEBUG_INPUT_DISPATCHER_POLICY LOGD("handleInterceptActions: Poking user activity."); #endif android_server_PowerManagerService_userActivity(when, POWER_MANAGER_BUTTON_EVENT); @@ -743,7 +743,7 @@ void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, if (wmActions & WM_ACTION_PASS_TO_USER) { policyFlags |= POLICY_FLAG_PASS_TO_USER; } else { -#ifdef DEBUG_INPUT_DISPATCHER_POLICY +#if DEBUG_INPUT_DISPATCHER_POLICY LOGD("handleInterceptActions: Not passing key to user."); #endif } diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java index 6c9f48fb003c..db14e53b3aff 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -28,10 +28,12 @@ import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; /** * Delegate implementing the native methods of android.graphics.Path @@ -331,58 +333,91 @@ public final class Path_Delegate { @LayoutlibDelegate /*package*/ static void native_addOval(int nPath, RectF oval, int dir) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Path.addOval is not supported.", null, null /*data*/); + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mPath.append(new Ellipse2D.Float( + oval.left, oval.top, oval.width(), oval.height()), false); } @LayoutlibDelegate /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Path.addCircle is not supported.", null, null /*data*/); + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + // because x/y is the center of the circle, need to offset this by the radius + pathDelegate.mPath.append(new Ellipse2D.Float( + x - radius, y - radius, radius * 2, radius * 2), false); } @LayoutlibDelegate /*package*/ static void native_addArc(int nPath, RectF oval, float startAngle, float sweepAngle) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Path.addArc is not supported.", null, null /*data*/); + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + // because x/y is the center of the circle, need to offset this by the radius + pathDelegate.mPath.append(new Arc2D.Float( + oval.left, oval.top, oval.width(), oval.height(), + startAngle, sweepAngle, Arc2D.OPEN), false); } @LayoutlibDelegate - /*package*/ static void native_addRoundRect(int nPath, RectF rect, - float rx, float ry, int dir) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Path.addRoundRect is not supported.", null, null /*data*/); + /*package*/ static void native_addRoundRect( + int nPath, RectF rect, float rx, float ry, int dir) { + + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mPath.append(new RoundRectangle2D.Float( + rect.left, rect.top, rect.width(), rect.height(), rx * 2, ry * 2), false); } @LayoutlibDelegate - /*package*/ static void native_addRoundRect(int nPath, RectF r, float[] radii, int dir) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Path.addRoundRect is not supported.", null, null /*data*/); + /*package*/ static void native_addRoundRect(int nPath, RectF rect, float[] radii, int dir) { + // Java2D doesn't support different rounded corners in each corner, so just use the + // first value. + native_addRoundRect(nPath, rect, radii[0], radii[1], dir); + + // there can be a case where this API is used but with similar values for all corners, so + // in that case we don't warn. + // we only care if 2 corners are different so just compare to the next one. + for (int i = 0 ; i < 3 ; i++) { + if (radii[i * 2] != radii[(i + 1) * 2] || radii[i * 2 + 1] != radii[(i + 1) * 2 + 1]) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Different corner sizes are not supported in Path.addRoundRect.", + null, null /*data*/); + break; + } + } } @LayoutlibDelegate /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Path.addPath is not supported.", null, null /*data*/); + addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy)); } @LayoutlibDelegate /*package*/ static void native_addPath(int nPath, int src) { - native_addPath(nPath, src, 0, 0); + addPath(nPath, src, null /*transform*/); } @LayoutlibDelegate /*package*/ static void native_addPath(int nPath, int src, int matrix) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Path.addPath is not supported.", null, null /*data*/); + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); + if (matrixDelegate == null) { + return; + } + + addPath(nPath, src, matrixDelegate.getAffineTransform()); } @LayoutlibDelegate @@ -487,6 +522,26 @@ public final class Path_Delegate { return null; } + private static void addPath(int destPath, int srcPath, AffineTransform transform) { + Path_Delegate destPathDelegate = sManager.getDelegate(destPath); + if (destPathDelegate == null) { + return; + } + + Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath); + if (srcPathDelegate == null) { + return; + } + + if (transform != null) { + destPathDelegate.mPath.append( + srcPathDelegate.mPath.getPathIterator(transform), false); + } else { + destPathDelegate.mPath.append(srcPathDelegate.mPath, false); + } + } + + /** * Returns whether the path is empty. * @return true if the path is empty. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index acc7379990d4..e6e96476c18a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -192,7 +192,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { Capability.UNBOUND_RENDERING, Capability.CUSTOM_BACKGROUND_COLOR, Capability.RENDER, - Capability.LAYOUT_ONLY, + //Capability.LAYOUT_ONLY, // disable to run on ADT 10.0 which doesn't include this. Capability.EMBEDDED_LAYOUT, Capability.VIEW_MANIPULATION, Capability.PLAY_ANIMATION, diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java index c1d760069188..138a455004be 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java @@ -412,9 +412,7 @@ public final class BridgeTypedArray extends TypedArray { return LayoutParams.MATCH_PARENT; } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { return LayoutParams.WRAP_CONTENT; - } - - if (RenderResources.REFERENCE_NULL.equals(s)) { + } else if (RenderResources.REFERENCE_NULL.equals(s)) { return defValue; } @@ -486,23 +484,32 @@ public final class BridgeTypedArray extends TypedArray { return LayoutParams.MATCH_PARENT; } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { return LayoutParams.WRAP_CONTENT; - } - - if (RenderResources.REFERENCE_NULL.equals(s)) { + } else if (RenderResources.REFERENCE_NULL.equals(s)) { return defValue; } - // FIXME huh? + if (ResourceHelper.stringToFloat(s, mValue)) { + float f = mValue.getDimension(mBridgeResources.mMetrics); - float f = getDimension(index, defValue); - final int res = (int)(f+0.5f); - if (res != 0) return res; - if (f == 0) return 0; - if (f > 0) return 1; + if (f < 0) { + // negative values are not allowed in pixel dimensions + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Negative pixel dimension: " + s, + null, null /*data*/); + return defValue; + } - Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, - "Can't convert to dimension: " + Integer.toString(index), - null, null /*data*/); + if (f == 0) return 0; + if (f < 1) return 1; + + return (int)(f+0.5f); + } + + // looks like we were unable to resolve the dimension value + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%1$s\" in attribute \"%2$s\" is not a valid format.", + s, mNames[index]), null /*data*/); return defValue; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index 69f46e6e335e..649160eb4501 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -377,7 +377,7 @@ public final class ResourceHelper { } // check the first character - if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.') { + if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.' && buf[0] != '-') { return false; } |