From d6982c9bb46511a5d86458b2dddde0a40e2f4f75 Mon Sep 17 00:00:00 2001 From: Cary Clark Date: Fri, 29 May 2009 11:02:22 -0400 Subject: in the browser, make the trackball more like a mouse Older code treated the trackball as a four way dpad with equivalents to moving up, down, left and right by generating arrow key events. This change makes the trackball solely generate mousemove events. The old arrow keys in turn were mapped to be as close as possible to tab-key events that moved the focus. The new model leaves focus-changes to the DOM. Clicking the dpad is distinguished from pressing the enter key to be more compatible with desktop-authored web pages. --- core/java/android/webkit/TextDialog.java | 49 +- core/java/android/webkit/WebView.java | 775 +++++++++++++----------------- core/java/android/webkit/WebViewCore.java | 190 ++++---- preloaded-classes | 3 +- 4 files changed, 462 insertions(+), 555 deletions(-) diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java index 6fe64ee257ee..85f3cff2166b 100644 --- a/core/java/android/webkit/TextDialog.java +++ b/core/java/android/webkit/TextDialog.java @@ -121,6 +121,10 @@ import java.util.ArrayList; if (isPopupShowing()) { return super.dispatchKeyEvent(event); } + if (!mWebView.nativeCursorMatchesFocus()) { + return down ? mWebView.onKeyDown(keyCode, event) : mWebView + .onKeyUp(keyCode, event); + } // Center key should be passed to a potential onClick if (!down) { mWebView.shortPressOnTextField(); @@ -128,6 +132,20 @@ import java.util.ArrayList; // Pass to super to handle longpress. return super.dispatchKeyEvent(event); } + boolean isArrowKey = false; + switch(keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + if (!mWebView.nativeCursorMatchesFocus()) { + return down ? mWebView.onKeyDown(keyCode, event) : mWebView + .onKeyUp(keyCode, event); + + } + isArrowKey = true; + break; + } // Ensure there is a layout so arrow keys are handled properly. if (getLayout() == null) { @@ -157,22 +175,11 @@ import java.util.ArrayList; // so do not pass down to javascript, and instead // return true. If it is an arrow key or a delete key, we can go // ahead and pass it down. - boolean isArrowKey; - switch(keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - isArrowKey = true; - break; - case KeyEvent.KEYCODE_ENTER: - // For multi-line text boxes, newlines will - // trigger onTextChanged for key down (which will send both - // key up and key down) but not key up. - mGotEnterDown = true; - default: - isArrowKey = false; - break; + if (KeyEvent.KEYCODE_ENTER == keyCode) { + // For multi-line text boxes, newlines will + // trigger onTextChanged for key down (which will send both + // key up and key down) but not key up. + mGotEnterDown = true; } if (maxedOut && !isArrowKey && keyCode != KeyEvent.KEYCODE_DEL) { if (oldEnd == oldStart) { @@ -216,10 +223,7 @@ import java.util.ArrayList; return true; } // if it is a navigation key, pass it to WebView - if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT - || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT - || keyCode == KeyEvent.KEYCODE_DPAD_UP - || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + if (isArrowKey) { // WebView check the trackballtime in onKeyDown to avoid calling // native from both trackball and key handling. As this is called // from TextDialog, we always want WebView to check with native. @@ -333,6 +337,11 @@ import java.util.ArrayList; if (event.getAction() != MotionEvent.ACTION_MOVE) { return false; } + // If the Cursor is not on the text input, webview should handle the + // trackball + if (!mWebView.nativeCursorMatchesFocus()) { + return mWebView.onTrackballEvent(event); + } Spannable text = (Spannable) getText(); MovementMethod move = getMovementMethod(); if (move != null && getLayout() != null && diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index eef128be6a4e..69254c040c68 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -84,7 +84,7 @@ import java.util.HashMap; import java.util.List; /** - *

A View that displays web pages. This class is the basis upon which you + *

A View that displays web pages. This class is the basis upon which you * can roll your own web browser or simply display some online content within your Activity. * It uses the WebKit rendering engine to display * web pages and includes methods to navigate forward and backward @@ -93,7 +93,7 @@ import java.util.List; * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} * (introduced in API version 3). *

Note that, in order for your Activity to access the Internet and load web pages - * in a WebView, you must add the INTERNET permissions to your + * in a WebView, you must add the INTERNET permissions to your * Android Manifest file:

*
<uses-permission android:name="android.permission.INTERNET" />
* @@ -195,7 +195,7 @@ import java.util.List; * changes, and then just leave the WebView alone. It'll automatically * re-orient itself as appropriate.

*/ -public class WebView extends AbsoluteLayout +public class WebView extends AbsoluteLayout implements ViewTreeObserver.OnGlobalFocusChangeListener, ViewGroup.OnHierarchyChangeListener { @@ -219,40 +219,40 @@ public class WebView extends AbsoluteLayout mZoomControls = (ZoomControls) findViewById(com.android.internal.R.id.zoomControls); mZoomMagnify = (ImageView) findViewById(com.android.internal.R.id.zoomMagnify); } - + public void show(boolean showZoom, boolean canZoomOut) { mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE); mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE); fade(View.VISIBLE, 0.0f, 1.0f); } - + public void hide() { fade(View.GONE, 1.0f, 0.0f); } - + private void fade(int visibility, float startAlpha, float endAlpha) { AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha); anim.setDuration(500); startAnimation(anim); setVisibility(visibility); } - + public void setIsZoomMagnifyEnabled(boolean isEnabled) { mZoomMagnify.setEnabled(isEnabled); } - + public boolean hasFocus() { return mZoomControls.hasFocus() || mZoomMagnify.hasFocus(); } - + public void setOnZoomInClickListener(OnClickListener listener) { mZoomControls.setOnZoomInClickListener(listener); } - + public void setOnZoomOutClickListener(OnClickListener listener) { mZoomControls.setOnZoomOutClickListener(listener); } - + public void setOnZoomMagnifyClickListener(OnClickListener listener) { mZoomMagnify.setOnClickListener(listener); } @@ -260,7 +260,7 @@ public class WebView extends AbsoluteLayout ZoomControls mZoomControls; ImageView mZoomMagnify; } - + /** * Transportation object for returning WebView across thread boundaries. */ @@ -363,12 +363,12 @@ public class WebView extends AbsoluteLayout // take control of touch events unless it says no for touch down event. private boolean mPreventDrag; - // If updateTextEntry gets called while we are out of focus, use this + // If updateTextEntry gets called while we are out of focus, use this // variable to remember to do it next time we gain focus. private boolean mNeedsUpdateTextEntry = false; - - // Whether or not to draw the focus ring. - private boolean mDrawFocusRing = true; + + // Whether or not to draw the cursor ring. + private boolean mDrawCursorRing = true; // true if onPause has been called (and not onResume) private boolean mIsPaused; @@ -391,7 +391,7 @@ public class WebView extends AbsoluteLayout // needed to avoid flinging after a pause of no movement private static final int MIN_FLING_TIME = 250; // The time that the Zoom Controls are visible before fading away - private static final long ZOOM_CONTROLS_TIMEOUT = + private static final long ZOOM_CONTROLS_TIMEOUT = ViewConfiguration.getZoomControlsTimeout(); // The amount of content to overlap between two screens when going through // pages with the space bar, in pixels. @@ -412,7 +412,7 @@ public class WebView extends AbsoluteLayout private int mContentWidth; // cache of value from WebViewCore private int mContentHeight; // cache of value from WebViewCore - // Need to have the separate control for horizontal and vertical scrollbar + // Need to have the separate control for horizontal and vertical scrollbar // style than the View's single scrollbar style private boolean mOverlayHorizontalScrollbar = true; private boolean mOverlayVerticalScrollbar = false; @@ -427,9 +427,6 @@ public class WebView extends AbsoluteLayout private boolean mWrapContent; - // true if we should call webcore to draw the content, false means we have - // requested something but it isn't ready to draw yet. - private WebViewCore.FocusData mFocusData; /** * Private message ids */ @@ -438,7 +435,7 @@ public class WebView extends AbsoluteLayout private static final int SWITCH_TO_SHORTPRESS = 3; private static final int SWITCH_TO_LONGPRESS = 4; private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6; - private static final int SWITCH_TO_ENTER = 7; + private static final int SWITCH_TO_CLICK = 7; private static final int RESUME_WEBCORE_UPDATE = 8; //! arg1=x, arg2=y @@ -454,7 +451,7 @@ public class WebView extends AbsoluteLayout static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17; static final int DID_FIRST_LAYOUT_MSG_ID = 18; static final int RECOMPUTE_FOCUS_MSG_ID = 19; - static final int NOTIFY_FOCUS_SET_MSG_ID = 20; + static final int MARK_NODE_INVALID_ID = 21; static final int UPDATE_CLIPBOARD = 22; static final int LONG_PRESS_ENTER = 23; @@ -462,7 +459,7 @@ public class WebView extends AbsoluteLayout static final int WEBCORE_NEED_TOUCH_EVENTS = 25; // obj=Rect in doc coordinates static final int INVAL_RECT_MSG_ID = 26; - + static final String[] HandlerDebugString = { "REMEMBER_PASSWORD", // = 1; "NEVER_REMEMBER_PASSWORD", // = 2; @@ -470,7 +467,7 @@ public class WebView extends AbsoluteLayout "SWITCH_TO_LONGPRESS", // = 4; "5", "UPDATE_TEXT_ENTRY_ADAPTER", // = 6; - "SWITCH_TO_ENTER", // = 7; + "SWITCH_TO_CLICK", // = 7; "RESUME_WEBCORE_UPDATE", // = 8; "9", "SCROLL_TO_MSG_ID", // = 10; @@ -483,7 +480,7 @@ public class WebView extends AbsoluteLayout "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17; "DID_FIRST_LAYOUT_MSG_ID", // = 18; "RECOMPUTE_FOCUS_MSG_ID", // = 19; - "NOTIFY_FOCUS_SET_MSG_ID", // = 20; + "20", "MARK_NODE_INVALID_ID", // = 21; "UPDATE_CLIPBOARD", // = 22; "LONG_PRESS_ENTER", // = 23; @@ -527,7 +524,7 @@ public class WebView extends AbsoluteLayout private static final int SNAP_X_LOCK = 4; private static final int SNAP_Y_LOCK = 5; private boolean mSnapPositive; - + // Used to match key downs and key ups private boolean mGotKeyDown; @@ -550,7 +547,7 @@ public class WebView extends AbsoluteLayout * URI scheme for map address */ public static final String SCHEME_GEO = "geo:0,0?q="; - + private int mBackgroundColor = Color.WHITE; // Used to notify listeners of a new picture. @@ -638,7 +635,7 @@ public class WebView extends AbsoluteLayout private ExtendedZoomControls mZoomControls; private Runnable mZoomControlRunnable; - private ZoomButtonsController mZoomButtonsController; + private ZoomButtonsController mZoomButtonsController; private ImageView mZoomOverviewButton; private ImageView mZoomFitPageButton; @@ -663,11 +660,11 @@ public class WebView extends AbsoluteLayout } else { zoomOut(); } - + updateZoomButtonsEnabled(); } }; - + /** * Construct a new WebView with a Context object. * @param context A Context object used to access application assets. @@ -698,11 +695,6 @@ public class WebView extends AbsoluteLayout mCallbackProxy = new CallbackProxy(context, this); mWebViewCore = new WebViewCore(context, this, mCallbackProxy); mDatabase = WebViewDatabase.getInstance(context); - mFocusData = new WebViewCore.FocusData(); - mFocusData.mFrame = 0; - mFocusData.mNode = 0; - mFocusData.mX = 0; - mFocusData.mY = 0; mScroller = new Scroller(context); initZoomController(context); @@ -969,7 +961,7 @@ public class WebView extends AbsoluteLayout clearTextEntry(); if (mWebViewCore != null) { // Set the handlers to null before destroying WebViewCore so no - // more messages will be posted. + // more messages will be posted. mCallbackProxy.setWebViewClient(null); mCallbackProxy.setWebChromeClient(null); // Tell WebViewCore to destroy itself @@ -1005,7 +997,7 @@ public class WebView extends AbsoluteLayout public static void disablePlatformNotifications() { Network.disablePlatformNotifications(); } - + /** * Inform WebView of the network state. This is used to set * the javascript property window.navigator.isOnline and @@ -1018,7 +1010,7 @@ public class WebView extends AbsoluteLayout } /** - * Save the state of this WebView used in + * Save the state of this WebView used in * {@link android.app.Activity#onSaveInstanceState}. Please note that this * method no longer stores the display data for this WebView. The previous * behavior could potentially leak files if {@link #restoreState} was never @@ -1149,10 +1141,10 @@ public class WebView extends AbsoluteLayout /** * Restore the state of this WebView from the given map used in - * {@link android.app.Activity#onRestoreInstanceState}. This method should - * be called to restore the state of the WebView before using the object. If - * it is called after the WebView has had a chance to build state (load - * pages, create a back/forward list, etc.) there may be undesirable + * {@link android.app.Activity#onRestoreInstanceState}. This method should + * be called to restore the state of the WebView before using the object. If + * it is called after the WebView has had a chance to build state (load + * pages, create a back/forward list, etc.) there may be undesirable * side-effects. Please note that this method no longer restores the * display data for this WebView. See {@link #savePicture} and {@link * #restorePicture} for saving and restoring the display data. @@ -1222,10 +1214,10 @@ public class WebView extends AbsoluteLayout * Load the url with postData using "POST" method into the WebView. If url * is not a network url, it will be loaded with {link * {@link #loadUrl(String)} instead. - * + * * @param url The url of the resource to load. * @param postData The data will be passed to "POST" request. - * + * * @hide pending API solidification */ public void postUrl(String url, byte[] postData) { @@ -1267,7 +1259,7 @@ public class WebView extends AbsoluteLayout * able to access asset files. If the baseUrl is anything other than * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for * sub resources. - * + * * @param baseUrl Url to resolve relative paths with, if null defaults to * "about:blank" * @param data A String of data in the given encoding. @@ -1278,7 +1270,7 @@ public class WebView extends AbsoluteLayout */ public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String failUrl) { - + if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { loadData(data, mimeType, encoding); return; @@ -1399,7 +1391,7 @@ public class WebView extends AbsoluteLayout ignoreSnapshot ? 1 : 0); } } - + private boolean extendScroll(int y) { int finalY = mScroller.getFinalY(); int newY = pinLocY(finalY + y); @@ -1408,7 +1400,7 @@ public class WebView extends AbsoluteLayout mScroller.extendDuration(computeDuration(0, y)); return true; } - + /** * Scroll the contents of the view up by half the view size * @param top true to jump to the top of the page @@ -1432,10 +1424,10 @@ public class WebView extends AbsoluteLayout y = -h / 2; } mUserScroll = true; - return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) + return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) : extendScroll(y); } - + /** * Scroll the contents of the view down by half the page size * @param bottom true to jump to bottom of page @@ -1458,7 +1450,7 @@ public class WebView extends AbsoluteLayout y = h / 2; } mUserScroll = true; - return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) + return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) : extendScroll(y); } @@ -1471,7 +1463,7 @@ public class WebView extends AbsoluteLayout mContentHeight = 0; mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); } - + /** * Return a new picture that captures the current display of the webview. * This is a copy of the display, and will be unaffected if the webview @@ -1482,7 +1474,7 @@ public class WebView extends AbsoluteLayout * bounds of the view. */ public Picture capturePicture() { - if (null == mWebViewCore) return null; // check for out of memory tab + if (null == mWebViewCore) return null; // check for out of memory tab return mWebViewCore.copyContentPicture(); } @@ -1500,7 +1492,7 @@ public class WebView extends AbsoluteLayout } } - /** + /** * Return the current scale of the WebView * @return The current scale. */ @@ -1564,26 +1556,26 @@ public class WebView extends AbsoluteLayout } HitTestResult result = new HitTestResult(); - - if (nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - if (node.mIsTextField || node.mIsTextArea) { + if (nativeHasCursorNode()) { + if (nativeCursorIsTextInput()) { result.setType(HitTestResult.EDIT_TEXT_TYPE); - } else if (node.mText != null) { - String text = node.mText; - if (text.startsWith(SCHEME_TEL)) { - result.setType(HitTestResult.PHONE_TYPE); - result.setExtra(text.substring(SCHEME_TEL.length())); - } else if (text.startsWith(SCHEME_MAILTO)) { - result.setType(HitTestResult.EMAIL_TYPE); - result.setExtra(text.substring(SCHEME_MAILTO.length())); - } else if (text.startsWith(SCHEME_GEO)) { - result.setType(HitTestResult.GEO_TYPE); - result.setExtra(URLDecoder.decode(text - .substring(SCHEME_GEO.length()))); - } else if (node.mIsAnchor) { - result.setType(HitTestResult.SRC_ANCHOR_TYPE); - result.setExtra(text); + } else { + String text = nativeCursorText(); + if (text != null) { + if (text.startsWith(SCHEME_TEL)) { + result.setType(HitTestResult.PHONE_TYPE); + result.setExtra(text.substring(SCHEME_TEL.length())); + } else if (text.startsWith(SCHEME_MAILTO)) { + result.setType(HitTestResult.EMAIL_TYPE); + result.setExtra(text.substring(SCHEME_MAILTO.length())); + } else if (text.startsWith(SCHEME_GEO)) { + result.setType(HitTestResult.GEO_TYPE); + result.setExtra(URLDecoder.decode(text + .substring(SCHEME_GEO.length()))); + } else if (nativeCursorIsAnchor()) { + result.setType(HitTestResult.SRC_ANCHOR_TYPE); + result.setExtra(text); + } } } } @@ -1595,8 +1587,8 @@ public class WebView extends AbsoluteLayout int contentY = viewToContent((int) mLastTouchY + mScrollY); String text = nativeImageURI(contentX, contentY); if (text != null) { - result.setType(type == HitTestResult.UNKNOWN_TYPE ? - HitTestResult.IMAGE_TYPE : + result.setType(type == HitTestResult.UNKNOWN_TYPE ? + HitTestResult.IMAGE_TYPE : HitTestResult.SRC_IMAGE_ANCHOR_TYPE); result.setExtra(text); } @@ -1608,7 +1600,7 @@ public class WebView extends AbsoluteLayout * Request the href of an anchor element due to getFocusNodePath returning * "href." If hrefMsg is null, this method returns immediately and does not * dispatch hrefMsg to its target. - * + * * @param hrefMsg This message will be dispatched with the result of the * request as the data member with "url" as key. The result can * be null. @@ -1617,22 +1609,20 @@ public class WebView extends AbsoluteLayout if (hrefMsg == null || mNativeClass == 0) { return; } - if (nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - if (node.mIsAnchor) { - // NOTE: We may already have the url of the anchor stored in - // node.mText but it may be out of date or the caller may want - // to know about javascript urls. - mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF, - node.mFramePointer, node.mNodePointer, hrefMsg); - } + if (nativeCursorIsAnchor()) { + // NOTE: We may already have the url of the anchor stored in + // node.mText but it may be out of date or the caller may want + // to know about javascript urls. + mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF, + nativeCursorFramePointer(), nativeCursorNodePointer(), + hrefMsg); } } - + /** * Request the url of the image last touched by the user. msg will be sent * to its target with a String representing the url as its object. - * + * * @param msg This message will be dispatched with the result of the request * as the data member with "url" as key. The result can be null. */ @@ -1707,7 +1697,7 @@ public class WebView extends AbsoluteLayout if ((w | h) == 0) { return; } - + // don't abort a scroll animation if we didn't change anything if (mContentWidth != w || mContentHeight != h) { // record new dimensions @@ -1767,7 +1757,7 @@ public class WebView extends AbsoluteLayout mActualScale = scale; mInvActualScale = 1 / scale; - // as we don't have animation for scaling, don't do animation + // as we don't have animation for scaling, don't do animation // for scrolling, as it causes weird intermediate state // pinScrollTo(Math.round(sx), Math.round(sy)); mScrollX = pinLocX(Math.round(sx)); @@ -1891,10 +1881,10 @@ public class WebView extends AbsoluteLayout WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); return h != null ? h.getUrl() : null; } - + /** - * Get the original url for the current page. This is not always the same - * as the url passed to WebViewClient.onPageStarted because although the + * Get the original url for the current page. This is not always the same + * as the url passed to WebViewClient.onPageStarted because although the * load for that url has begun, the current page may not have changed. * Also, there may have been redirects resulting in a different url to that * originally requested. @@ -1932,7 +1922,7 @@ public class WebView extends AbsoluteLayout public int getProgress() { return mCallbackProxy.getProgress(); } - + /** * @return the height of the HTML content. */ @@ -2048,7 +2038,7 @@ public class WebView extends AbsoluteLayout /* * Highlight and scroll to the next occurance of String in findAll. - * Wraps the page infinitely, and scrolls. Must be called after + * Wraps the page infinitely, and scrolls. Must be called after * calling findAll. * * @param forward Direction to search. @@ -2074,11 +2064,8 @@ public class WebView extends AbsoluteLayout // or not we draw the highlights for matches. private boolean mFindIsUp; - private native int nativeFindAll(String findLower, String findUpper); - private native void nativeFindNext(boolean forward); - /** - * Return the first substring consisting of the address of a physical + * Return the first substring consisting of the address of a physical * location. Currently, only addresses in the United States are detected, * and consist of: * - a house number @@ -2091,7 +2078,7 @@ public class WebView extends AbsoluteLayout * All names must be correctly capitalized, and the zip code, if present, * must be valid for the state. The street type must be a standard USPS * spelling or abbreviation. The state or territory must also be spelled - * or abbreviated using USPS standards. The house number may not exceed + * or abbreviated using USPS standards. The house number may not exceed * five digits. * @param addr The string to search for addresses. * @@ -2396,7 +2383,7 @@ public class WebView extends AbsoluteLayout protected void finalize() throws Throwable { destroy(); } - + @Override protected void onDraw(Canvas canvas) { // if mNativeClass is 0, the WebView has been destroyed. Do nothing. @@ -2405,7 +2392,7 @@ public class WebView extends AbsoluteLayout } if (mWebViewCore.mEndScaleZoom) { mWebViewCore.mEndScaleZoom = false; - if (mTouchMode >= FIRST_SCROLL_ZOOM + if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) { setHorizontalScrollBarEnabled(true); setVerticalScrollBarEnabled(true); @@ -2428,10 +2415,10 @@ public class WebView extends AbsoluteLayout nativeRecordButtons(hasFocus() && hasWindowFocus(), mTouchMode == TOUCH_SHORTPRESS_START_MODE || mTrackballDown || mGotEnterDown, false); - drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing); + drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing); } canvas.restoreToCount(sc); - + if (AUTO_REDRAW_HACK && mAutoRedraw) { invalidate(); } @@ -2454,7 +2441,7 @@ public class WebView extends AbsoluteLayout } } - private void drawCoreAndFocusRing(Canvas canvas, int color, + private void drawCoreAndCursorRing(Canvas canvas, int color, boolean drawFocus) { if (mDrawHistory) { canvas.scale(mActualScale, mActualScale); @@ -2463,14 +2450,14 @@ public class WebView extends AbsoluteLayout } boolean animateZoom = mZoomScale != 0; - boolean animateScroll = !mScroller.isFinished() + boolean animateScroll = !mScroller.isFinished() || mVelocityTracker != null; if (animateZoom) { float zoomScale; int interval = (int) (SystemClock.uptimeMillis() - mZoomStart); if (interval < ZOOM_ANIMATION_LENGTH) { float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; - zoomScale = 1.0f / (mInvInitialZoomScale + zoomScale = 1.0f / (mInvInitialZoomScale + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); invalidate(); } else { @@ -2510,7 +2497,7 @@ public class WebView extends AbsoluteLayout if (mTouchSelection) { nativeDrawSelectionRegion(canvas); } else { - nativeDrawSelection(canvas, mSelectX, mSelectY, + nativeDrawSelection(canvas, mSelectX, mSelectY, mExtendSelection); } } else if (drawFocus) { @@ -2524,7 +2511,7 @@ public class WebView extends AbsoluteLayout LONG_PRESS_TIMEOUT); } } - nativeDrawFocusRing(canvas); + nativeDrawCursorRing(canvas); } // When the FindDialog is up, only draw the matches if we are not in // the process of scrolling them into view. @@ -2533,14 +2520,12 @@ public class WebView extends AbsoluteLayout } } - private native void nativeDrawMatches(Canvas canvas); - private float scrollZoomGridScale(float invScale) { - float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID) + float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID) / (float) SCROLL_ZOOM_GRID; return 1.0f / griddedInvScale; } - + private float scrollZoomX(float scale) { int width = getViewWidth(); float maxScrollZoomX = mContentWidth * scale - width; @@ -2556,7 +2541,7 @@ public class WebView extends AbsoluteLayout return -(maxScrollZoomY > 0 ? mZoomScrollY * maxScrollZoomY / maxY : maxScrollZoomY / 2); } - + private void drawMagnifyFrame(Canvas canvas, Rect frame, Paint paint) { final float ADORNMENT_LEN = 16.0f; float width = frame.width(); @@ -2577,13 +2562,13 @@ public class WebView extends AbsoluteLayout path.offset(frame.left, frame.top); canvas.drawPath(path, paint); } - - // Returns frame surrounding magified portion of screen while + + // Returns frame surrounding magified portion of screen while // scroll-zoom is enabled. The frame is also used to center the // zoom-in zoom-out points at the start and end of the animation. private Rect scrollZoomFrame(int width, int height, float halfScale) { Rect scrollFrame = new Rect(); - scrollFrame.set(mZoomScrollX, mZoomScrollY, + scrollFrame.set(mZoomScrollX, mZoomScrollY, mZoomScrollX + width, mZoomScrollY + height); if (mContentWidth * mZoomScrollLimit < width) { float scale = zoomFrameScaleX(width, halfScale, 1.0f); @@ -2599,37 +2584,37 @@ public class WebView extends AbsoluteLayout } return scrollFrame; } - + private float zoomFrameScaleX(int width, float halfScale, float noScale) { // mContentWidth > width > mContentWidth * mZoomScrollLimit if (mContentWidth <= width) { return halfScale; } - float part = (width - mContentWidth * mZoomScrollLimit) + float part = (width - mContentWidth * mZoomScrollLimit) / (width * (1 - mZoomScrollLimit)); return halfScale * part + noScale * (1.0f - part); } - + private float zoomFrameScaleY(int height, float halfScale, float noScale) { if (mContentHeight <= height) { return halfScale; } - float part = (height - mContentHeight * mZoomScrollLimit) + float part = (height - mContentHeight * mZoomScrollLimit) / (height * (1 - mZoomScrollLimit)); return halfScale * part + noScale * (1.0f - part); } - + private float scrollZoomMagScale(float invScale) { return (invScale * 2 + mInvActualScale) / 3; } - + private void scrollZoomDraw(Canvas canvas) { - float invScale = mZoomScrollInvLimit; + float invScale = mZoomScrollInvLimit; int elapsed = 0; if (mTouchMode != SCROLL_ZOOM_OUT) { - elapsed = (int) Math.min(System.currentTimeMillis() + elapsed = (int) Math.min(System.currentTimeMillis() - mZoomScrollStart, SCROLL_ZOOM_DURATION); - float transitionScale = (mZoomScrollInvLimit - mInvActualScale) + float transitionScale = (mZoomScrollInvLimit - mInvActualScale) * elapsed / SCROLL_ZOOM_DURATION; if (mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { invScale = mInvActualScale + transitionScale; @@ -2648,8 +2633,8 @@ public class WebView extends AbsoluteLayout setHorizontalScrollBarEnabled(true); setVerticalScrollBarEnabled(true); updateTextEntry(); - scrollTo((int) (scrollFrame.centerX() * mActualScale) - - (width >> 1), (int) (scrollFrame.centerY() + scrollTo((int) (scrollFrame.centerX() * mActualScale) + - (width >> 1), (int) (scrollFrame.centerY() * mActualScale) - (height >> 1)); mTouchMode = TOUCH_DONE_MODE; } else { @@ -2661,7 +2646,7 @@ public class WebView extends AbsoluteLayout if (LOGV_ENABLED) { Log.v(LOGTAG, "scrollZoomDraw scale=" + scale + " + (" + newX + ", " + newY + ") mZoomScroll=(" + mZoomScrollX + ", " - + mZoomScrollY + ")" + " invScale=" + invScale + " scale=" + + mZoomScrollY + ")" + " invScale=" + invScale + " scale=" + scale); } canvas.translate(newX, newY); @@ -2706,7 +2691,7 @@ public class WebView extends AbsoluteLayout canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX , mZoomScrollY + height * halfY); if (LOGV_ENABLED) { - Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=(" + Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=(" + width + ", " + height + ") half=(" + halfX + ", " + halfY + ")"); } @@ -2766,7 +2751,7 @@ public class WebView extends AbsoluteLayout return mContentWidth >= width * limit || mContentHeight >= height * limit; } - + private void startZoomScrollOut() { setHorizontalScrollBarEnabled(false); setVerticalScrollBarEnabled(false); @@ -2792,18 +2777,18 @@ public class WebView extends AbsoluteLayout mZoomScrollStart = System.currentTimeMillis(); Rect zoomFrame = scrollZoomFrame(width, height , scrollZoomMagScale(mZoomScrollInvLimit)); - mZoomScrollX = Math.max(0, (int) ((mScrollX + halfW) * mInvActualScale) + mZoomScrollX = Math.max(0, (int) ((mScrollX + halfW) * mInvActualScale) - (zoomFrame.width() >> 1)); - mZoomScrollY = Math.max(0, (int) ((mScrollY + halfH) * mInvActualScale) + mZoomScrollY = Math.max(0, (int) ((mScrollY + halfH) * mInvActualScale) - (zoomFrame.height() >> 1)); scrollTo(0, 0); // triggers inval, starts animation clearTextEntry(); if (LOGV_ENABLED) { - Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=(" + Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=(" + mZoomScrollX + ", " + mZoomScrollY +")"); } } - + private void zoomScrollOut() { if (canZoomScrollOut() == false) { mTouchMode = TOUCH_DONE_MODE; @@ -2815,7 +2800,7 @@ public class WebView extends AbsoluteLayout } private void moveZoomScrollWindow(float x, float y) { - if (Math.abs(x - mLastZoomScrollRawX) < 1.5f + if (Math.abs(x - mLastZoomScrollRawX) < 1.5f && Math.abs(y - mLastZoomScrollRawY) < 1.5f) { return; } @@ -2827,12 +2812,12 @@ public class WebView extends AbsoluteLayout int height = getViewHeight(); int maxZoomX = mContentWidth - width; if (maxZoomX > 0) { - int maxScreenX = width - (int) Math.ceil(width + int maxScreenX = width - (int) Math.ceil(width * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow-X" + Log.v(LOGTAG, "moveZoomScrollWindow-X" + " maxScreenX=" + maxScreenX + " width=" + width - + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x); + + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x); } x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX; x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit); @@ -2840,12 +2825,12 @@ public class WebView extends AbsoluteLayout } int maxZoomY = mContentHeight - height; if (maxZoomY > 0) { - int maxScreenY = height - (int) Math.ceil(height + int maxScreenY = height - (int) Math.ceil(height * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow-Y" + Log.v(LOGTAG, "moveZoomScrollWindow-Y" + " maxScreenY=" + maxScreenY + " height=" + height - + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y); + + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y); } y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY; y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit); @@ -2855,11 +2840,11 @@ public class WebView extends AbsoluteLayout invalidate(); } if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow" - + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")" - + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")" - + " maxZoom=(" + maxZoomX + ", " + maxZoomY + ")" - + " last=("+mLastScrollX+", "+mLastScrollY+")" + Log.v(LOGTAG, "moveZoomScrollWindow" + + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")" + + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")" + + " maxZoom=(" + maxZoomX + ", " + maxZoomY + ")" + + " last=("+mLastScrollX+", "+mLastScrollY+")" + " x=" + x + " y=" + y); } } @@ -2920,64 +2905,20 @@ public class WebView extends AbsoluteLayout } } - /** - * Class representing the node which is focused. - */ - private static class FocusNode { - public FocusNode() { - mBounds = new Rect(); - } - // Only to be called by JNI - private void setAll(boolean isTextField, boolean isTextArea, boolean - isPassword, boolean isAnchor, boolean isRtlText, int maxLength, - int textSize, int boundsX, int boundsY, int boundsRight, int - boundsBottom, int nodePointer, int framePointer, String text, - String name, int rootTextGeneration) { - mIsTextField = isTextField; - mIsTextArea = isTextArea; - mIsPassword = isPassword; - mIsAnchor = isAnchor; - mIsRtlText = isRtlText; - - mMaxLength = maxLength; - mTextSize = textSize; - - mBounds.set(boundsX, boundsY, boundsRight, boundsBottom); - - - mNodePointer = nodePointer; - mFramePointer = framePointer; - mText = text; - mName = name; - mRootTextGeneration = rootTextGeneration; - } - public boolean mIsTextField; - public boolean mIsTextArea; - public boolean mIsPassword; - public boolean mIsAnchor; - public boolean mIsRtlText; - - public int mSelectionStart; - public int mSelectionEnd; - public int mMaxLength; - public int mTextSize; - - public Rect mBounds; - - public int mNodePointer; - public int mFramePointer; - public String mText; - public String mName; - public int mRootTextGeneration; - } - - // Warning: ONLY use mFocusNode AFTER calling nativeUpdateFocusNode(), - // and ONLY if it returns true; - private FocusNode mFocusNode = new FocusNode(); - + WebViewCore.CursorData cursorData() { + WebViewCore.CursorData result = new WebViewCore.CursorData(); + result.mMoveGeneration = nativeMoveGeneration(); + result.mFrame = nativeCursorFramePointer(); + result.mNode = nativeCursorNodePointer(); + Rect bounds = nativeCursorNodeBounds(); + result.mX = bounds.centerX(); + result.mY = bounds.centerY(); + return result; + } + /** * Delete text from start to end in the focused textfield. If there is no - * focus, or if start == end, silently fail. If start and end are out of + * focus, or if start == end, silently fail. If start and end are out of * order, swap them. * @param start Beginning of selection to delete. * @param end End of selection to delete. @@ -2985,7 +2926,7 @@ public class WebView extends AbsoluteLayout /* package */ void deleteSelection(int start, int end) { mTextGeneration++; mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end, - new WebViewCore.FocusData(mFocusData)); + cursorData()); } /** @@ -2996,7 +2937,7 @@ public class WebView extends AbsoluteLayout */ /* package */ void setSelection(int start, int end) { mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end, - new WebViewCore.FocusData(mFocusData)); + cursorData()); } // Called by JNI when a touch event puts a textfield into focus. @@ -3021,7 +2962,7 @@ public class WebView extends AbsoluteLayout private void updateTextEntry() { // If we do not have focus, do nothing until we gain focus. if (!hasFocus() && (null == mTextEntry || !mTextEntry.hasFocus()) - || (mTouchMode >= FIRST_SCROLL_ZOOM + || (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM)) { mNeedsUpdateTextEntry = true; return; @@ -3029,14 +2970,8 @@ public class WebView extends AbsoluteLayout boolean alreadyThere = inEditingMode(); // inEditingMode can only return true if mTextEntry is non-null, // so we can safely call remove() if (alreadyThere) - if (0 == mNativeClass || !nativeUpdateFocusNode()) { - if (alreadyThere) { - mTextEntry.remove(); - } - return; - } - FocusNode node = mFocusNode; - if (!node.mIsTextField && !node.mIsTextArea) { + if (0 == mNativeClass || (!nativeFocusIsTextInput() + && !nativeCursorIsTextInput())) { if (alreadyThere) { mTextEntry.remove(); } @@ -3049,19 +2984,18 @@ public class WebView extends AbsoluteLayout // Initialize our generation number. mTextGeneration = 0; } - mTextEntry.setTextSize(contentToView(node.mTextSize)); + mTextEntry.setTextSize(contentToView(nativeFocusTextSize())); Rect visibleRect = sendOurVisibleRect(); // Note that sendOurVisibleRect calls viewToContent, so the coordinates // should be in content coordinates. - if (!Rect.intersects(node.mBounds, visibleRect)) { + Rect bounds = nativeFocusNodeBounds(); + if (!Rect.intersects(bounds, visibleRect)) { // Node is not on screen, so do not bother. return; } - int x = node.mBounds.left; - int y = node.mBounds.top; - int width = node.mBounds.width(); - int height = node.mBounds.height(); - if (alreadyThere && mTextEntry.isSameTextField(node.mNodePointer)) { + String text = nativeFocusText(); + int nodePointer = nativeFocusNodePointer(); + if (alreadyThere && mTextEntry.isSameTextField(nodePointer)) { // It is possible that we have the same textfield, but it has moved, // i.e. In the case of opening/closing the screen. // In that case, we need to set the dimensions, but not the other @@ -3071,36 +3005,37 @@ public class WebView extends AbsoluteLayout Spannable spannable = (Spannable) mTextEntry.getText(); int start = Selection.getSelectionStart(spannable); int end = Selection.getSelectionEnd(spannable); - setTextEntryRect(x, y, width, height); // If the text has been changed by webkit, update it. However, if // there has been more UI text input, ignore it. We will receive // another update when that text is recognized. - if (node.mText != null && !node.mText.equals(spannable.toString()) - && node.mRootTextGeneration == mTextGeneration) { - mTextEntry.setTextAndKeepSelection(node.mText); + if (text != null && !text.equals(spannable.toString()) + && nativeTextGeneration() == mTextGeneration) { + mTextEntry.setTextAndKeepSelection(text); } else { Selection.setSelection(spannable, start, end); } } else { - String text = node.mText; - setTextEntryRect(x, y, width, height); - mTextEntry.setGravity(node.mIsRtlText ? Gravity.RIGHT : + Rect vBox = contentToView(bounds); + mTextEntry.setRect(vBox.left, vBox.top, vBox.width(), vBox.height()); + mTextEntry.setGravity(nativeFocusIsRtlText() ? Gravity.RIGHT : Gravity.NO_GRAVITY); // this needs to be called before update adapter thread starts to // ensure the mTextEntry has the same node pointer - mTextEntry.setNodePointer(node.mNodePointer); + mTextEntry.setNodePointer(nodePointer); int maxLength = -1; - if (node.mIsTextField) { - maxLength = node.mMaxLength; + boolean isTextField = nativeFocusIsTextField(); + if (isTextField) { + maxLength = nativeFocusMaxLength(); + String name = nativeFocusName(); if (mWebViewCore.getSettings().getSaveFormData() - && node.mName != null) { + && name != null) { HashMap data = new HashMap(); - data.put("text", node.mText); + data.put("text", text); Message update = mPrivateHandler.obtainMessage( - UPDATE_TEXT_ENTRY_ADAPTER, node.mNodePointer, 0, + UPDATE_TEXT_ENTRY_ADAPTER, nodePointer, 0, data); UpdateTextEntryAdapter updater = new UpdateTextEntryAdapter( - node.mName, getUrl(), update); + name, getUrl(), update); Thread t = new Thread(updater); t.start(); } @@ -3108,8 +3043,8 @@ public class WebView extends AbsoluteLayout mTextEntry.setMaxLength(maxLength); AutoCompleteAdapter adapter = null; mTextEntry.setAdapterCustom(adapter); - mTextEntry.setSingleLine(node.mIsTextField); - mTextEntry.setInPassword(node.mIsPassword); + mTextEntry.setSingleLine(isTextField); + mTextEntry.setInPassword(nativeFocusIsPassword()); if (null == text) { mTextEntry.setText("", 0, 0); } else { @@ -3123,7 +3058,7 @@ public class WebView extends AbsoluteLayout // selection at the end, and textareas at the beginning. if (false) { mTextEntry.setText(text, 0, text.length()); - } else if (node.mIsTextField) { + } else if (isTextField) { int length = text.length(); mTextEntry.setText(text, length, length); } else { @@ -3156,14 +3091,6 @@ public class WebView extends AbsoluteLayout } } - private void setTextEntryRect(int x, int y, int width, int height) { - x = contentToView(x); - y = contentToView(y); - width = contentToView(width); - height = contentToView(height); - mTextEntry.setRect(x, y, width, height); - } - // This is used to determine long press with the enter key, or // a center key. Does not affect long press with the trackball/touch. private boolean mGotEnterDown = false; @@ -3199,14 +3126,14 @@ public class WebView extends AbsoluteLayout } if (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false - && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { mExtendSelection = false; mShiftIsPressed = true; - if (nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - mSelectX = contentToView(node.mBounds.left); - mSelectY = contentToView(node.mBounds.top); + if (nativeHasCursorNode()) { + Rect rect = nativeCursorNodeBounds(); + mSelectX = contentToView(rect.left); + mSelectY = contentToView(rect.top); } else { mSelectX = mScrollX + (int) mLastTouchX; mSelectY = mScrollY + (int) mLastTouchY; @@ -3305,10 +3232,9 @@ public class WebView extends AbsoluteLayout } // special CALL handling when focus node's href is "tel:XXX" - if (keyCode == KeyEvent.KEYCODE_CALL && nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - String text = node.mText; - if (!node.mIsTextField && !node.mIsTextArea && text != null + if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) { + String text = nativeCursorText(); + if (!nativeCursorIsTextInput() && text != null && text.startsWith(SCHEME_TEL)) { Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text)); getContext().startActivity(intent); @@ -3335,7 +3261,7 @@ public class WebView extends AbsoluteLayout return false; } - if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { if (commitCopy()) { return true; @@ -3367,7 +3293,7 @@ public class WebView extends AbsoluteLayout Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE"); } mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT); + .obtainMessage(SWITCH_TO_CLICK), TAP_TIMEOUT); mTouchMode = TOUCH_DOUBLECLICK_MODE; } return true; @@ -3377,19 +3303,14 @@ public class WebView extends AbsoluteLayout Rect visibleRect = sendOurVisibleRect(); // Note that sendOurVisibleRect calls viewToContent, so the // coordinates should be in content coordinates. - if (nativeUpdateFocusNode()) { - if (Rect.intersects(mFocusNode.mBounds, visibleRect)) { - nativeSetFollowedLink(true); - mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, - EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, - new WebViewCore.FocusData(mFocusData)); - playSoundEffect(SoundEffectConstants.CLICK); - if (!mCallbackProxy.uiOverrideUrlLoading(mFocusNode.mText)) { - // use CLICK instead of KEY_DOWN/KEY_UP so that we can - // trigger mouse click events - mWebViewCore.sendMessage(EventHub.CLICK); - } - } + if (nativeCursorIntersects(visibleRect)) { + nativeSetFollowedLink(true); + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, + EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, + cursorData()); + playSoundEffect(SoundEffectConstants.CLICK); + return true; + } else if (nativeHasCursorNode()) { return true; } // Bubble up the key event as WebView doesn't handle it @@ -3407,7 +3328,7 @@ public class WebView extends AbsoluteLayout // Bubble up the key event as WebView doesn't handle it return false; } - + /** * @hide */ @@ -3464,10 +3385,10 @@ public class WebView extends AbsoluteLayout // Clean up the zoom controller mZoomButtonsController.setVisible(false); } - + // Implementation for OnHierarchyChangeListener public void onChildViewAdded(View parent, View child) {} - + public void onChildViewRemoved(View p, View child) { if (child == this) { if (inEditingMode()) { @@ -3486,16 +3407,16 @@ public class WebView extends AbsoluteLayout public void onGlobalFocusChanged(View oldFocus, View newFocus) { } - // To avoid drawing the focus ring, and remove the TextView when our window + // To avoid drawing the cursor ring, and remove the TextView when our window // loses focus. @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (hasWindowFocus) { if (hasFocus()) { // If our window regained focus, and we have focus, then begin - // drawing the focus ring, and restore the TextView if + // drawing the cursor ring, and restore the TextView if // necessary. - mDrawFocusRing = true; + mDrawCursorRing = true; if (mNeedsUpdateTextEntry) { updateTextEntry(); } @@ -3505,8 +3426,8 @@ public class WebView extends AbsoluteLayout setFocusControllerActive(true); } else { // If our window gained focus, but we do not have it, do not - // draw the focus ring. - mDrawFocusRing = false; + // draw the cursor ring. + mDrawCursorRing = false; // We do not call nativeRecordButtons here because we assume // that when we lost focus, or window focus, it got called with // false for the first parameter @@ -3515,13 +3436,13 @@ public class WebView extends AbsoluteLayout if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) { /* * The zoom controls come in their own window, so our window - * loses focus. Our policy is to not draw the focus ring if + * loses focus. Our policy is to not draw the cursor ring if * our window is not focused, but this is an exception since * the user can still navigate the web page with the zoom * controls showing. */ - // If our window has lost focus, stop drawing the focus ring - mDrawFocusRing = false; + // If our window has lost focus, stop drawing the cursor ring + mDrawCursorRing = false; } mGotKeyDown = false; mShiftIsPressed = false; @@ -3552,9 +3473,9 @@ public class WebView extends AbsoluteLayout } if (focused) { // When we regain focus, if we have window focus, resume drawing - // the focus ring, and add the TextView if necessary. + // the cursor ring, and add the TextView if necessary. if (hasWindowFocus()) { - mDrawFocusRing = true; + mDrawCursorRing = true; if (mNeedsUpdateTextEntry) { updateTextEntry(); mNeedsUpdateTextEntry = false; @@ -3573,9 +3494,9 @@ public class WebView extends AbsoluteLayout } } else { // When we lost focus, unless focus went to the TextView (which is - // true if we are in editing mode), stop drawing the focus ring. + // true if we are in editing mode), stop drawing the cursor ring. if (!inEditingMode()) { - mDrawFocusRing = false; + mDrawCursorRing = false; if (mNativeClass != 0) { nativeRecordButtons(false, false, true); } @@ -3610,8 +3531,8 @@ public class WebView extends AbsoluteLayout super.onScrollChanged(l, t, oldl, oldt); sendOurVisibleRect(); } - - + + @Override public boolean dispatchKeyEvent(KeyEvent event) { boolean dispatch = true; @@ -3680,7 +3601,7 @@ public class WebView extends AbsoluteLayout if (mForwardTouchEvents && mTouchMode != SCROLL_ZOOM_OUT && mTouchMode != SCROLL_ZOOM_ANIMATION_IN && mTouchMode != SCROLL_ZOOM_ANIMATION_OUT - && (action != MotionEvent.ACTION_MOVE || + && (action != MotionEvent.ACTION_MOVE || eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) { WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData(); ted.mAction = action; @@ -3739,7 +3660,7 @@ public class WebView extends AbsoluteLayout break; } case MotionEvent.ACTION_MOVE: { - if (mTouchMode == TOUCH_DONE_MODE + if (mTouchMode == TOUCH_DONE_MODE || mTouchMode == SCROLL_ZOOM_ANIMATION_IN || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { // no dragging during scroll zoom animation @@ -3801,7 +3722,7 @@ public class WebView extends AbsoluteLayout if (settings.supportZoom() && settings.getBuiltInZoomControls() && !mZoomButtonsController.isVisible() - && (canZoomScrollOut() || + && (canZoomScrollOut() || mMinZoomScale < mMaxZoomScale)) { mZoomButtonsController.setVisible(true); } @@ -3827,7 +3748,7 @@ public class WebView extends AbsoluteLayout } // reverse direction means lock in the snap mode if ((ax > MAX_SLOPE_FOR_DIAG * ay) && - ((mSnapPositive && + ((mSnapPositive && deltaX < -mMinLockSnapReverseDistance) || (!mSnapPositive && deltaX > mMinLockSnapReverseDistance))) { @@ -3841,9 +3762,9 @@ public class WebView extends AbsoluteLayout } // reverse direction means lock in the snap mode if ((ay > MAX_SLOPE_FOR_DIAG * ax) && - ((mSnapPositive && + ((mSnapPositive && deltaY < -mMinLockSnapReverseDistance) - || (!mSnapPositive && + || (!mSnapPositive && deltaY > mMinLockSnapReverseDistance))) { mSnapScrollMode = SNAP_Y_LOCK; } @@ -3974,7 +3895,7 @@ public class WebView extends AbsoluteLayout } return true; } - + private long mTrackballFirstTime = 0; private long mTrackballLastTime = 0; private float mTrackballRemainsX = 0.0f; @@ -4000,10 +3921,10 @@ public class WebView extends AbsoluteLayout private Rect mLastFocusBounds; // Set by default; BrowserActivity clears to interpret trackball data - // directly for movement. Currently, the framework only passes + // directly for movement. Currently, the framework only passes // arrow key events, not trackball events, from one child to the next private boolean mMapTrackballToArrowKeys = true; - + public void setMapTrackballToArrowKeys(boolean setMap) { mMapTrackballToArrowKeys = setMap; } @@ -4021,23 +3942,23 @@ public class WebView extends AbsoluteLayout return true; } if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mPrivateHandler.removeMessages(SWITCH_TO_ENTER); + mPrivateHandler.removeMessages(SWITCH_TO_CLICK); mTrackballDown = true; if (mNativeClass != 0) { nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); } if (time - mLastFocusTime <= TRACKBALL_TIMEOUT - && !mLastFocusBounds.equals(nativeGetFocusRingBounds())) { + && !mLastFocusBounds.equals(nativeGetCursorRingBounds())) { nativeSelectBestAt(mLastFocusBounds); } if (LOGV_ENABLED) { Log.v(LOGTAG, "onTrackballEvent down ev=" + ev - + " time=" + time + + " time=" + time + " mLastFocusTime=" + mLastFocusTime); } if (isInTouchMode()) requestFocusFromTouch(); return false; // let common code in onKeyDown at it - } + } if (ev.getAction() == MotionEvent.ACTION_UP) { // LONG_PRESS_ENTER is set in common onKeyDown mPrivateHandler.removeMessages(LONG_PRESS_ENTER); @@ -4052,7 +3973,7 @@ public class WebView extends AbsoluteLayout } if (LOGV_ENABLED) { Log.v(LOGTAG, "onTrackballEvent up ev=" + ev - + " time=" + time + + " time=" + time ); } return false; // let common code in onKeyUp at it @@ -4061,7 +3982,7 @@ public class WebView extends AbsoluteLayout if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent gmail quit"); return false; } - // no move if we're still waiting on SWITCH_TO_ENTER timeout + // no move if we're still waiting on SWITCH_TO_CLICK timeout if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent 2 click quit"); return true; @@ -4078,7 +3999,7 @@ public class WebView extends AbsoluteLayout switchOutDrawHistory(); if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { if (LOGV_ENABLED) { - Log.v(LOGTAG, "onTrackballEvent time=" + Log.v(LOGTAG, "onTrackballEvent time=" + time + " last=" + mTrackballLastTime); } mTrackballFirstTime = time; @@ -4093,7 +4014,7 @@ public class WebView extends AbsoluteLayout doTrackball(time); return true; } - + void moveSelection(float xRate, float yRate) { if (mNativeClass == 0) return; @@ -4108,7 +4029,7 @@ public class WebView extends AbsoluteLayout mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET , mSelectY)); if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveSelection" + Log.v(LOGTAG, "moveSelection" + " mSelectX=" + mSelectX + " mSelectY=" + mSelectY + " mScrollX=" + mScrollX @@ -4120,10 +4041,10 @@ public class WebView extends AbsoluteLayout nativeMoveSelection(viewToContent(mSelectX) , viewToContent(mSelectY), mExtendSelection); int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET - : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET + : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET : 0; int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET - : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET + : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET : 0; pinScrollBy(scrollX, scrollY, true, 0); Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); @@ -4180,7 +4101,7 @@ public class WebView extends AbsoluteLayout if (elapsed == 0) { elapsed = TRACKBALL_TIMEOUT; } - float xRate = mTrackballRemainsX * 1000 / elapsed; + float xRate = mTrackballRemainsX * 1000 / elapsed; float yRate = mTrackballRemainsY * 1000 / elapsed; if (mShiftIsPressed) { moveSelection(xRate, yRate); @@ -4209,7 +4130,7 @@ public class WebView extends AbsoluteLayout mZoomScrollY += scaleTrackballY(yRate, maxWH); if (LOGV_ENABLED) { Log.v(LOGTAG, "doTrackball SCROLL_ZOOM_OUT" - + " mZoomScrollX=" + mZoomScrollX + + " mZoomScrollX=" + mZoomScrollX + " mZoomScrollY=" + mZoomScrollY); } mZoomScrollX = Math.min(width, Math.max(0, mZoomScrollX)); @@ -4227,13 +4148,13 @@ public class WebView extends AbsoluteLayout int oldScrollX = mScrollX; int oldScrollY = mScrollY; if (count > 0) { - int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? - KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : + int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? + KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT; count = Math.min(count, TRACKBALL_MOVE_COUNT); if (LOGV_ENABLED) { - Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode + Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode + " count=" + count + " mTrackballRemainsX=" + mTrackballRemainsX + " mTrackballRemainsY=" + mTrackballRemainsY); @@ -4250,8 +4171,8 @@ public class WebView extends AbsoluteLayout Log.v(LOGTAG, "doTrackball pinScrollBy" + " count=" + count + " xMove=" + xMove + " yMove=" + yMove - + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) - + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) + + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) + + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) ); } if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { @@ -4264,18 +4185,18 @@ public class WebView extends AbsoluteLayout pinScrollBy(xMove, yMove, true, 0); } mUserScroll = true; - } - mWebViewCore.sendMessage(EventHub.UNBLOCK_FOCUS); + } + mWebViewCore.sendMessage(EventHub.UNBLOCK_FOCUS); } public void flingScroll(int vx, int vy) { int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0); - + mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY); invalidate(); } - + private void doFling() { if (mVelocityTracker == null) { return; @@ -4294,7 +4215,7 @@ public class WebView extends AbsoluteLayout vx = 0; } } - + if (true /* EMG release: make our fling more like Maps' */) { // maps cuts their velocity in half vx = vx * 3 / 4; @@ -4355,7 +4276,7 @@ public class WebView extends AbsoluteLayout } if (mZoomControls == null) { mZoomControls = createZoomControls(); - + /* * need to be set to VISIBLE first so that getMeasuredHeight() in * {@link #onSizeChanged()} can return the measured value for proper @@ -4364,7 +4285,7 @@ public class WebView extends AbsoluteLayout mZoomControls.setVisibility(View.VISIBLE); mZoomControlRunnable = new Runnable() { public void run() { - + /* Don't dismiss the controls if the user has * focus on them. Wait and check again later. */ @@ -4416,7 +4337,7 @@ public class WebView extends AbsoluteLayout /** * Gets the {@link ZoomButtonsController} which can be used to add * additional buttons to the zoom controls window. - * + * * @return The instance of {@link ZoomButtonsController} used by this class, * or null if it is unavailable. * @hide @@ -4480,8 +4401,7 @@ public class WebView extends AbsoluteLayout Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0); } } - if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField - && !mFocusNode.mIsTextArea) { + if (nativeHasCursorNode() && !nativeCursorIsTextInput()) { playSoundEffect(SoundEffectConstants.CLICK); } } @@ -4520,7 +4440,7 @@ public class WebView extends AbsoluteLayout default: return result; } - if (mNativeClass != 0 && !nativeUpdateFocusNode()) { + if (mNativeClass != 0 && !nativeHasCursorNode()) { navHandledKey(fakeKeyDirection, 1, true, 0); } } @@ -4626,11 +4546,11 @@ public class WebView extends AbsoluteLayout return false; } - + /* package */ void replaceTextfieldText(int oldStart, int oldEnd, String replace, int newStart, int newEnd) { HashMap arg = new HashMap(); - arg.put("focusData", new WebViewCore.FocusData(mFocusData)); + arg.put("focusData", cursorData()); arg.put("replace", replace); arg.put("start", Integer.valueOf(newStart)); arg.put("end", Integer.valueOf(newEnd)); @@ -4640,19 +4560,19 @@ public class WebView extends AbsoluteLayout /* package */ void passToJavaScript(String currentText, KeyEvent event) { HashMap arg = new HashMap(); - arg.put("focusData", new WebViewCore.FocusData(mFocusData)); + arg.put("focusData", cursorData()); arg.put("event", event); arg.put("currentText", currentText); // Increase our text generation number, and pass it to webcore thread mTextGeneration++; mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); // WebKit's document state is not saved until about to leave the page. - // To make sure the host application, like Browser, has the up to date - // document state when it goes to background, we force to save the + // To make sure the host application, like Browser, has the up to date + // document state when it goes to background, we force to save the // document state. mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, - new WebViewCore.FocusData(mFocusData), 1000); + cursorData(), 1000); } /* package */ WebViewCore getWebViewCore() { @@ -4671,8 +4591,8 @@ public class WebView extends AbsoluteLayout @Override public void handleMessage(Message msg) { if (LOGV_ENABLED) { - Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what - > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) + Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what + > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) : HandlerDebugString[msg.what - REMEMBER_PASSWORD]); } switch (msg.what) { @@ -4703,19 +4623,28 @@ public class WebView extends AbsoluteLayout updateTextEntry(); break; } - case SWITCH_TO_ENTER: - if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER"); + case SWITCH_TO_CLICK: mTouchMode = TOUCH_DONE_MODE; - onKeyUp(KeyEvent.KEYCODE_ENTER - , new KeyEvent(KeyEvent.ACTION_UP - , KeyEvent.KEYCODE_ENTER)); + Rect visibleRect = sendOurVisibleRect(); + // Note that sendOurVisibleRect calls viewToContent, so the + // coordinates should be in content coordinates. + if (!nativeCursorIntersects(visibleRect)) { + break; + } + nativeSetFollowedLink(true); + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, + cursorData()); + playSoundEffect(SoundEffectConstants.CLICK); + if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) { + mWebViewCore.sendMessage(EventHub.CLICK); + } break; case SCROLL_BY_MSG_ID: setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj); break; case SYNC_SCROLL_TO_MSG_ID: if (mUserScroll) { - // if user has scrolled explicitly, don't sync the + // if user has scrolled explicitly, don't sync the // scroll position any more mUserScroll = false; break; @@ -4724,7 +4653,7 @@ public class WebView extends AbsoluteLayout case SCROLL_TO_MSG_ID: if (setContentScrollTo(msg.arg1, msg.arg2)) { // if we can't scroll to the exact position due to pin, - // send a message to WebCore to re-scroll when we get a + // send a message to WebCore to re-scroll when we get a // new picture mUserScroll = false; mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, @@ -4736,7 +4665,7 @@ public class WebView extends AbsoluteLayout break; case NEW_PICTURE_MSG_ID: // called for new content - final WebViewCore.DrawData draw = + final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj; final Point viewSize = draw.mViewPoint; if (mZoomScale > 0) { @@ -4759,7 +4688,7 @@ public class WebView extends AbsoluteLayout // received in the fixed dimension. final boolean updateLayout = viewSize.x == mLastWidthSent && viewSize.y == mLastHeightSent; - recordNewContentSize(draw.mWidthHeight.x, + recordNewContentSize(draw.mWidthHeight.x, draw.mWidthHeight.y, updateLayout); if (LOGV_ENABLED) { Rect b = draw.mInvalRegion.getBounds(); @@ -4777,8 +4706,8 @@ public class WebView extends AbsoluteLayout break; case UPDATE_TEXTFIELD_TEXT_MSG_ID: // Make sure that the textfield is currently focused - // and representing the same node as the pointer. - if (inEditingMode() && + // and representing the same node as the pointer. + if (inEditingMode() && mTextEntry.isSameTextField(msg.arg1)) { if (msg.getData().getBoolean("password")) { Spannable text = (Spannable) mTextEntry.getText(); @@ -4802,7 +4731,7 @@ public class WebView extends AbsoluteLayout break; } // Do not reset the focus or clear the text; the user may have already -// navigated or entered text at this point. The focus should have gotten +// navigated or entered text at this point. The focus should have gotten // reset, if need be, when the focus cache was built. Similarly, the text // view should already be torn down and rebuilt if needed. // nativeResetFocus(); @@ -4858,20 +4787,11 @@ public class WebView extends AbsoluteLayout case MARK_NODE_INVALID_ID: nativeMarkNodeInvalid(msg.arg1); break; - case NOTIFY_FOCUS_SET_MSG_ID: - if (mNativeClass != 0) { - nativeNotifyFocusSet(inEditingMode()); - } - break; case UPDATE_TEXT_ENTRY_MSG_ID: - // this is sent after finishing resize in WebViewCore. Make + // this is sent after finishing resize in WebViewCore. Make // sure the text edit box is still on the screen. - boolean alreadyThere = inEditingMode(); - if (alreadyThere && nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - if (node.mIsTextField || node.mIsTextArea) { - mTextEntry.bringIntoView(); - } + if (inEditingMode() && nativeCursorIsTextInput()) { + mTextEntry.bringIntoView(); } updateTextEntry(); break; @@ -4957,7 +4877,7 @@ public class WebView extends AbsoluteLayout // Passed in to a list with multiple selection to tell // which items are selected. private int[] mSelectedArray; - // Passed in to a list with single selection to tell + // Passed in to a list with single selection to tell // where the initial selection is. private int mSelection; @@ -4976,14 +4896,14 @@ public class WebView extends AbsoluteLayout } /** - * Subclass ArrayAdapter so we can disable OptionGroupLabels, + * Subclass ArrayAdapter so we can disable OptionGroupLabels, * and allow filtering. */ private class MyArrayListAdapter extends ArrayAdapter { public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) { - super(context, + super(context, multiple ? com.android.internal.R.layout.select_dialog_multichoice : - com.android.internal.R.layout.select_dialog_singlechoice, + com.android.internal.R.layout.select_dialog_singlechoice, objects); } @@ -5041,7 +4961,7 @@ public class WebView extends AbsoluteLayout } } - private InvokeListBox(String[] array, boolean[] enabled, int + private InvokeListBox(String[] array, boolean[] enabled, int selection) { mSelection = selection; mMultiple = false; @@ -5104,17 +5024,17 @@ public class WebView extends AbsoluteLayout public void run() { final ListView listView = (ListView) LayoutInflater.from(mContext) .inflate(com.android.internal.R.layout.select_dialog, null); - final MyArrayListAdapter adapter = new + final MyArrayListAdapter adapter = new MyArrayListAdapter(mContext, mContainers, mMultiple); AlertDialog.Builder b = new AlertDialog.Builder(mContext) .setView(listView).setCancelable(true) .setInverseBackgroundForced(true); - + if (mMultiple) { b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mWebViewCore.sendMessage( - EventHub.LISTBOX_CHOICES, + EventHub.LISTBOX_CHOICES, adapter.getCount(), 0, listView.getCheckedItemPositions()); }}); @@ -5130,10 +5050,10 @@ public class WebView extends AbsoluteLayout listView.setFocusableInTouchMode(true); // There is a bug (1250103) where the checks in a ListView with // multiple items selected are associated with the positions, not - // the ids, so the items do not properly retain their checks when + // the ids, so the items do not properly retain their checks when // filtered. Do not allow filtering on multiple lists until // that bug is fixed. - + listView.setTextFilterEnabled(!mMultiple); if (mMultiple) { listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); @@ -5196,47 +5116,26 @@ public class WebView extends AbsoluteLayout } // called by JNI - private void sendFinalFocus(int frame, int node, int x, int y) { - WebViewCore.FocusData focusData = new WebViewCore.FocusData(); - focusData.mFrame = frame; - focusData.mNode = node; - focusData.mX = x; - focusData.mY = y; - mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, - EventHub.NO_FOCUS_CHANGE_BLOCK, 0, focusData); + private void sendMoveMouse(int frame, int node, int x, int y) { + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, + new WebViewCore.CursorData(frame, node, x, y)); } // called by JNI - private void setFocusData(int moveGeneration, int buildGeneration, - int frame, int node, int x, int y, boolean ignoreNullFocus) { - mFocusData.mMoveGeneration = moveGeneration; - mFocusData.mBuildGeneration = buildGeneration; - mFocusData.mFrame = frame; - mFocusData.mNode = node; - mFocusData.mX = x; - mFocusData.mY = y; - mFocusData.mIgnoreNullFocus = ignoreNullFocus; - } - - // called by JNI - private void sendKitFocus() { - WebViewCore.FocusData focusData = new WebViewCore.FocusData(mFocusData); - mWebViewCore.sendMessage(EventHub.SET_KIT_FOCUS, focusData); + private void sendMoveMouseIfLatest() { + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, cursorData()); } // called by JNI - private void sendMotionUp(int touchGeneration, int buildGeneration, - int frame, int node, int x, int y, int size, - boolean retry) { + private void sendMotionUp(int touchGeneration, + int frame, int node, int x, int y, int size) { WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); touchUpData.mMoveGeneration = touchGeneration; - touchUpData.mBuildGeneration = buildGeneration; touchUpData.mSize = size; - touchUpData.mRetry = retry; - mFocusData.mFrame = touchUpData.mFrame = frame; - mFocusData.mNode = touchUpData.mNode = node; - mFocusData.mX = touchUpData.mX = x; - mFocusData.mY = touchUpData.mY = y; + touchUpData.mFrame = frame; + touchUpData.mNode = node; + touchUpData.mX = x; + touchUpData.mY = y; mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); } @@ -5275,7 +5174,7 @@ public class WebView extends AbsoluteLayout private void viewInvalidate() { invalidate(); } - + // return true if the key was handled private boolean navHandledKey(int keyCode, int count, boolean noScroll , long time) { @@ -5283,7 +5182,7 @@ public class WebView extends AbsoluteLayout return false; } mLastFocusTime = time; - mLastFocusBounds = nativeGetFocusRingBounds(); + mLastFocusBounds = nativeGetCursorRingBounds(); boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false; if (LOGV_ENABLED) { Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds @@ -5293,7 +5192,7 @@ public class WebView extends AbsoluteLayout if (keyHandled == false || mHeightCanMeasure == false) { return keyHandled; } - Rect contentFocus = nativeGetFocusRingBounds(); + Rect contentFocus = nativeGetCursorRingBounds(); if (contentFocus.isEmpty()) return keyHandled; Rect viewFocus = contentToView(contentFocus); Rect visRect = new Rect(); @@ -5324,7 +5223,7 @@ public class WebView extends AbsoluteLayout mUserScroll = true; return keyHandled; } - + /** * Set the background color. It's white by default. Pass * zero to make the view transparent. @@ -5339,7 +5238,7 @@ public class WebView extends AbsoluteLayout nativeDebugDump(); mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); } - + /** * Update our cache with updatedText. * @param updatedText The new text to put in our cache. @@ -5349,52 +5248,70 @@ public class WebView extends AbsoluteLayout // we recognize that it is up to date. nativeUpdateCachedTextfield(updatedText, mTextGeneration); } - - // Never call this version except by updateCachedTextfield(String) - - // we always want to pass in our generation number. - private native void nativeUpdateCachedTextfield(String updatedText, - int generation); + private native void nativeClearFocus(int x, int y); private native void nativeCreate(int ptr); + private native int nativeCursorFramePointer(); + private native Rect nativeCursorNodeBounds(); + /* package */ native int nativeCursorNodePointer(); + /* package */ native boolean nativeCursorMatchesFocus(); + private native boolean nativeCursorIntersects(Rect visibleRect); + private native boolean nativeCursorIsAnchor(); + private native boolean nativeCursorIsTextInput(); + private native String nativeCursorText(); private native void nativeDebugDump(); private native void nativeDestroy(); - private native void nativeDrawFocusRing(Canvas content); + private native void nativeDrawCursorRing(Canvas content); + private native void nativeDrawMatches(Canvas canvas); private native void nativeDrawSelection(Canvas content , int x, int y, boolean extendSelection); private native void nativeDrawSelectionRegion(Canvas content); - private native boolean nativeUpdateFocusNode(); - private native Rect nativeGetFocusRingBounds(); - private native Rect nativeGetNavBounds(); + private native void nativeDumpDisplayTree(String urlOrNull); + private native int nativeFindAll(String findLower, String findUpper); + private native void nativeFindNext(boolean forward); + private native boolean nativeFocusIsPassword(); + private native boolean nativeFocusIsRtlText(); + private native boolean nativeFocusIsTextField(); + private native boolean nativeFocusIsTextInput(); + private native int nativeFocusMaxLength(); + private native String nativeFocusName(); + private native Rect nativeFocusNodeBounds(); + /* package */ native int nativeFocusNodePointer(); + private native String nativeFocusText(); + private native int nativeFocusTextSize(); + /** + * Returns true if the native focus nodes says it wants to handle key events + * (ala plugins). This can only be called if mNativeClass is non-zero! + */ + private native boolean nativeFocusNodeWantsKeyEvents(); + private native Rect nativeGetCursorRingBounds(); + private native Region nativeGetSelection(); + private native boolean nativeHasCursorNode(); + private native boolean nativeHasFocusNode(); + private native String nativeImageURI(int x, int y); private native void nativeInstrumentReport(); private native void nativeMarkNodeInvalid(int node); // return true if the page has been scrolled private native boolean nativeMotionUp(int x, int y, int slop); // returns false if it handled the key - private native boolean nativeMoveFocus(int keyCode, int count, + private native boolean nativeMoveFocus(int keyCode, int count, boolean noScroll); - private native void nativeNotifyFocusSet(boolean inEditingMode); + private native int nativeMoveGeneration(); + private native void nativeMoveSelection(int x, int y, + boolean extendSelection); private native void nativeRecomputeFocus(); // Like many other of our native methods, you must make sure that // mNativeClass is not null before calling this method. private native void nativeRecordButtons(boolean focused, boolean pressed, boolean invalidate); - private native void nativeResetFocus(); - private native void nativeResetNavClipBounds(); private native void nativeSelectBestAt(Rect rect); private native void nativeSetFindIsDown(); private native void nativeSetFollowedLink(boolean followed); private native void nativeSetHeightCanMeasure(boolean measure); - private native void nativeSetNavBounds(Rect rect); - private native void nativeSetNavClipBounds(Rect rect); - private native String nativeImageURI(int x, int y); - /** - * Returns true if the native focus nodes says it wants to handle key events - * (ala plugins). This can only be called if mNativeClass is non-zero! - */ - private native boolean nativeFocusNodeWantsKeyEvents(); - private native void nativeMoveSelection(int x, int y - , boolean extendSelection); - private native Region nativeGetSelection(); + private native int nativeTextGeneration(); + // Never call this version except by updateCachedTextfield(String) - + // we always want to pass in our generation number. + private native void nativeUpdateCachedTextfield(String updatedText, + int generation); - private native void nativeDumpDisplayTree(String urlOrNull); } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index f9bbc313c8e8..1d91f52e00e5 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -96,7 +96,7 @@ final class WebViewCore { private int mViewportMaximumScale = 0; private boolean mViewportUserScalable = true; - + private int mRestoredScale = 100; private int mRestoredX = 0; private int mRestoredY = 0; @@ -308,15 +308,15 @@ final class WebViewCore { * Empty the picture set. */ private native void nativeClearContent(); - + /** * Create a flat picture from the set of pictures. */ private native void nativeCopyContentToPicture(Picture picture); - + /** * Draw the picture set with a background color. Returns true - * if some individual picture took too long to draw and can be + * if some individual picture took too long to draw and can be * split into parts. Called from the UI thread. */ private native boolean nativeDrawContent(Canvas canvas, int color); @@ -325,13 +325,13 @@ final class WebViewCore { * check to see if picture is blank and in progress */ private native boolean nativePictureReady(); - + /** * Redraw a portion of the picture set. The Point wh returns the * width and height of the overall picture. */ private native boolean nativeRecordContent(Region invalRegion, Point wh); - + /** * Splits slow parts of the picture set. Called from the webkit * thread after nativeDrawContent returns true. @@ -359,10 +359,10 @@ final class WebViewCore { float scale, int realScreenWidth, int screenHeight); private native int nativeGetContentMinPrefWidth(); - + // Start: functions that deal with text editing - private native void nativeReplaceTextfieldText(int frame, int node, int x, - int y, int oldStart, int oldEnd, String replace, int newStart, + private native void nativeReplaceTextfieldText(int frame, int node, int x, + int y, int oldStart, int oldEnd, String replace, int newStart, int newEnd); private native void passToJs(int frame, int node, int x, int y, int gen, @@ -373,31 +373,31 @@ final class WebViewCore { private native void nativeSaveDocumentState(int frame); - private native void nativeSetFinalFocus(int framePtr, int nodePtr, int x, - int y, boolean block); + private native void nativeMoveMouse(int framePtr, int nodePtr, int x, + int y); - private native void nativeSetKitFocus(int moveGeneration, - int buildGeneration, int framePtr, int nodePtr, int x, int y, + private native void nativeMoveMouseIfLatest(int moveGeneration, + int framePtr, int nodePtr, int x, int y, boolean ignoreNullFocus); private native String nativeRetrieveHref(int framePtr, int nodePtr); - - private native void nativeTouchUp(int touchGeneration, - int buildGeneration, int framePtr, int nodePtr, int x, int y, - int size, boolean retry); + + private native void nativeTouchUp(int touchGeneration, + int framePtr, int nodePtr, int x, int y, + int size); private native boolean nativeHandleTouchEvent(int action, int x, int y); private native void nativeUnblockFocus(); - + private native void nativeUpdateFrameCache(); - + private native void nativeSetSnapAnchor(int x, int y); - + private native void nativeSnapToAnchor(); - + private native void nativeSetBackgroundColor(int color); - + private native void nativeDumpDomTree(boolean useFile); private native void nativeDumpRenderTree(boolean useFile); @@ -406,7 +406,7 @@ final class WebViewCore { /** * Delete text from start to end in the focused textfield. If there is no - * focus, or if start == end, silently fail. If start and end are out of + * focus, or if start == end, silently fail. If start and end are out of * order, swap them. * @param start Beginning of selection to delete. * @param end End of selection to delete. @@ -424,7 +424,7 @@ final class WebViewCore { int start, int end); private native String nativeGetSelection(Region sel); - + // Register a scheme to be treated as local scheme so that it can access // local asset files for resources private native void nativeRegisterURLSchemeAsLocal(String scheme); @@ -485,7 +485,7 @@ final class WebViewCore { CacheManager.endCacheTransaction(); CacheManager.startCacheTransaction(); sendMessageDelayed( - obtainMessage(CACHE_TICKER), + obtainMessage(CACHE_TICKER), CACHE_TICKER_INTERVAL); } break; @@ -510,19 +510,15 @@ final class WebViewCore { } } - static class FocusData { - FocusData() {} - FocusData(FocusData d) { - mMoveGeneration = d.mMoveGeneration; - mBuildGeneration = d.mBuildGeneration; - mFrame = d.mFrame; - mNode = d.mNode; - mX = d.mX; - mY = d.mY; - mIgnoreNullFocus = d.mIgnoreNullFocus; + static class CursorData { + CursorData() {} + CursorData(int frame, int node, int x, int y) { + mFrame = frame; + mNode = node; + mX = x; + mY = y; } int mMoveGeneration; - int mBuildGeneration; int mFrame; int mNode; int mX; @@ -532,13 +528,11 @@ final class WebViewCore { static class TouchUpData { int mMoveGeneration; - int mBuildGeneration; int mFrame; int mNode; int mX; int mY; int mSize; - boolean mRetry; } static class TouchEventData { @@ -583,8 +577,8 @@ final class WebViewCore { "POST_URL", // = 132; "SPLIT_PICTURE_SET", // = 133; "CLEAR_CONTENT", // = 134; - "SET_FINAL_FOCUS", // = 135; - "SET_KIT_FOCUS", // = 136; + "SET_MOVE_MOUSE", // = 135; + "SET_MOVE_MOUSE_IF_LATEST", // = 136; "REQUEST_FOCUS_HREF", // = 137; "ADD_JS_INTERFACE", // = 138; "LOAD_DATA", // = 139; @@ -632,10 +626,10 @@ final class WebViewCore { static final int POST_URL = 132; static final int SPLIT_PICTURE_SET = 133; static final int CLEAR_CONTENT = 134; - + // UI nav messages - static final int SET_FINAL_FOCUS = 135; - static final int SET_KIT_FOCUS = 136; + static final int SET_MOVE_MOUSE = 135; + static final int SET_MOVE_MOUSE_IF_LATEST = 136; static final int REQUEST_FOCUS_HREF = 137; static final int ADD_JS_INTERFACE = 138; static final int LOAD_DATA = 139; @@ -668,7 +662,7 @@ final class WebViewCore { // private message ids private static final int DESTROY = 200; - + // flag values passed to message SET_FINAL_FOCUS static final int NO_FOCUS_CHANGE_BLOCK = 0; static final int BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP = 1; @@ -701,7 +695,7 @@ final class WebViewCore { @Override public void handleMessage(Message msg) { if (LOGV_ENABLED) { - Log.v(LOGTAG, msg.what < LOAD_URL || msg.what + Log.v(LOGTAG, msg.what < LOAD_URL || msg.what > SET_ACTIVE ? Integer.toString(msg.what) : HandlerDebugString[msg.what - LOAD_URL]); } @@ -744,7 +738,7 @@ final class WebViewCore { * we automatically add the scheme of the * baseUrl for local access as long as it is * not http(s)/ftp(s)/about/javascript - */ + */ String scheme = baseUrl.substring(0, i); if (!scheme.startsWith("http") && !scheme.startsWith("ftp") && @@ -762,9 +756,9 @@ final class WebViewCore { break; case STOP_LOADING: - // If the WebCore has committed the load, but not - // finished the first layout yet, we need to set - // first layout done to trigger the interpreted side sync + // If the WebCore has committed the load, but not + // finished the first layout yet, we need to set + // first layout done to trigger the interpreted side sync // up with native side if (mBrowserFrame.committed() && !mBrowserFrame.firstLayoutDone()) { @@ -800,7 +794,7 @@ final class WebViewCore { // (inv-zoom) nativeSetScrollOffset(msg.arg1, msg.arg2); break; - + case SET_GLOBAL_BOUNDS: Rect r = (Rect) msg.obj; nativeSetGlobalBounds(r.left, r.top, r.width(), @@ -811,7 +805,7 @@ final class WebViewCore { // If it is a standard load and the load is not // committed yet, we interpret BACK as RELOAD if (!mBrowserFrame.committed() && msg.arg1 == -1 && - (mBrowserFrame.loadType() == + (mBrowserFrame.loadType() == BrowserFrame.FRAME_LOADTYPE_STANDARD)) { mBrowserFrame.reload(true); } else { @@ -877,13 +871,13 @@ final class WebViewCore { close(mBrowserFrame.mNativeFrame); break; - case REPLACE_TEXT: + case REPLACE_TEXT: HashMap jMap = (HashMap) msg.obj; - FocusData fData = (FocusData) jMap.get("focusData"); + CursorData fData = (CursorData) jMap.get("focusData"); String replace = (String) jMap.get("replace"); - int newStart = + int newStart = ((Integer) jMap.get("start")).intValue(); - int newEnd = + int newEnd = ((Integer) jMap.get("end")).intValue(); nativeReplaceTextfieldText(fData.mFrame, fData.mNode, fData.mX, fData.mY, msg.arg1, @@ -892,7 +886,7 @@ final class WebViewCore { case PASS_TO_JS: { HashMap jsMap = (HashMap) msg.obj; - FocusData fDat = (FocusData) jsMap.get("focusData"); + CursorData fDat = (CursorData) jsMap.get("focusData"); KeyEvent evt = (KeyEvent) jsMap.get("event"); int keyCode = evt.getKeyCode(); int keyValue = evt.getUnicodeChar(); @@ -909,7 +903,7 @@ final class WebViewCore { } case SAVE_DOCUMENT_STATE: { - FocusData fDat = (FocusData) msg.obj; + CursorData fDat = (CursorData) msg.obj; nativeSaveDocumentState(fDat.mFrame); break; } @@ -922,11 +916,9 @@ final class WebViewCore { case TOUCH_UP: TouchUpData touchUpData = (TouchUpData) msg.obj; nativeTouchUp(touchUpData.mMoveGeneration, - touchUpData.mBuildGeneration, touchUpData.mFrame, touchUpData.mNode, - touchUpData.mX, touchUpData.mY, - touchUpData.mSize, - touchUpData.mRetry); + touchUpData.mX, touchUpData.mY, + touchUpData.mSize); break; case TOUCH_EVENT: { @@ -961,22 +953,20 @@ final class WebViewCore { mBrowserFrame.documentAsText((Message) msg.obj); break; - case SET_FINAL_FOCUS: - FocusData finalData = (FocusData) msg.obj; - nativeSetFinalFocus(finalData.mFrame, - finalData.mNode, finalData.mX, - finalData.mY, msg.arg1 - != EventHub.NO_FOCUS_CHANGE_BLOCK); + case SET_MOVE_MOUSE: + CursorData finalData = (CursorData) msg.obj; + nativeMoveMouse(finalData.mFrame, + finalData.mNode, finalData.mX, + finalData.mY); break; case UNBLOCK_FOCUS: nativeUnblockFocus(); break; - case SET_KIT_FOCUS: - FocusData focusData = (FocusData) msg.obj; - nativeSetKitFocus(focusData.mMoveGeneration, - focusData.mBuildGeneration, + case SET_MOVE_MOUSE_IF_LATEST: + CursorData focusData = (CursorData) msg.obj; + nativeMoveMouseIfLatest(focusData.mMoveGeneration, focusData.mFrame, focusData.mNode, focusData.mX, focusData.mY, focusData.mIgnoreNullFocus); @@ -989,7 +979,7 @@ final class WebViewCore { hrefMsg.sendToTarget(); break; } - + case UPDATE_CACHE_AND_TEXT_ENTRY: nativeUpdateFrameCache(); // FIXME: this should provide a minimal rectangle @@ -1009,21 +999,21 @@ final class WebViewCore { case SET_SNAP_ANCHOR: nativeSetSnapAnchor(msg.arg1, msg.arg2); break; - + case DELETE_SELECTION: - FocusData delData = (FocusData) msg.obj; + CursorData delData = (CursorData) msg.obj; nativeDeleteSelection(delData.mFrame, - delData.mNode, delData.mX, + delData.mNode, delData.mX, delData.mY, msg.arg1, msg.arg2); break; case SET_SELECTION: - FocusData selData = (FocusData) msg.obj; + CursorData selData = (CursorData) msg.obj; nativeSetSelection(selData.mFrame, - selData.mNode, selData.mX, + selData.mNode, selData.mX, selData.mY, msg.arg1, msg.arg2); break; - + case LISTBOX_CHOICES: SparseBooleanArray choices = (SparseBooleanArray) msg.obj; @@ -1032,18 +1022,18 @@ final class WebViewCore { for (int c = 0; c < choicesSize; c++) { choicesArray[c] = choices.get(c); } - nativeSendListBoxChoices(choicesArray, + nativeSendListBoxChoices(choicesArray, choicesSize); break; case SINGLE_LISTBOX_CHOICE: nativeSendListBoxChoice(msg.arg1); break; - + case SET_BACKGROUND_COLOR: nativeSetBackgroundColor(msg.arg1); break; - + case GET_SELECTION: String str = nativeGetSelection((Region) msg.obj); Message.obtain(mWebView.mPrivateHandler @@ -1072,7 +1062,7 @@ final class WebViewCore { nativeSplitContent(); mSplitPictureIsScheduled = false; break; - + case CLEAR_CONTENT: // Clear the view so that onDraw() will draw nothing // but white background @@ -1282,7 +1272,7 @@ final class WebViewCore { && (w < mViewportWidth || mViewportWidth == -1)) { int width = mViewportWidth; if (mViewportWidth == -1) { - if (mSettings.getLayoutAlgorithm() == + if (mSettings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NORMAL) { width = WebView.ZOOM_OUT_WIDTH; } else { @@ -1329,7 +1319,7 @@ final class WebViewCore { // Used to avoid posting more than one draw message. private boolean mDrawIsScheduled; - + // Used to avoid posting more than one split picture message. private boolean mSplitPictureIsScheduled; @@ -1338,7 +1328,7 @@ final class WebViewCore { // Used to end scale+scroll mode, accessed by both threads boolean mEndScaleZoom = false; - + public class DrawData { public DrawData() { mInvalRegion = new Region(); @@ -1348,12 +1338,12 @@ final class WebViewCore { public Point mViewPoint; public Point mWidthHeight; } - + private void webkitDraw() { mDrawIsScheduled = false; DrawData draw = new DrawData(); if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw start"); - if (nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight) + if (nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight) == false) { if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw abort"); return; @@ -1427,9 +1417,9 @@ final class WebViewCore { sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler .obtainMessage(WebCoreThread.REDUCE_PRIORITY)); // Note: there is one possible failure mode. If pauseUpdate() is called - // from UI thread while in webcore thread WEBKIT_DRAW is just pulled out - // of the queue and about to be executed. mDrawIsScheduled may be set to - // false in webkitDraw(). So update won't be blocked. But at least the + // from UI thread while in webcore thread WEBKIT_DRAW is just pulled out + // of the queue and about to be executed. mDrawIsScheduled may be set to + // false in webkitDraw(). So update won't be blocked. But at least the // webcore thread priority is still lowered. if (core != null) { synchronized (core) { @@ -1498,7 +1488,7 @@ final class WebViewCore { mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW)); } } - + // called by JNI private void contentScrollBy(int dx, int dy, boolean animate) { if (!mBrowserFrame.firstLayoutDone()) { @@ -1573,14 +1563,6 @@ final class WebViewCore { } } - // called by JNI - private void sendNotifyFocusSet() { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.NOTIFY_FOCUS_SET_MSG_ID).sendToTarget(); - } - } - // called by JNI private void sendNotifyProgressFinished() { sendUpdateTextEntry(); @@ -1617,12 +1599,12 @@ final class WebViewCore { } private native void setViewportSettingsFromNative(); - + // called by JNI private void didFirstLayout(boolean standardLoad) { // Trick to ensure that the Picture has the exact height for the content // by forcing to layout with 0 height after the page is ready, which is - // indicated by didFirstLayout. This is essential to get rid of the + // indicated by didFirstLayout. This is essential to get rid of the // white space in the GMail which uses WebView for message view. if (mWebView != null && mWebView.mHeightCanMeasure) { mWebView.mLastHeightSent = 0; @@ -1667,7 +1649,7 @@ final class WebViewCore { mViewportMaximumScale = mViewportInitialScale; } else if (mViewportInitialScale == 0) { mViewportInitialScale = mViewportMaximumScale; - } + } } if (mViewportWidth < 0 && mViewportInitialScale == 100) { mViewportWidth = 0; @@ -1737,7 +1719,7 @@ final class WebViewCore { String text, int textGeneration) { if (mWebView != null) { Message msg = Message.obtain(mWebView.mPrivateHandler, - WebView.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr, + WebView.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr, textGeneration, text); msg.getData().putBoolean("password", changeToPassword); msg.sendToTarget(); @@ -1763,7 +1745,7 @@ final class WebViewCore { if (mWebView != null) { mWebView.requestListBox(array, enabledArray, selection); } - + } private native void nativePause(); diff --git a/preloaded-classes b/preloaded-classes index e72c6af99f92..8ee015fae166 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -503,12 +503,11 @@ android.webkit.WebSyncManager android.webkit.WebSyncManager$SyncHandler android.webkit.WebView android.webkit.WebView$ExtendedZoomControls -android.webkit.WebView$FocusNode android.webkit.WebView$PrivateHandler android.webkit.WebViewCore +android.webkit.WebViewCore$CursorData android.webkit.WebViewCore$EventHub android.webkit.WebViewCore$EventHub$1 -android.webkit.WebViewCore$FocusData android.webkit.WebViewCore$WebCoreThread android.webkit.WebViewCore$WebCoreThread$1 android.webkit.WebViewDatabase -- cgit v1.2.3-59-g8ed1b From 21fd5f144a3e6919ff6b4e8a1a733c940d5dd6df Mon Sep 17 00:00:00 2001 From: Mike LeBeau Date: Mon, 1 Jun 2009 22:30:56 +0100 Subject: Change comment for SUGGEST_COLUMN_INTENT_EXTRA_DATA to clarify that this column is for use by global search only. --- core/java/android/app/SearchManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 3bf37c3b2b01..be2f50f0d799 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -1302,9 +1302,10 @@ public class SearchManager /** * Column name for suggestions cursor. Optional. This column allows suggestions * to provide additional arbitrary data which will be included as an extra under the key - * {@link #EXTRA_DATA_KEY}. + * {@link #EXTRA_DATA_KEY}. For use by the global search system only - if other providers + * attempt to use this column, the value will be overwritten by global search. * - * @hide pending API council approval + * @hide */ public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data"; /** -- cgit v1.2.3-59-g8ed1b From f315238a2b07c7e84f6ade800e504f520d262e66 Mon Sep 17 00:00:00 2001 From: Wink Saville Date: Mon, 1 Jun 2009 14:39:20 -0700 Subject: TODO's from Teleca with modifications from wink. --- .../drawable/stat_sys_vp_phone_call_bluetooth.png | Bin 0 -> 815 bytes core/res/res/values/strings.xml | 18 ++ .../com/android/server/status/StatusBarPolicy.java | 28 +- .../internal/telephony/CommandsInterface.java | 5 +- .../com/android/internal/telephony/PhoneBase.java | 9 - .../java/com/android/internal/telephony/RIL.java | 5 +- .../android/internal/telephony/cdma/CDMAPhone.java | 210 +------------- .../internal/telephony/cdma/CallFailCause.java | 17 +- .../telephony/cdma/CdmaDataConnectionTracker.java | 4 +- .../telephony/cdma/CdmaServiceStateTracker.java | 301 ++++++++++----------- .../android/internal/telephony/cdma/EriInfo.java | 22 -- .../internal/telephony/cdma/EriManager.java | 230 +++++++++++++++- .../internal/telephony/cdma/RuimRecords.java | 81 ++---- .../telephony/gsm/GsmDataConnectionTracker.java | 1 - .../telephony/gsm/GsmServiceStateTracker.java | 7 - .../internal/telephony/gsm/stk/StkService.java | 5 +- 16 files changed, 420 insertions(+), 523 deletions(-) create mode 100644 core/res/res/drawable/stat_sys_vp_phone_call_bluetooth.png diff --git a/core/res/res/drawable/stat_sys_vp_phone_call_bluetooth.png b/core/res/res/drawable/stat_sys_vp_phone_call_bluetooth.png new file mode 100644 index 000000000000..7abfd194fb80 Binary files /dev/null and b/core/res/res/drawable/stat_sys_vp_phone_call_bluetooth.png differ diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7078bbf0d65c..e048ffe97402 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -148,6 +148,24 @@ PAD + + + Roaming Indicator On + Roaming Indicator Off + Roaming Indicator Flashing + Out of Neighborhood + Out of Building + Roaming - Preferred System + Roaming - Available System + Roaming - Alliance Partner + Roaming - Premium Partner + Roaming - Full Service Functionality + Roaming - Partial Service Functionality + Roaming Banner On + Roaming Banner Off + Searching for Service + + +
  • Handle case where no activity matches
  • Consider how to launch your activities
  • Allow activities to add to current task
  • -
  • Notifications should be easy to return from
  • +
  • Notifications should let user easily get back
  • Use the notification system
  • Don't take over BACK key unless you absolutely need to
  • @@ -49,7 +49,6 @@ page.title=Activity and Task Design Guidelines
    1. Application Fundamentals
    2. -
    3. Activities and Tasks blog post
    @@ -630,12 +629,12 @@ page.title=Activity and Task Design Guidelines

    When the user takes an action on some data, such as touching a mailto:info@example.com link, they are actually initiating an Intent - object which then gets resolved to a particular component (we will - consider only activity components here). So, the result of a user - touching a mailto: link is an Intent object that the system tries to - match to an activity. If that Intent object was written explicitly - naming an activity (an explicit intent), then the system - immediately launches that activity in response to the user + object, or just an intent, which then gets resolved to a + particular component (we consider only activity components here). + So, the result of a user touching a mailto: link is an Intent object + that the system tries to match to an activity. If that Intent object was + written explicitly naming an activity (an explicit intent), + then the system immediately launches that activity in response to the user action. However, if that Intent object was written without naming an activity (an implicit intent), the system compares the Intent object to the intent filters of available activities. If more @@ -872,12 +871,29 @@ page.title=Activity and Task Design Guidelines

    Your applications can re-use activities made available from other - applications. In doing so, you cannot presume that external activity - will always be present — you must handle the case that the - external activity is not installed. Do this in the way you find most - appropriate, such as dimming the user control that accesses it (such - as a button or menu item), or displaying a message to the user that - sends them to the location to download it, such as the Market. + applications. In doing so, you cannot presume your intent will always + be resolved to a matching external activity — you must handle the case + where no application installed on the device can handle the intent. +

    + +

    + You can either test that an activity matches the intent, which you can do + before starting the activity, or catch an exception if starting the + activity fails. Both approaches are descibed in the blog posting + Can + I use this Intent?. +

    + +

    + To test whether an intent can be resolved, your code can query the package manager. + The blog post provides an example in the isIntentAvailable() helper method. + You can perform this test when initializing the user interface. + For instance, you could disable the user control that initiates + the Intent object, or display a message to the user that lets them go + to a location, such as the Market, to download its application. + In this way, your code can start the activity (using either startActivity() + or startActivityForResult()) only if the intent has tested to resolve + to an activity that is actually present.

    Consider how you want your activities to be launched or used by other applications

    @@ -1054,15 +1070,14 @@ page.title=Activity and Task Design Guidelines

    -

    Notifications should be easy for the user to return from

    - -

    - Applications that are in the background or haven't been run can - send out notifications to the user letting them know about events - of interest. For example, Calendar can send out notifications of - upcoming events, and Email can send out notifications when new - messages arrive. One of the user interface rules is that when the - user is in activity A and gets a notification for activity B and +

    Notifications should let the user easily get back to the previous activity

    +

    + Applications that are in the background or not running can have + services that send out notifications to the user letting them know about + events of interest. Two examples are Calendar, which can send out notifications of + upcoming events, and Email, which can send out notifications when new + messages arrive. One of the user interface guidelines is that when the + user is in activity A, gets a notification for activity B and picks that notification, when they press the BACK key, they should go back to activity A. 

    @@ -1108,11 +1123,11 @@ Notifications generally happen primarily in one of two ways:
    • - The application has a dedicated activity for - notification - For example, when the user receives a - Calendar notification, the act of selecting that + The chosen activity is dedicated for notification only - + For example, when the user receives a + Calendar notification, choosing that notification starts a special activity that displays a list - of upcoming calendar events — a view available only + of upcoming calendar events — this view is available only from the notification, not through the Calendar's own user interface. After viewing this upcoming event, to ensure that the user pressing the BACK key will return to the activity @@ -1140,25 +1155,25 @@ Notifications generally happen primarily in one of two ways:
    • - The user choosing the notification brings the activity to + The chosen activity is not dedicated, but always comes to the foreground in its initial state - For example, in - response to a notification, the Gmail application is brought - to the foreground presenting the list of conversations. You - do this by having the user's response to the notification - trigger an intent to launch the activity with the clear top - flag set. (That is, you put {@link + response to a notification, when the Gmail application comes + to the foreground, it always presents the list of conversations. + You can ensure this happens by setting a "clear top" flag in the + intent that the notification triggers. This ensures that when the + activity is launched, it displays its initial activity, preventing + Gmail from coming to the foreground in whatever state the user last + happened to be viewing it. (To do this, you put {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP - FLAG_ACTIVITY_CLEAR_TOP} in the intent you pass to - startActivity()). This prevents Gmail from coming to the - foreground in whatever state the user last happened to be - viewing it. + FLAG_ACTIVITY_CLEAR_TOP} in the intent you pass to startActivity()).

    There are other ways to handle notifications, such as bringing the - activity to the foreground set to display specific data, such as the - ongoing text message thread of a particular person. + activity to the foreground, set to display specific data, such as + displaying the text message thread for the person who just sent a + new text message.

    -- cgit v1.2.3-59-g8ed1b From 5b4718ba8aeac969095b75c20fbe74ced0c04725 Mon Sep 17 00:00:00 2001 From: Mike Reed Date: Tue, 2 Jun 2009 15:30:57 -0400 Subject: make FEATURE_OPENGL public (but hidden for now) so we can test with it --- core/java/android/view/Window.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index d7457a030468..2c32d8b54af6 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -56,11 +56,11 @@ public abstract class Window { public static final int FEATURE_CONTEXT_MENU = 6; /** Flag for custom title. You cannot combine this feature with other title features. */ public static final int FEATURE_CUSTOM_TITLE = 7; - /* Flag for asking for an OpenGL enabled window. + /** Flag for asking for an OpenGL enabled window. All 2D graphics will be handled by OpenGL ES. - Private for now, until it is better tested (not shipping in 1.0) + @hide */ - private static final int FEATURE_OPENGL = 8; + public static final int FEATURE_OPENGL = 8; /** Flag for setting the progress bar's visibility to VISIBLE */ public static final int PROGRESS_VISIBILITY_ON = -1; /** Flag for setting the progress bar's visibility to GONE */ -- cgit v1.2.3-59-g8ed1b From 47e82dee6b18c33fab8c2cdf4f68b20d3663079e Mon Sep 17 00:00:00 2001 From: Nick Pelly Date: Mon, 1 Jun 2009 19:09:37 -0700 Subject: Implement bulk read and writes for Bluetooth sockets. Before: 0.1 kB/s After: 100 kB/s (in my java BT speed test app) --- .../android/bluetooth/BluetoothInputStream.java | 42 ++++++++++++++++-- .../android/bluetooth/BluetoothOutputStream.java | 34 ++++++++++++++- core/java/android/bluetooth/BluetoothSocket.java | 4 +- core/jni/android_bluetooth_BluetoothSocket.cpp | 50 +++++++++++++++++----- 4 files changed, 112 insertions(+), 18 deletions(-) diff --git a/core/java/android/bluetooth/BluetoothInputStream.java b/core/java/android/bluetooth/BluetoothInputStream.java index ceae70c586d3..e6f501c9b69f 100644 --- a/core/java/android/bluetooth/BluetoothInputStream.java +++ b/core/java/android/bluetooth/BluetoothInputStream.java @@ -24,7 +24,6 @@ import java.io.InputStream; * * Used to write to a Bluetooth socket. * - * TODO: Implement bulk writes (instead of one byte at a time). * @hide */ /*package*/ final class BluetoothInputStream extends InputStream { @@ -54,9 +53,46 @@ import java.io.InputStream; * @return the byte read or -1 if the end of stream has been reached. * @throws IOException * if the stream is closed or another IOException occurs. - * @since Android 1.0 + * @since Android 1.5 */ public int read() throws IOException { - return mSocket.readNative(); + byte b[] = new byte[1]; + int ret = mSocket.readNative(b, 0, 1); + if (ret == 1) { + return (int)b[0]; + } else { + return -1; + } + } + + /** + * Reads at most {@code length} bytes from this stream and stores them in + * the byte array {@code b} starting at {@code offset}. + * + * @param b + * the byte array in which to store the bytes read. + * @param offset + * the initial position in {@code buffer} to store the bytes + * read from this stream. + * @param length + * the maximum number of bytes to store in {@code b}. + * @return the number of bytes actually read or -1 if the end of the stream + * has been reached. + * @throws IndexOutOfBoundsException + * if {@code offset < 0} or {@code length < 0}, or if + * {@code offset + length} is greater than the length of + * {@code b}. + * @throws IOException + * if the stream is closed or another IOException occurs. + * @since Android 1.5 + */ + public int read(byte[] b, int offset, int length) throws IOException { + if (b == null) { + throw new NullPointerException("byte array is null"); + } + if ((offset | length) < 0 || length > b.length - offset) { + throw new ArrayIndexOutOfBoundsException("invalid offset or length"); + } + return mSocket.readNative(b, offset, length); } } diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java index 32e6d17c82a8..7e2ead478afb 100644 --- a/core/java/android/bluetooth/BluetoothOutputStream.java +++ b/core/java/android/bluetooth/BluetoothOutputStream.java @@ -24,7 +24,6 @@ import java.io.OutputStream; * * Used to read from a Bluetooth socket. * - * TODO: Implement bulk reads (instead of one byte at a time). * @hide */ /*package*/ final class BluetoothOutputStream extends OutputStream { @@ -52,6 +51,37 @@ import java.io.OutputStream; * @since Android 1.0 */ public void write(int oneByte) throws IOException { - mSocket.writeNative(oneByte); + byte b[] = new byte[1]; + b[0] = (byte)oneByte; + mSocket.writeNative(b, 0, 1); + } + + /** + * Writes {@code count} bytes from the byte array {@code buffer} starting + * at position {@code offset} to this stream. + * + * @param b + * the buffer to be written. + * @param offset + * the start position in {@code buffer} from where to get bytes. + * @param count + * the number of bytes from {@code buffer} to write to this + * stream. + * @throws IOException + * if an error occurs while writing to this stream. + * @throws IndexOutOfBoundsException + * if {@code offset < 0} or {@code count < 0}, or if + * {@code offset + count} is bigger than the length of + * {@code buffer}. + * @since Android 1.0 + */ + public void write(byte[] b, int offset, int count) throws IOException { + if (b == null) { + throw new NullPointerException("buffer is null"); + } + if ((offset | count) < 0 || count > b.length - offset) { + throw new IndexOutOfBoundsException("invalid offset or length"); + } + mSocket.writeNative(b, offset, count); } } diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index fd8885ece993..670146b245ee 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -169,8 +169,8 @@ public final class BluetoothSocket implements Closeable { /*package*/ native void bindListenNative(int port) throws IOException; /*package*/ native BluetoothSocket acceptNative(int timeout) throws IOException; /*package*/ native int availableNative(); - /*package*/ native int readNative(); - /*package*/ native void writeNative(int data); + /*package*/ native int readNative(byte[] b, int offset, int length); + /*package*/ native int writeNative(byte[] b, int offset, int length); /*package*/ native void closeNative(); private native void destroyNative(); } diff --git a/core/jni/android_bluetooth_BluetoothSocket.cpp b/core/jni/android_bluetooth_BluetoothSocket.cpp index dc4c1d43ab0e..e9c04a54f49a 100644 --- a/core/jni/android_bluetooth_BluetoothSocket.cpp +++ b/core/jni/android_bluetooth_BluetoothSocket.cpp @@ -227,44 +227,72 @@ static jint availableNative(JNIEnv *env, jobject obj) { return -1; } -static jint readNative(JNIEnv *env, jobject obj) { +/** jb must not be null. offset and offset+length must be within array */ +static jint readNative(JNIEnv *env, jobject obj, jbyteArray jb, jint offset, + jint length) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); - char buf; + int ret; + jbyte *b; struct asocket *s = get_socketData(env, obj); if (!s) return -1; - if (asocket_read(s, &buf, 1, -1) < 0) { + b = env->GetByteArrayElements(jb, NULL); + if (b == NULL) { + jniThrowIOException(env, EINVAL); + return -1; + } + + ret = asocket_read(s, &b[offset], length, -1); + if (ret < 0) { jniThrowIOException(env, errno); + env->ReleaseByteArrayElements(jb, b, JNI_ABORT); return -1; } - return (jint)buf; + env->ReleaseByteArrayElements(jb, b, 0); + return (jint)ret; #endif jniThrowIOException(env, ENOSYS); return -1; } -static void writeNative(JNIEnv *env, jobject obj, jint data) { +/** jb must not be null. offset and offset+length must be within array */ +static jint writeNative(JNIEnv *env, jobject obj, jbyteArray jb, jint offset, + jint length) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); - const char buf = (char)data; + int ret; + jbyte *b; struct asocket *s = get_socketData(env, obj); if (!s) - return; + return -1; - if (asocket_write(s, &buf, 1, -1) < 0) + b = env->GetByteArrayElements(jb, NULL); + if (b == NULL) { + jniThrowIOException(env, EINVAL); + return -1; + } + + ret = asocket_write(s, &b[offset], length, -1); + if (ret < 0) { jniThrowIOException(env, errno); + env->ReleaseByteArrayElements(jb, b, JNI_ABORT); + return -1; + } + + env->ReleaseByteArrayElements(jb, b, JNI_ABORT); // no need to commit + return (jint)ret; - return; #endif jniThrowIOException(env, ENOSYS); + return -1; } static void closeNative(JNIEnv *env, jobject obj) { @@ -301,8 +329,8 @@ static JNINativeMethod sMethods[] = { {"bindListenNative", "(I)V", (void *) bindListenNative}, {"acceptNative", "(I)Landroid/bluetooth/BluetoothSocket;", (void *) acceptNative}, {"availableNative", "()I", (void *) availableNative}, - {"readNative", "()I", (void *) readNative}, - {"writeNative", "(I)V", (void *) writeNative}, + {"readNative", "([BII)I", (void *) readNative}, + {"writeNative", "([BII)I", (void *) writeNative}, {"closeNative", "()V", (void *) closeNative}, {"destroyNative", "()V", (void *) destroyNative}, }; -- cgit v1.2.3-59-g8ed1b From c9260540729d731e22458ce48127ca2ffaef33ee Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Thu, 28 May 2009 17:55:12 -0700 Subject: Consolidating data needed for contact aggregator into the data1 and data2 fields. The aggregator will then only read data1 and data2 and do the matching on those (taking mime type into account, of course). --- core/java/android/provider/ContactsContract.java | 51 +++++++++++++----------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 9d48bc7831e6..3763f9ae64f9 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -399,16 +399,16 @@ public final class ContactsContract { public static final String TYPE = "data1"; /** - * The user defined label for the the contact method. + * The data for the contact method. *

    Type: TEXT

    */ - public static final String LABEL = "data2"; + public static final String DATA = "data2"; /** - * The data for the contact method. + * The user defined label for the the contact method. *

    Type: TEXT

    */ - public static final String DATA = "data3"; + public static final String LABEL = "data3"; /** * Whether this is the primary entry of its kind for the contact it belongs to @@ -427,27 +427,28 @@ public final class ContactsContract { public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/name"; /** - * The contact's honorific prefix, e.g. "Sir" + * The given name for the contact. + *

    Type: TEXT

    */ - public static final String PREFIX = "data1"; + public static final String GIVEN_NAME = "data1"; /** - * The given name for the contact. + * The family name for the contact. *

    Type: TEXT

    */ - public static final String GIVEN_NAME = "data2"; + public static final String FAMILY_NAME = "data2"; /** - * The contact's middle name + * The contact's honorific prefix, e.g. "Sir" *

    Type: TEXT

    */ - public static final String MIDDLE_NAME = "data3"; + public static final String PREFIX = "data3"; /** - * The family name for the contact. + * The contact's middle name *

    Type: TEXT

    */ - public static final String FAMILY_NAME = "data4"; + public static final String MIDDLE_NAME = "data4"; /** * The contact's honorific suffix, e.g. "Jr" @@ -502,15 +503,15 @@ public final class ContactsContract { public static final int TYPE_INITIALS = 6; /** - * The user provided label, only used if TYPE is {@link #TYPE_CUSTOM}. - *

    Type: TEXT

    + * The name itself */ - public static final String LABEL = "data2"; + public static final String NAME = "data2"; /** - * The name itself + * The user provided label, only used if TYPE is {@link #TYPE_CUSTOM}. + *

    Type: TEXT

    */ - public static final String NAME = "data3"; + public static final String LABEL = "data3"; } /** @@ -535,8 +536,7 @@ public final class ContactsContract { * The phone number as the user entered it. *

    Type: TEXT

    */ - public static final String NUMBER = "data3"; - + public static final String NUMBER = "data2"; } /** @@ -552,7 +552,6 @@ public final class ContactsContract { public static final int TYPE_HOME = 1; public static final int TYPE_WORK = 2; public static final int TYPE_OTHER = 3; - } /** @@ -590,9 +589,11 @@ public final class ContactsContract { * The predefined IM protocol types. The protocol can either be non-present, one * of these types, or a free-form string. These cases are encoded in the PROTOCOL * column as: - * - null - * - pre: - * - custom: + *
      + *
    • null
    • + *
    • pre:<an integer, one of the protocols below>
    • + *
    • custom:<a string>
    • + *
    */ public static final int PROTOCOL_AIM = 0; public static final int PROTOCOL_MSN = 1; @@ -647,7 +648,6 @@ public final class ContactsContract { *

    Type: INTEGER (if set, non-0 means true)

    */ public static final String ISPRIMARY = "data5"; - } /** @@ -684,6 +684,9 @@ public final class ContactsContract { public static final String NOTE = "data1"; } + /** + * Custom ringtone associated with the contact. + */ public static final class CustomRingtone implements BaseCommonColumns { private CustomRingtone() {} -- cgit v1.2.3-59-g8ed1b From 78ebbabfe18f2c6fb4825e4bdbb1613e0901f0f3 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Tue, 2 Jun 2009 15:09:51 -0700 Subject: Addressed comments of change 2515 for the TtsService class: - made the SpeechItem and SoundResource inner classes static, - prefixed the TtsService member variables by 'm', - changed indentation from 2 to 4 characters. --- tts/java/android/tts/TtsService.java | 1270 +++++++++++++++++----------------- 1 file changed, 635 insertions(+), 635 deletions(-) diff --git a/tts/java/android/tts/TtsService.java b/tts/java/android/tts/TtsService.java index 4b794db7f52c..d31718181aaf 100755 --- a/tts/java/android/tts/TtsService.java +++ b/tts/java/android/tts/TtsService.java @@ -45,635 +45,158 @@ import java.util.concurrent.locks.ReentrantLock; */ public class TtsService extends Service implements OnCompletionListener { - private class SpeechItem { - public static final int SPEECH = 0; - public static final int EARCON = 1; - public static final int SILENCE = 2; - public String mText = null; - public ArrayList mParams = null; - public int mType = SPEECH; - public long mDuration = 0; - - public SpeechItem(String text, ArrayList params, int itemType) { - mText = text; - mParams = params; - mType = itemType; - } - - public SpeechItem(long silenceTime) { - mDuration = silenceTime; - } - } - - /** - * Contains the information needed to access a sound resource; the name of - * the package that contains the resource and the resID of the resource - * within that package. - */ - private class SoundResource { - public String mSourcePackageName = null; - public int mResId = -1; - public String mFilename = null; - - public SoundResource(String packageName, int id) { - mSourcePackageName = packageName; - mResId = id; - mFilename = null; - } + private static class SpeechItem { + public static final int SPEECH = 0; + public static final int EARCON = 1; + public static final int SILENCE = 2; + public String mText = null; + public ArrayList mParams = null; + public int mType = SPEECH; + public long mDuration = 0; + + public SpeechItem(String text, ArrayList params, int itemType) { + mText = text; + mParams = params; + mType = itemType; + } - public SoundResource(String file) { - mSourcePackageName = null; - mResId = -1; - mFilename = file; + public SpeechItem(long silenceTime) { + mDuration = silenceTime; + } } - } - private static final String ACTION = "android.intent.action.USE_TTS"; - private static final String CATEGORY = "android.intent.category.TTS"; - private static final String PKGNAME = "android.tts"; - - final RemoteCallbackList mCallbacks = new RemoteCallbackList(); - - private Boolean isSpeaking; - private ArrayList speechQueue; - private HashMap earcons; - private HashMap utterances; - private MediaPlayer player; - private TtsService self; + /** + * Contains the information needed to access a sound resource; the name of + * the package that contains the resource and the resID of the resource + * within that package. + */ + private static class SoundResource { + public String mSourcePackageName = null; + public int mResId = -1; + public String mFilename = null; + + public SoundResource(String packageName, int id) { + mSourcePackageName = packageName; + mResId = id; + mFilename = null; + } - private SharedPreferences prefs; + public SoundResource(String file) { + mSourcePackageName = null; + mResId = -1; + mFilename = file; + } + } - private final ReentrantLock speechQueueLock = new ReentrantLock(); - private final ReentrantLock synthesizerLock = new ReentrantLock(); + private static final String ACTION = "android.intent.action.USE_TTS"; + private static final String CATEGORY = "android.intent.category.TTS"; + private static final String PKGNAME = "android.tts"; - // TODO support multiple SpeechSynthesis objects - private SynthProxy nativeSynth; + final RemoteCallbackList mCallbacks = new RemoteCallbackList(); - @Override - public void onCreate() { - super.onCreate(); - Log.i("TTS", "TTS starting"); + private Boolean mIsSpeaking; + private ArrayList mSpeechQueue; + private HashMap mEarcons; + private HashMap mUtterances; + private MediaPlayer mPlayer; + private TtsService mSelf; + private SharedPreferences prefs; - // TODO: Make this work when the settings are done in the main Settings - // app. - prefs = PreferenceManager.getDefaultSharedPreferences(this); + private final ReentrantLock speechQueueLock = new ReentrantLock(); + private final ReentrantLock synthesizerLock = new ReentrantLock(); - // TODO: This should be changed to work by requesting the path - // from the default engine. - nativeSynth = new SynthProxy(prefs.getString("engine_pref", "")); + // TODO support multiple SpeechSynthesis objects + private SynthProxy nativeSynth; + @Override + public void onCreate() { + super.onCreate(); + Log.i("TTS", "TTS starting"); - self = this; - isSpeaking = false; - earcons = new HashMap(); - utterances = new HashMap(); + // TODO: Make this work when the settings are done in the main Settings + // app. + prefs = PreferenceManager.getDefaultSharedPreferences(this); - speechQueue = new ArrayList(); - player = null; + // TODO: This should be changed to work by requesting the path + // from the default engine. + nativeSynth = new SynthProxy(prefs.getString("engine_pref", "")); - setLanguage(prefs.getString("lang_pref", "en-rUS")); - setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140"))); - } - @Override - public void onDestroy() { - super.onDestroy(); - // Don't hog the media player - cleanUpPlayer(); + mSelf = this; + mIsSpeaking = false; - nativeSynth.shutdown(); + mEarcons = new HashMap(); + mUtterances = new HashMap(); - // Unregister all callbacks. - mCallbacks.kill(); - } + mSpeechQueue = new ArrayList(); + mPlayer = null; - private void setSpeechRate(int rate) { - if (prefs.getBoolean("override_pref", false)) { - // This is set to the default here so that the preview in the prefs - // activity will show the change without a restart, even if apps are - // not allowed to change the defaults. - rate = Integer.parseInt(prefs.getString("rate_pref", "140")); - } - nativeSynth.setSpeechRate(rate); - } - - private void setLanguage(String lang) { - if (prefs.getBoolean("override_pref", false)) { - // This is set to the default here so that the preview in the prefs - // activity will show the change without a restart, even if apps are - // not - // allowed to change the defaults. - lang = prefs.getString("lang_pref", "en-rUS"); + setLanguage(prefs.getString("lang_pref", "en-rUS")); + setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140"))); } - nativeSynth.setLanguage(lang); - } - private void setEngine(String engineName, String[] requestedLanguages, - int strictness) { - // TODO: Implement engine selection code here. - Intent engineIntent = new Intent( - "android.intent.action.START_TTS_ENGINE"); - if (engineName != null) { - engineIntent.addCategory("android.intent.action.tts_engine." - + engineName); - } - for (int i = 0; i < requestedLanguages.length; i++) { - engineIntent.addCategory("android.intent.action.tts_lang." - + requestedLanguages[i]); - } - ResolveInfo[] enginesArray = new ResolveInfo[0]; - PackageManager pm = getPackageManager(); - enginesArray = pm.queryIntentActivities(engineIntent, 0).toArray( - enginesArray); - } - - private void setEngine(Intent engineIntent) { - // TODO: Implement engine selection code here. - } - - private int getEngineStatus() { - // TODO: Proposal - add a sanity check method that - // TTS engine plugins must implement. - return 0; - } - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - private void addSpeech(String text, String packageName, int resId) { - utterances.put(text, new SoundResource(packageName, resId)); - } - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a complete - * path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - private void addSpeech(String text, String filename) { - utterances.put(text, new SoundResource(filename)); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - private void addEarcon(String earcon, String packageName, int resId) { - earcons.put(earcon, new SoundResource(packageName, resId)); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a complete - * path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - private void addEarcon(String earcon, String filename) { - earcons.put(earcon, new SoundResource(filename)); - } - - /** - * Speaks the given text using the specified queueing mode and parameters. - * - * @param text - * The text that should be spoken - * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. This is not implemented for all - * engines. - */ - private void speak(String text, int queueMode, ArrayList params) { - if (queueMode == 0) { - stop(); - } - speechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH)); - if (!isSpeaking) { - processSpeechQueue(); - } - } - - /** - * Plays the earcon using the specified queueing mode and parameters. - * - * @param earcon - * The earcon that should be played - * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. This is not implemented for all - * engines. - */ - private void playEarcon(String earcon, int queueMode, - ArrayList params) { - if (queueMode == 0) { - stop(); - } - speechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON)); - if (!isSpeaking) { - processSpeechQueue(); - } - } - - /** - * Stops all speech output and removes any utterances still in the queue. - */ - private void stop() { - Log.i("TTS", "Stopping"); - speechQueue.clear(); - - nativeSynth.stop(); - isSpeaking = false; - if (player != null) { - try { - player.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. - } - } - Log.i("TTS", "Stopped"); - } + @Override + public void onDestroy() { + super.onDestroy(); + // Don't hog the media player + cleanUpPlayer(); - public void onCompletion(MediaPlayer arg0) { - processSpeechQueue(); - } + nativeSynth.shutdown(); - private void playSilence(long duration, int queueMode, - ArrayList params) { - if (queueMode == 0) { - stop(); + // Unregister all callbacks. + mCallbacks.kill(); } - speechQueue.add(new SpeechItem(duration)); - if (!isSpeaking) { - processSpeechQueue(); - } - } - private void silence(final long duration) { - class SilenceThread implements Runnable { - public void run() { - try { - Thread.sleep(duration); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - processSpeechQueue(); + private void setSpeechRate(int rate) { + if (prefs.getBoolean("override_pref", false)) { + // This is set to the default here so that the preview in the prefs + // activity will show the change without a restart, even if apps are + // not allowed to change the defaults. + rate = Integer.parseInt(prefs.getString("rate_pref", "140")); } - } - } - Thread slnc = (new Thread(new SilenceThread())); - slnc.setPriority(Thread.MIN_PRIORITY); - slnc.start(); - } - - private void speakInternalOnly(final String text, - final ArrayList params) { - class SynthThread implements Runnable { - public void run() { - boolean synthAvailable = false; - try { - synthAvailable = synthesizerLock.tryLock(); - if (!synthAvailable) { - Thread.sleep(100); - Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); - synth.start(); - return; - } - nativeSynth.speak(text); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; - // even if the - // method returns somewhere in the try block. - if (synthAvailable) { - synthesizerLock.unlock(); - } - } - } - } - Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); - synth.start(); - } - - private SoundResource getSoundResource(SpeechItem speechItem) { - SoundResource sr = null; - String text = speechItem.mText; - if (speechItem.mType == SpeechItem.SILENCE) { - // Do nothing if this is just silence - } else if (speechItem.mType == SpeechItem.EARCON) { - sr = earcons.get(text); - } else { - sr = utterances.get(text); - } - return sr; - } - - private void dispatchSpeechCompletedCallbacks(String mark) { - Log.i("TTS callback", "dispatch started"); - // Broadcast to all clients the new value. - final int N = mCallbacks.beginBroadcast(); - for (int i = 0; i < N; i++) { - try { - mCallbacks.getBroadcastItem(i).markReached(mark); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing - // the dead object for us. - } + nativeSynth.setSpeechRate(rate); } - mCallbacks.finishBroadcast(); - Log.i("TTS callback", "dispatch completed to " + N); - } - - private void processSpeechQueue() { - boolean speechQueueAvailable = false; - try { - speechQueueAvailable = speechQueueLock.tryLock(); - if (!speechQueueAvailable) { - return; - } - if (speechQueue.size() < 1) { - isSpeaking = false; - // Dispatch a completion here as this is the - // only place where speech completes normally. - // Nothing left to say in the queue is a special case - // that is always a "mark" - associated text is null. - dispatchSpeechCompletedCallbacks(""); - return; - } - - SpeechItem currentSpeechItem = speechQueue.get(0); - isSpeaking = true; - SoundResource sr = getSoundResource(currentSpeechItem); - // Synth speech as needed - synthesizer should call - // processSpeechQueue to continue running the queue - Log.i("TTS processing: ", currentSpeechItem.mText); - if (sr == null) { - if (currentSpeechItem.mType == SpeechItem.SPEECH) { - // TODO: Split text up into smaller chunks before accepting - // them - // for processing. - speakInternalOnly(currentSpeechItem.mText, - currentSpeechItem.mParams); - } else { - // This is either silence or an earcon that was missing - silence(currentSpeechItem.mDuration); - } - } else { - cleanUpPlayer(); - if (sr.mSourcePackageName == PKGNAME) { - // Utterance is part of the TTS library - player = MediaPlayer.create(this, sr.mResId); - } else if (sr.mSourcePackageName != null) { - // Utterance is part of the app calling the library - Context ctx; - try { - ctx = this.createPackageContext(sr.mSourcePackageName, - 0); - } catch (NameNotFoundException e) { - e.printStackTrace(); - speechQueue.remove(0); // Remove it from the queue and - // move on - isSpeaking = false; - return; - } - player = MediaPlayer.create(ctx, sr.mResId); - } else { - // Utterance is coming from a file - player = MediaPlayer.create(this, Uri.parse(sr.mFilename)); - } - // Check if Media Server is dead; if it is, clear the queue and - // give up for now - hopefully, it will recover itself. - if (player == null) { - speechQueue.clear(); - isSpeaking = false; - return; + private void setLanguage(String lang) { + if (prefs.getBoolean("override_pref", false)) { + // This is set to the default here so that the preview in the prefs + // activity will show the change without a restart, even if apps are + // not + // allowed to change the defaults. + lang = prefs.getString("lang_pref", "en-rUS"); } - player.setOnCompletionListener(this); - try { - player.start(); - } catch (IllegalStateException e) { - speechQueue.clear(); - isSpeaking = false; - cleanUpPlayer(); - return; - } - } - if (speechQueue.size() > 0) { - speechQueue.remove(0); - } - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - } - } - - private void cleanUpPlayer() { - if (player != null) { - player.release(); - player = null; - } - } - - /** - * Synthesizes the given text using the specified queuing mode and - * parameters. - * - * @param text - * The String of text that should be synthesized - * @param params - * An ArrayList of parameters. The first element of this array - * controls the type of voice to use. - * @param filename - * The string that gives the full output filename; it should be - * something like "/sdcard/myappsounds/mysound.wav". - * @return A boolean that indicates if the synthesis succeeded - */ - private boolean synthesizeToFile(String text, ArrayList params, - String filename, boolean calledFromApi) { - // Only stop everything if this is a call made by an outside app trying - // to - // use the API. Do NOT stop if this is a call from within the service as - // clearing the speech queue here would be a mistake. - if (calledFromApi) { - stop(); - } - Log.i("TTS", "Synthesizing to " + filename); - boolean synthAvailable = false; - try { - synthAvailable = synthesizerLock.tryLock(); - if (!synthAvailable) { - return false; - } - // Don't allow a filename that is too long - // TODO use platform constant - if (filename.length() > 250) { - return false; - } - nativeSynth.synthesizeToFile(text, filename); - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (synthAvailable) { - synthesizerLock.unlock(); - } - } - Log.i("TTS", "Completed synthesis for " + filename); - return true; - } - - @Override - public IBinder onBind(Intent intent) { - if (ACTION.equals(intent.getAction())) { - for (String category : intent.getCategories()) { - if (category.equals(CATEGORY)) { - return mBinder; - } - } - } - return null; - } - - private final ITts.Stub mBinder = new Stub() { - - public void registerCallback(ITtsCallback cb) { - if (cb != null) - mCallbacks.register(cb); + nativeSynth.setLanguage(lang); } - public void unregisterCallback(ITtsCallback cb) { - if (cb != null) - mCallbacks.unregister(cb); - } - - /** - * Gives a hint about the type of engine that is preferred. - * - * @param selectedEngine - * The TTS engine that should be used - */ - public void setEngine(String engineName, String[] supportedLanguages, - int strictness) { - self.setEngine(engineName, supportedLanguages, strictness); - } - - /** - * Specifies exactly what the engine has to support. Will always be - * considered "strict"; can be used for implementing - * optional/experimental features that are not supported by all engines. - * - * @param engineIntent - * An intent that specifies exactly what the engine has to - * support. - */ - public void setEngineWithIntent(Intent engineIntent) { - self.setEngine(engineIntent); - } - - /** - * Speaks the given text using the specified queueing mode and - * parameters. - * - * @param text - * The text that should be spoken - * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. The first element of this - * array controls the type of voice to use. - */ - public void speak(String text, int queueMode, String[] params) { - ArrayList speakingParams = new ArrayList(); - if (params != null) { - speakingParams = new ArrayList(Arrays.asList(params)); - } - self.speak(text, queueMode, speakingParams); - } - - /** - * Plays the earcon using the specified queueing mode and parameters. - * - * @param earcon - * The earcon that should be played - * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. - */ - public void playEarcon(String earcon, int queueMode, String[] params) { - ArrayList speakingParams = new ArrayList(); - if (params != null) { - speakingParams = new ArrayList(Arrays.asList(params)); - } - self.playEarcon(earcon, queueMode, speakingParams); - } - - /** - * Plays the silence using the specified queueing mode and parameters. - * - * @param duration - * The duration of the silence that should be played - * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. - */ - public void playSilence(long duration, int queueMode, String[] params) { - ArrayList speakingParams = new ArrayList(); - if (params != null) { - speakingParams = new ArrayList(Arrays.asList(params)); - } - self.playSilence(duration, queueMode, speakingParams); + private void setEngine(String engineName, String[] requestedLanguages, + int strictness) { + // TODO: Implement engine selection code here. + Intent engineIntent = new Intent( + "android.intent.action.START_TTS_ENGINE"); + if (engineName != null) { + engineIntent.addCategory("android.intent.action.tts_engine." + + engineName); + } + for (int i = 0; i < requestedLanguages.length; i++) { + engineIntent.addCategory("android.intent.action.tts_lang." + + requestedLanguages[i]); + } + ResolveInfo[] enginesArray = new ResolveInfo[0]; + PackageManager pm = getPackageManager(); + enginesArray = pm.queryIntentActivities(engineIntent, 0).toArray( + enginesArray); } - - /** - * Stops all speech output and removes any utterances still in the - * queue. - */ - public void stop() { - self.stop(); + private void setEngine(Intent engineIntent) { + // TODO: Implement engine selection code here. } - /** - * Returns whether or not the TTS is speaking. - * - * @return Boolean to indicate whether or not the TTS is speaking - */ - public boolean isSpeaking() { - return (self.isSpeaking && (speechQueue.size() < 1)); + private int getEngineStatus() { + // TODO: Proposal - add a sanity check method that + // TTS engine plugins must implement. + return 0; } /** @@ -686,8 +209,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param resId * The resource ID of the sound within its package */ - public void addSpeech(String text, String packageName, int resId) { - self.addSpeech(text, packageName, resId); + private void addSpeech(String text, String packageName, int resId) { + mUtterances.put(text, new SoundResource(packageName, resId)); } /** @@ -696,11 +219,11 @@ public class TtsService extends Service implements OnCompletionListener { * @param text * The text that should be associated with the sound resource * @param filename - * The filename of the sound resource. This must be a - * complete path like: (/sdcard/mysounds/mysoundbite.mp3). + * The filename of the sound resource. This must be a complete + * path like: (/sdcard/mysounds/mysoundbite.mp3). */ - public void addSpeechFile(String text, String filename) { - self.addSpeech(text, filename); + private void addSpeech(String text, String filename) { + mUtterances.put(text, new SoundResource(filename)); } /** @@ -713,8 +236,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param resId * The resource ID of the sound within its package */ - public void addEarcon(String earcon, String packageName, int resId) { - self.addEarcon(earcon, packageName, resId); + private void addEarcon(String earcon, String packageName, int resId) { + mEarcons.put(earcon, new SoundResource(packageName, resId)); } /** @@ -723,61 +246,538 @@ public class TtsService extends Service implements OnCompletionListener { * @param earcon * The text that should be associated with the sound resource * @param filename - * The filename of the sound resource. This must be a - * complete path like: (/sdcard/mysounds/mysoundbite.mp3). + * The filename of the sound resource. This must be a complete + * path like: (/sdcard/mysounds/mysoundbite.mp3). */ - public void addEarconFile(String earcon, String filename) { - self.addEarcon(earcon, filename); + private void addEarcon(String earcon, String filename) { + mEarcons.put(earcon, new SoundResource(filename)); } /** - * Sets the speech rate for the TTS. Note that this will only have an - * effect on synthesized speech; it will not affect pre-recorded speech. + * Speaks the given text using the specified queueing mode and parameters. * - * @param speechRate - * The speech rate that should be used + * @param text + * The text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. */ - public void setSpeechRate(int speechRate) { - self.setSpeechRate(speechRate); + private void speak(String text, int queueMode, ArrayList params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH)); + if (!mIsSpeaking) { + processSpeechQueue(); + } } - // TODO: Fix comment about language /** - * Sets the speech rate for the TTS. Note that this will only have an - * effect on synthesized speech; it will not affect pre-recorded speech. + * Plays the earcon using the specified queueing mode and parameters. * - * @param language - * The language to be used. The languages are specified by - * their IETF language tags as defined by BCP 47. This is the - * same standard used for the lang attribute in HTML. See: - * http://en.wikipedia.org/wiki/IETF_language_tag + * @param earcon + * The earcon that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. */ - public void setLanguage(String language) { - self.setLanguage(language); + private void playEarcon(String earcon, int queueMode, + ArrayList params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + /** + * Stops all speech output and removes any utterances still in the queue. + */ + private void stop() { + Log.i("TTS", "Stopping"); + mSpeechQueue.clear(); + + nativeSynth.stop(); + mIsSpeaking = false; + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } + } + Log.i("TTS", "Stopped"); + } + + public void onCompletion(MediaPlayer arg0) { + processSpeechQueue(); + } + + private void playSilence(long duration, int queueMode, + ArrayList params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(duration)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + private void silence(final long duration) { + class SilenceThread implements Runnable { + public void run() { + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + processSpeechQueue(); + } + } + } + Thread slnc = (new Thread(new SilenceThread())); + slnc.setPriority(Thread.MIN_PRIORITY); + slnc.start(); + } + + private void speakInternalOnly(final String text, + final ArrayList params) { + class SynthThread implements Runnable { + public void run() { + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + Thread.sleep(100); + Thread synth = (new Thread(new SynthThread())); + synth.setPriority(Thread.MIN_PRIORITY); + synth.start(); + return; + } + nativeSynth.speak(text); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + // This check is needed because finally will always run; + // even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + } + } + Thread synth = (new Thread(new SynthThread())); + synth.setPriority(Thread.MIN_PRIORITY); + synth.start(); + } + + private SoundResource getSoundResource(SpeechItem speechItem) { + SoundResource sr = null; + String text = speechItem.mText; + if (speechItem.mType == SpeechItem.SILENCE) { + // Do nothing if this is just silence + } else if (speechItem.mType == SpeechItem.EARCON) { + sr = mEarcons.get(text); + } else { + sr = mUtterances.get(text); + } + return sr; + } + + private void dispatchSpeechCompletedCallbacks(String mark) { + Log.i("TTS callback", "dispatch started"); + // Broadcast to all clients the new value. + final int N = mCallbacks.beginBroadcast(); + for (int i = 0; i < N; i++) { + try { + mCallbacks.getBroadcastItem(i).markReached(mark); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + mCallbacks.finishBroadcast(); + Log.i("TTS callback", "dispatch completed to " + N); + } + + private void processSpeechQueue() { + boolean speechQueueAvailable = false; + try { + speechQueueAvailable = speechQueueLock.tryLock(); + if (!speechQueueAvailable) { + return; + } + if (mSpeechQueue.size() < 1) { + mIsSpeaking = false; + // Dispatch a completion here as this is the + // only place where speech completes normally. + // Nothing left to say in the queue is a special case + // that is always a "mark" - associated text is null. + dispatchSpeechCompletedCallbacks(""); + return; + } + + SpeechItem currentSpeechItem = mSpeechQueue.get(0); + mIsSpeaking = true; + SoundResource sr = getSoundResource(currentSpeechItem); + // Synth speech as needed - synthesizer should call + // processSpeechQueue to continue running the queue + Log.i("TTS processing: ", currentSpeechItem.mText); + if (sr == null) { + if (currentSpeechItem.mType == SpeechItem.SPEECH) { + // TODO: Split text up into smaller chunks before accepting + // them + // for processing. + speakInternalOnly(currentSpeechItem.mText, + currentSpeechItem.mParams); + } else { + // This is either silence or an earcon that was missing + silence(currentSpeechItem.mDuration); + } + } else { + cleanUpPlayer(); + if (sr.mSourcePackageName == PKGNAME) { + // Utterance is part of the TTS library + mPlayer = MediaPlayer.create(this, sr.mResId); + } else if (sr.mSourcePackageName != null) { + // Utterance is part of the app calling the library + Context ctx; + try { + ctx = this.createPackageContext(sr.mSourcePackageName, + 0); + } catch (NameNotFoundException e) { + e.printStackTrace(); + mSpeechQueue.remove(0); // Remove it from the queue and + // move on + mIsSpeaking = false; + return; + } + mPlayer = MediaPlayer.create(ctx, sr.mResId); + } else { + // Utterance is coming from a file + mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename)); + } + + // Check if Media Server is dead; if it is, clear the queue and + // give up for now - hopefully, it will recover itself. + if (mPlayer == null) { + mSpeechQueue.clear(); + mIsSpeaking = false; + return; + } + mPlayer.setOnCompletionListener(this); + try { + mPlayer.start(); + } catch (IllegalStateException e) { + mSpeechQueue.clear(); + mIsSpeaking = false; + cleanUpPlayer(); + return; + } + } + if (mSpeechQueue.size() > 0) { + mSpeechQueue.remove(0); + } + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (speechQueueAvailable) { + speechQueueLock.unlock(); + } + } + } + + private void cleanUpPlayer() { + if (mPlayer != null) { + mPlayer.release(); + mPlayer = null; + } } /** - * Speaks the given text using the specified queueing mode and + * Synthesizes the given text using the specified queuing mode and * parameters. * * @param text * The String of text that should be synthesized * @param params - * An ArrayList of parameters. The first element of this - * array controls the type of voice to use. + * An ArrayList of parameters. The first element of this array + * controls the type of voice to use. * @param filename - * The string that gives the full output filename; it should - * be something like "/sdcard/myappsounds/mysound.wav". + * The string that gives the full output filename; it should be + * something like "/sdcard/myappsounds/mysound.wav". * @return A boolean that indicates if the synthesis succeeded */ - public boolean synthesizeToFile(String text, String[] params, - String filename) { - ArrayList speakingParams = new ArrayList(); - if (params != null) { - speakingParams = new ArrayList(Arrays.asList(params)); - } - return self.synthesizeToFile(text, speakingParams, filename, true); + private boolean synthesizeToFile(String text, ArrayList params, + String filename, boolean calledFromApi) { + // Only stop everything if this is a call made by an outside app trying + // to + // use the API. Do NOT stop if this is a call from within the service as + // clearing the speech queue here would be a mistake. + if (calledFromApi) { + stop(); + } + Log.i("TTS", "Synthesizing to " + filename); + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + return false; + } + // Don't allow a filename that is too long + // TODO use platform constant + if (filename.length() > 250) { + return false; + } + nativeSynth.synthesizeToFile(text, filename); + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + Log.i("TTS", "Completed synthesis for " + filename); + return true; + } + + @Override + public IBinder onBind(Intent intent) { + if (ACTION.equals(intent.getAction())) { + for (String category : intent.getCategories()) { + if (category.equals(CATEGORY)) { + return mBinder; + } + } + } + return null; } - }; + + private final ITts.Stub mBinder = new Stub() { + + public void registerCallback(ITtsCallback cb) { + if (cb != null) + mCallbacks.register(cb); + } + + public void unregisterCallback(ITtsCallback cb) { + if (cb != null) + mCallbacks.unregister(cb); + } + + /** + * Gives a hint about the type of engine that is preferred. + * + * @param selectedEngine + * The TTS engine that should be used + */ + public void setEngine(String engineName, String[] supportedLanguages, + int strictness) { + mSelf.setEngine(engineName, supportedLanguages, strictness); + } + + /** + * Specifies exactly what the engine has to support. Will always be + * considered "strict"; can be used for implementing + * optional/experimental features that are not supported by all engines. + * + * @param engineIntent + * An intent that specifies exactly what the engine has to + * support. + */ + public void setEngineWithIntent(Intent engineIntent) { + mSelf.setEngine(engineIntent); + } + + /** + * Speaks the given text using the specified queueing mode and + * parameters. + * + * @param text + * The text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + */ + public void speak(String text, int queueMode, String[] params) { + ArrayList speakingParams = new ArrayList(); + if (params != null) { + speakingParams = new ArrayList(Arrays.asList(params)); + } + mSelf.speak(text, queueMode, speakingParams); + } + + /** + * Plays the earcon using the specified queueing mode and parameters. + * + * @param earcon + * The earcon that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. + */ + public void playEarcon(String earcon, int queueMode, String[] params) { + ArrayList speakingParams = new ArrayList(); + if (params != null) { + speakingParams = new ArrayList(Arrays.asList(params)); + } + mSelf.playEarcon(earcon, queueMode, speakingParams); + } + + /** + * Plays the silence using the specified queueing mode and parameters. + * + * @param duration + * The duration of the silence that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. + */ + public void playSilence(long duration, int queueMode, String[] params) { + ArrayList speakingParams = new ArrayList(); + if (params != null) { + speakingParams = new ArrayList(Arrays.asList(params)); + } + mSelf.playSilence(duration, queueMode, speakingParams); + } + + + /** + * Stops all speech output and removes any utterances still in the + * queue. + */ + public void stop() { + mSelf.stop(); + } + + /** + * Returns whether or not the TTS is speaking. + * + * @return Boolean to indicate whether or not the TTS is speaking + */ + public boolean isSpeaking() { + return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1)); + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + public void addSpeech(String text, String packageName, int resId) { + mSelf.addSpeech(text, packageName, resId); + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a + * complete path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + public void addSpeechFile(String text, String filename) { + mSelf.addSpeech(text, filename); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + public void addEarcon(String earcon, String packageName, int resId) { + mSelf.addEarcon(earcon, packageName, resId); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a + * complete path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + public void addEarconFile(String earcon, String filename) { + mSelf.addEarcon(earcon, filename); + } + + /** + * Sets the speech rate for the TTS. Note that this will only have an + * effect on synthesized speech; it will not affect pre-recorded speech. + * + * @param speechRate + * The speech rate that should be used + */ + public void setSpeechRate(int speechRate) { + mSelf.setSpeechRate(speechRate); + } + + // TODO: Fix comment about language + /** + * Sets the speech rate for the TTS. Note that this will only have an + * effect on synthesized speech; it will not affect pre-recorded speech. + * + * @param language + * The language to be used. The languages are specified by + * their IETF language tags as defined by BCP 47. This is the + * same standard used for the lang attribute in HTML. See: + * http://en.wikipedia.org/wiki/IETF_language_tag + */ + public void setLanguage(String language) { + mSelf.setLanguage(language); + } + + /** + * Speaks the given text using the specified queueing mode and + * parameters. + * + * @param text + * The String of text that should be synthesized + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + * @param filename + * The string that gives the full output filename; it should + * be something like "/sdcard/myappsounds/mysound.wav". + * @return A boolean that indicates if the synthesis succeeded + */ + public boolean synthesizeToFile(String text, String[] params, + String filename) { + ArrayList speakingParams = new ArrayList(); + if (params != null) { + speakingParams = new ArrayList(Arrays.asList(params)); + } + return mSelf.synthesizeToFile(text, speakingParams, filename, true); + } + }; } -- cgit v1.2.3-59-g8ed1b From ce16d787bdcefa393cb2da09652b580ba94b94a9 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Tue, 2 Jun 2009 15:15:12 -0700 Subject: Fixes #1890914. Bright theme's background color should be #fff9f9f9 instead of pure white (#ffffffff). --- core/res/res/values/colors.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 96369f4099a4..f67f04cf2469 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -18,7 +18,7 @@ */ --> - #ffffffff + #fff9f9f9 #ff1a1a1a #ff000000 #ff000000 @@ -37,7 +37,7 @@ #323232 #80323232 #808080 - #ffffffff + #fff9f9f9 #ff000000 #ffffffff #80000000 @@ -58,7 +58,7 @@ @drawable/editbox_dropdown_background_dark @drawable/editbox_dropdown_background - #ffffffff + #fff9f9f9 #ff0092f4 -- cgit v1.2.3-59-g8ed1b From 3dfd0e131efab40df4beba1e40d232a4fcf807dc Mon Sep 17 00:00:00 2001 From: Guang Zhu Date: Tue, 2 Jun 2009 15:42:48 -0700 Subject: Fixed issue where code for extracting scripts was eclipsed by runTest method. --- tests/DumpRenderTree/assets/run_reliability_tests.py | 8 ++------ .../src/com/android/dumprendertree/ReliabilityTest.java | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/DumpRenderTree/assets/run_reliability_tests.py b/tests/DumpRenderTree/assets/run_reliability_tests.py index c12c783060c4..84b9501620da 100755 --- a/tests/DumpRenderTree/assets/run_reliability_tests.py +++ b/tests/DumpRenderTree/assets/run_reliability_tests.py @@ -110,8 +110,8 @@ def main(options, args): # Call ReliabilityTestsAutoTest#startReliabilityTests test_cmd = (test_cmd_prefix + " -e class " "com.android.dumprendertree.ReliabilityTest#" - "runTest -e timeout %d %s" % - (timeout_ms, test_cmd_postfix)) + "runReliabilityTest -e timeout %s %s" % + (str(timeout_ms), test_cmd_postfix)) adb_output = subprocess.Popen(test_cmd, shell=True, stdout=subprocess.PIPE, @@ -125,10 +125,6 @@ def main(options, args): crashed_tests.append(crashed_test) logging.info("Resuming reliability test runner...") - test_cmd = (test_cmd_prefix + " -e class " - "com.android.dumprendertree.ReliabilityTest#" - "runTest -e timeout %d %s" % - (timeout_ms, test_cmd_postfix)) adb_output = subprocess.Popen(test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java index 081ddafa24ec..aa3940ed9b04 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java @@ -30,8 +30,7 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2 Date: Tue, 2 Jun 2009 16:02:31 -0700 Subject: Corrected the name of the native library the SynthProxy class loads (libttssynthproxy instead of libsynthproxy) to match the lib name from frameworks/base/tts/jni/Android.mk. --- tts/java/android/tts/SynthProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tts/java/android/tts/SynthProxy.java b/tts/java/android/tts/SynthProxy.java index 4ed975450d3c..e065f407599a 100755 --- a/tts/java/android/tts/SynthProxy.java +++ b/tts/java/android/tts/SynthProxy.java @@ -120,7 +120,7 @@ public class SynthProxy { } static { - System.loadLibrary("synthproxy"); + System.loadLibrary("ttssynthproxy"); } private final static String TAG = "SynthProxy"; -- cgit v1.2.3-59-g8ed1b From 6a669fac385b51b8bb01844b77a9a43840dda854 Mon Sep 17 00:00:00 2001 From: Nick Pelly Date: Tue, 2 Jun 2009 15:57:18 -0700 Subject: Implement and expose SCO socket support in BluetoothSocket.java. Implement L2CAP socket support, but do not expose it (untested). NEXT: Switch to Builder style constructor instead of factory method. --- .../android/bluetooth/BluetoothServerSocket.java | 44 ++++- core/java/android/bluetooth/BluetoothSocket.java | 53 +++-- core/jni/android_bluetooth_BluetoothSocket.cpp | 219 ++++++++++++++++++--- 3 files changed, 264 insertions(+), 52 deletions(-) diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index ca467011c91e..f3baeab18dee 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -27,7 +27,7 @@ import java.io.IOException; * RFCOMM is a connection orientated, streaming transport over Bluetooth. It is * also known as the Serial Port Profile (SPP). * - * TODO: Consider implementing SCO and L2CAP sockets. + * TODO: Consider exposing L2CAP sockets. * TODO: Clean up javadoc grammer and formatting. * TODO: Remove @hide * @hide @@ -45,9 +45,10 @@ public final class BluetoothServerSocket implements Closeable { * insufficient permissions. */ public static BluetoothServerSocket listenUsingRfcommOn(int port) throws IOException { - BluetoothServerSocket socket = new BluetoothServerSocket(true, true); + BluetoothServerSocket socket = new BluetoothServerSocket( + BluetoothSocket.TYPE_RFCOMM, true, true, port); try { - socket.mSocket.bindListenNative(port); + socket.mSocket.bindListenNative(); } catch (IOException e) { try { socket.close(); @@ -65,9 +66,31 @@ public final class BluetoothServerSocket implements Closeable { * insufficient permissions. */ public static BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException { - BluetoothServerSocket socket = new BluetoothServerSocket(false, false); + BluetoothServerSocket socket = new BluetoothServerSocket( + BluetoothSocket.TYPE_RFCOMM, false, false, port); try { - socket.mSocket.bindListenNative(port); + socket.mSocket.bindListenNative(); + } catch (IOException e) { + try { + socket.close(); + } catch (IOException e2) { } + throw e; + } + return socket; + } + + /** + * Construct a SCO server socket. + * Call #accept to retrieve connections to this socket. + * @return A SCO BluetoothServerSocket + * @throws IOException On error, for example Bluetooth not available, or + * insufficient permissions. + */ + public static BluetoothServerSocket listenUsingScoOn() throws IOException { + BluetoothServerSocket socket = new BluetoothServerSocket( + BluetoothSocket.TYPE_SCO, false, false, -1); + try { + socket.mSocket.bindListenNative(); } catch (IOException e) { try { socket.close(); @@ -79,13 +102,16 @@ public final class BluetoothServerSocket implements Closeable { /** * Construct a socket for incoming connections. - * @param auth Require the remote device to be authenticated - * @param encrypt Require the connection to be encrypted + * @param type type of socket + * @param auth require the remote device to be authenticated + * @param encrypt require the connection to be encrypted + * @param port remote port * @throws IOException On error, for example Bluetooth not available, or * insufficient priveleges */ - private BluetoothServerSocket(boolean auth, boolean encrypt) throws IOException { - mSocket = new BluetoothSocket(-1, auth, encrypt, null, -1); + private BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port) + throws IOException { + mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port); } /** diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 670146b245ee..de1f32601db2 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -29,13 +29,19 @@ import java.io.OutputStream; * RFCOMM is a connection orientated, streaming transport over Bluetooth. It is * also known as the Serial Port Profile (SPP). * - * TODO: Consider implementing SCO and L2CAP sockets. + * TODO: Consider exposing L2CAP sockets. * TODO: Clean up javadoc grammer and formatting. * TODO: Remove @hide * @hide */ public final class BluetoothSocket implements Closeable { - private final int mPort; + /** Keep TYPE_RFCOMM etc in sync with BluetoothSocket.cpp */ + /*package*/ static final int TYPE_RFCOMM = 1; + /*package*/ static final int TYPE_SCO = 2; + /*package*/ static final int TYPE_L2CAP = 3; + + private final int mType; /* one of TYPE_RFCOMM etc */ + private final int mPort; /* RFCOMM channel or L2CAP psm */ private final String mAddress; /* remote address */ private final boolean mAuth; private final boolean mEncrypt; @@ -57,7 +63,7 @@ public final class BluetoothSocket implements Closeable { */ public static BluetoothSocket createRfcommSocket(String address, int port) throws IOException { - return new BluetoothSocket(-1, true, true, address, port); + return new BluetoothSocket(TYPE_RFCOMM, -1, true, true, address, port); } /** @@ -74,11 +80,25 @@ public final class BluetoothSocket implements Closeable { */ public static BluetoothSocket createInsecureRfcommSocket(String address, int port) throws IOException { - return new BluetoothSocket(-1, false, false, address, port); + return new BluetoothSocket(TYPE_RFCOMM, -1, false, false, address, port); + } + + /** + * Construct a SCO socket ready to start an outgoing connection. + * Call #connect on the returned #BluetoothSocket to begin the connection. + * @param address remote Bluetooth address that this socket can connect to + * @return a SCO BluetoothSocket + * @throws IOException on error, for example Bluetooth not available, or + * insufficient permissions. + */ + public static BluetoothSocket createScoSocket(String address, int port) + throws IOException { + return new BluetoothSocket(TYPE_SCO, -1, true, true, address, port); } /** * Construct a Bluetooth. + * @param type type of socket * @param fd fd to use for connected socket, or -1 for a new socket * @param auth require the remote device to be authenticated * @param encrypt require the connection to be encrypted @@ -87,8 +107,9 @@ public final class BluetoothSocket implements Closeable { * @throws IOException On error, for example Bluetooth not available, or * insufficient priveleges */ - /*package*/ BluetoothSocket(int fd, boolean auth, boolean encrypt, String address, int port) - throws IOException { + /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, + int port) throws IOException { + mType = type; mAuth = auth; mEncrypt = encrypt; mAddress = address; @@ -120,7 +141,7 @@ public final class BluetoothSocket implements Closeable { * @throws IOException On error, for example connection failure */ public void connect() throws IOException { - connectNative(mAddress, mPort, -1); + connectNative(); } /** @@ -163,14 +184,14 @@ public final class BluetoothSocket implements Closeable { return mOutputStream; } - private native void initSocketNative(); - private native void initSocketFromFdNative(int fd); - private native void connectNative(String address, int port, int timeout); - /*package*/ native void bindListenNative(int port) throws IOException; + private native void initSocketNative() throws IOException; + private native void initSocketFromFdNative(int fd) throws IOException; + private native void connectNative() throws IOException; + /*package*/ native void bindListenNative() throws IOException; /*package*/ native BluetoothSocket acceptNative(int timeout) throws IOException; - /*package*/ native int availableNative(); - /*package*/ native int readNative(byte[] b, int offset, int length); - /*package*/ native int writeNative(byte[] b, int offset, int length); - /*package*/ native void closeNative(); - private native void destroyNative(); + /*package*/ native int availableNative() throws IOException; + /*package*/ native int readNative(byte[] b, int offset, int length) throws IOException; + /*package*/ native int writeNative(byte[] b, int offset, int length) throws IOException; + /*package*/ native void closeNative() throws IOException; + private native void destroyNative() throws IOException; } diff --git a/core/jni/android_bluetooth_BluetoothSocket.cpp b/core/jni/android_bluetooth_BluetoothSocket.cpp index e9c04a54f49a..9c4f7c7336f0 100644 --- a/core/jni/android_bluetooth_BluetoothSocket.cpp +++ b/core/jni/android_bluetooth_BluetoothSocket.cpp @@ -31,16 +31,29 @@ #ifdef HAVE_BLUETOOTH #include #include +#include +#include #endif +#define TYPE_AS_STR(t) \ + ((t) == TYPE_RFCOMM ? "RFCOMM" : ((t) == TYPE_SCO ? "SCO" : "L2CAP")) + namespace android { static jfieldID field_mAuth; /* read-only */ static jfieldID field_mEncrypt; /* read-only */ +static jfieldID field_mType; /* read-only */ +static jfieldID field_mAddress; /* read-only */ +static jfieldID field_mPort; /* read-only */ static jfieldID field_mSocketData; static jmethodID method_BluetoothSocket_ctor; static jclass class_BluetoothSocket; +/* Keep TYPE_RFCOMM etc in sync with BluetoothSocket.java */ +static const int TYPE_RFCOMM = 1; +static const int TYPE_SCO = 2; +static const int TYPE_L2CAP = 3; // TODO: Test l2cap code paths + static struct asocket *get_socketData(JNIEnv *env, jobject obj) { struct asocket *s = (struct asocket *) env->GetIntField(obj, field_mSocketData); @@ -76,9 +89,25 @@ static void initSocketNative(JNIEnv *env, jobject obj) { int lm = 0; jboolean auth; jboolean encrypt; + jint type; + + type = env->GetIntField(obj, field_mType); + + switch (type) { + case TYPE_RFCOMM: + fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + break; + case TYPE_SCO: + fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + break; + case TYPE_L2CAP: + fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + break; + default: + jniThrowIOException(env, ENOSYS); + return; + } - /*TODO: do not hardcode to rfcomm */ - fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); if (fd < 0) { LOGV("socket() failed, throwing"); jniThrowIOException(env, errno); @@ -88,8 +117,17 @@ static void initSocketNative(JNIEnv *env, jobject obj) { auth = env->GetBooleanField(obj, field_mAuth); encrypt = env->GetBooleanField(obj, field_mEncrypt); - lm |= auth ? RFCOMM_LM_AUTH : 0; - lm |= encrypt? RFCOMM_LM_ENCRYPT : 0; + /* kernel does not yet support LM for SCO */ + switch (type) { + case TYPE_RFCOMM: + lm |= auth ? RFCOMM_LM_AUTH : 0; + lm |= encrypt? RFCOMM_LM_ENCRYPT : 0; + break; + case TYPE_L2CAP: + lm |= auth ? L2CAP_LM_AUTH : 0; + lm |= encrypt? L2CAP_LM_ENCRYPT : 0; + break; + } if (lm) { if (setsockopt(fd, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm))) { @@ -99,36 +137,83 @@ static void initSocketNative(JNIEnv *env, jobject obj) { } } + LOGV("...fd %d created (%s, lm = %x)", fd, TYPE_AS_STR(type), lm); + initSocketFromFdNative(env, obj, fd); return; #endif jniThrowIOException(env, ENOSYS); } -static void connectNative(JNIEnv *env, jobject obj, jstring address, - jint port, jint timeout) { +static void connectNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); int ret; - struct sockaddr_rc addr; + jint type; const char *c_address; + jstring address; + bdaddr_t bdaddress; + socklen_t addr_sz; + struct sockaddr *addr; struct asocket *s = get_socketData(env, obj); if (!s) return; - addr.rc_family = AF_BLUETOOTH; - addr.rc_channel = port; + type = env->GetIntField(obj, field_mType); + + /* parse address into bdaddress */ + address = (jstring) env->GetObjectField(obj, field_mAddress); c_address = env->GetStringUTFChars(address, NULL); - if (get_bdaddr((const char *)c_address, &addr.rc_bdaddr)) { + if (get_bdaddr(c_address, &bdaddress)) { env->ReleaseStringUTFChars(address, c_address); jniThrowIOException(env, EINVAL); return; } env->ReleaseStringUTFChars(address, c_address); - ret = asocket_connect(s, (struct sockaddr *)&addr, sizeof(addr), timeout); + switch (type) { + case TYPE_RFCOMM: + struct sockaddr_rc addr_rc; + addr = (struct sockaddr *)&addr_rc; + addr_sz = sizeof(addr_rc); + + memset(addr, 0, addr_sz); + addr_rc.rc_family = AF_BLUETOOTH; + addr_rc.rc_channel = env->GetIntField(obj, field_mPort); + memcpy(&addr_rc.rc_bdaddr, &bdaddress, sizeof(bdaddr_t)); + + break; + case TYPE_SCO: + struct sockaddr_sco addr_sco; + addr = (struct sockaddr *)&addr_sco; + addr_sz = sizeof(addr_sco); + + memset(addr, 0, addr_sz); + addr_sco.sco_family = AF_BLUETOOTH; + memcpy(&addr_sco.sco_bdaddr, &bdaddress, sizeof(bdaddr_t)); + + break; + case TYPE_L2CAP: + struct sockaddr_l2 addr_l2; + addr = (struct sockaddr *)&addr_l2; + addr_sz = sizeof(addr_l2); + + memset(addr, 0, addr_sz); + addr_l2.l2_family = AF_BLUETOOTH; + addr_l2.l2_psm = env->GetIntField(obj, field_mPort); + memcpy(&addr_l2.l2_bdaddr, &bdaddress, sizeof(bdaddr_t)); + + break; + default: + jniThrowIOException(env, ENOSYS); + return; + } + + ret = asocket_connect(s, addr, addr_sz, -1); + LOGV("...connect(%d, %s) = %d (errno %d)", + s->fd, TYPE_AS_STR(type), ret, errno); if (ret) jniThrowIOException(env, errno); @@ -138,22 +223,57 @@ static void connectNative(JNIEnv *env, jobject obj, jstring address, jniThrowIOException(env, ENOSYS); } -static void bindListenNative(JNIEnv *env, jobject obj, jint port) { +static void bindListenNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); - struct sockaddr_rc addr; + jint type; + socklen_t addr_sz; + struct sockaddr *addr; + bdaddr_t bdaddr = *BDADDR_ANY; struct asocket *s = get_socketData(env, obj); if (!s) return; - memset(&addr, 0, sizeof(struct sockaddr_rc)); - addr.rc_family = AF_BLUETOOTH; - addr.rc_bdaddr = *BDADDR_ANY; - addr.rc_channel = port; + type = env->GetIntField(obj, field_mType); + + switch (type) { + case TYPE_RFCOMM: + struct sockaddr_rc addr_rc; + addr = (struct sockaddr *)&addr_rc; + addr_sz = sizeof(addr_rc); + + memset(addr, 0, addr_sz); + addr_rc.rc_family = AF_BLUETOOTH; + addr_rc.rc_channel = env->GetIntField(obj, field_mPort); + memcpy(&addr_rc.rc_bdaddr, &bdaddr, sizeof(bdaddr_t)); + break; + case TYPE_SCO: + struct sockaddr_sco addr_sco; + addr = (struct sockaddr *)&addr_sco; + addr_sz = sizeof(addr_sco); + + memset(addr, 0, addr_sz); + addr_sco.sco_family = AF_BLUETOOTH; + memcpy(&addr_sco.sco_bdaddr, &bdaddr, sizeof(bdaddr_t)); + break; + case TYPE_L2CAP: + struct sockaddr_l2 addr_l2; + addr = (struct sockaddr *)&addr_l2; + addr_sz = sizeof(addr_l2); + + memset(addr, 0, addr_sz); + addr_l2.l2_family = AF_BLUETOOTH; + addr_l2.l2_psm = env->GetIntField(obj, field_mPort); + memcpy(&addr_l2.l2_bdaddr, &bdaddr, sizeof(bdaddr_t)); + break; + default: + jniThrowIOException(env, ENOSYS); + return; + } - if (bind(s->fd, (struct sockaddr *)&addr, sizeof(addr))) { + if (bind(s->fd, addr, addr_sz)) { jniThrowIOException(env, errno); return; } @@ -173,10 +293,12 @@ static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) { LOGV(__FUNCTION__); int fd; - struct sockaddr_rc addr; - int addrlen = sizeof(addr); + jint type; + struct sockaddr *addr; + socklen_t addr_sz; jstring addr_jstr; char addr_cstr[BTADDR_SIZE]; + bdaddr_t *bdaddr; jboolean auth; jboolean encrypt; @@ -185,7 +307,39 @@ static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) { if (!s) return NULL; - fd = asocket_accept(s, (struct sockaddr *)&addr, &addrlen, timeout); + type = env->GetIntField(obj, field_mType); + + switch (type) { + case TYPE_RFCOMM: + struct sockaddr_rc addr_rc; + addr = (struct sockaddr *)&addr_rc; + addr_sz = sizeof(addr_rc); + bdaddr = &addr_rc.rc_bdaddr; + memset(addr, 0, addr_sz); + break; + case TYPE_SCO: + struct sockaddr_sco addr_sco; + addr = (struct sockaddr *)&addr_sco; + addr_sz = sizeof(addr_sco); + bdaddr = &addr_sco.sco_bdaddr; + memset(addr, 0, addr_sz); + break; + case TYPE_L2CAP: + struct sockaddr_l2 addr_l2; + addr = (struct sockaddr *)&addr_l2; + addr_sz = sizeof(addr_l2); + bdaddr = &addr_l2.l2_bdaddr; + memset(addr, 0, addr_sz); + break; + default: + jniThrowIOException(env, ENOSYS); + return NULL; + } + + fd = asocket_accept(s, addr, &addr_sz, timeout); + + LOGV("...accept(%d, %s) = %d (errno %d)", + s->fd, TYPE_AS_STR(type), fd, errno); if (fd < 0) { jniThrowIOException(env, errno); @@ -195,10 +349,12 @@ static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) { /* Connected - return new BluetoothSocket */ auth = env->GetBooleanField(obj, field_mAuth); encrypt = env->GetBooleanField(obj, field_mEncrypt); - get_bdaddr_as_string(&addr.rc_bdaddr, addr_cstr); + + get_bdaddr_as_string(bdaddr, addr_cstr); + addr_jstr = env->NewStringUTF(addr_cstr); - return env->NewObject(class_BluetoothSocket, method_BluetoothSocket_ctor, fd, - auth, encrypt, addr_jstr, -1); + return env->NewObject(class_BluetoothSocket, method_BluetoothSocket_ctor, + type, fd, auth, encrypt, addr_jstr, -1); #endif jniThrowIOException(env, ENOSYS); @@ -304,6 +460,8 @@ static void closeNative(JNIEnv *env, jobject obj) { return; asocket_abort(s); + + LOGV("...asocket_abort(%d) complete", s->fd); return; #endif jniThrowIOException(env, ENOSYS); @@ -313,10 +471,14 @@ static void destroyNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); struct asocket *s = get_socketData(env, obj); + int fd = s->fd; + if (!s) return; asocket_destroy(s); + + LOGV("...asocket_destroy(%d) complete", fd); return; #endif jniThrowIOException(env, ENOSYS); @@ -325,8 +487,8 @@ static void destroyNative(JNIEnv *env, jobject obj) { static JNINativeMethod sMethods[] = { {"initSocketNative", "()V", (void*) initSocketNative}, {"initSocketFromFdNative", "(I)V", (void*) initSocketFromFdNative}, - {"connectNative", "(Ljava/lang/String;II)V", (void *) connectNative}, - {"bindListenNative", "(I)V", (void *) bindListenNative}, + {"connectNative", "()V", (void *) connectNative}, + {"bindListenNative", "()V", (void *) bindListenNative}, {"acceptNative", "(I)Landroid/bluetooth/BluetoothSocket;", (void *) acceptNative}, {"availableNative", "()I", (void *) availableNative}, {"readNative", "([BII)I", (void *) readNative}, @@ -340,10 +502,13 @@ int register_android_bluetooth_BluetoothSocket(JNIEnv *env) { if (clazz == NULL) return -1; class_BluetoothSocket = (jclass) env->NewGlobalRef(clazz); + field_mType = env->GetFieldID(clazz, "mType", "I"); + field_mAddress = env->GetFieldID(clazz, "mAddress", "Ljava/lang/String;"); + field_mPort = env->GetFieldID(clazz, "mPort", "I"); field_mAuth = env->GetFieldID(clazz, "mAuth", "Z"); field_mEncrypt = env->GetFieldID(clazz, "mEncrypt", "Z"); field_mSocketData = env->GetFieldID(clazz, "mSocketData", "I"); - method_BluetoothSocket_ctor = env->GetMethodID(clazz, "", "(IZZLjava/lang/String;I)V"); + method_BluetoothSocket_ctor = env->GetMethodID(clazz, "", "(IIZZLjava/lang/String;I)V"); return AndroidRuntime::registerNativeMethods(env, "android/bluetooth/BluetoothSocket", sMethods, NELEM(sMethods)); } -- cgit v1.2.3-59-g8ed1b From 03228fa8f950dfee3f679d280f98d040dea397dc Mon Sep 17 00:00:00 2001 From: johnwang Date: Tue, 2 Jun 2009 14:58:35 -0700 Subject: Allow response payload on RIL_REQUEST error Handle response payload while RIL_REQUEST returns error. modified: gsm/GsmServiceStateTracker.java --- .../java/com/android/internal/telephony/RIL.java | 51 ++++++++++++---------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java index f206d9010810..0cdeefff7b6a 100644 --- a/telephony/java/com/android/internal/telephony/RIL.java +++ b/telephony/java/com/android/internal/telephony/RIL.java @@ -165,7 +165,7 @@ class RILRequest { } void - onError(int error) { + onError(int error, Object ret) { CommandException ex; ex = CommandException.fromRilErrno(error); @@ -175,7 +175,7 @@ class RILRequest { + " error: " + ex); if (mResult != null) { - AsyncResult.forMessage(mResult, null, ex); + AsyncResult.forMessage(mResult, ret, ex); mResult.sendToTarget(); } @@ -290,7 +290,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { s = mSocket; if (s == null) { - rr.onError(RADIO_NOT_AVAILABLE); + rr.onError(RADIO_NOT_AVAILABLE, null); rr.release(); mRequestMessagesPending--; alreadySubtracted = true; @@ -331,7 +331,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { // make sure this request has not already been handled, // eg, if RILReceiver cleared the list. if (req != null || !alreadySubtracted) { - rr.onError(RADIO_NOT_AVAILABLE); + rr.onError(RADIO_NOT_AVAILABLE, null); rr.release(); } } catch (RuntimeException exc) { @@ -340,7 +340,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { // make sure this request has not already been handled, // eg, if RILReceiver cleared the list. if (req != null || !alreadySubtracted) { - rr.onError(GENERIC_FAILURE); + rr.onError(GENERIC_FAILURE, null); rr.release(); } } @@ -545,7 +545,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { synchronized (mRequestsList) { for (int i = 0, sz = mRequestsList.size() ; i < sz ; i++) { RILRequest rr = mRequestsList.get(i); - rr.onError(RADIO_NOT_AVAILABLE); + rr.onError(RADIO_NOT_AVAILABLE, null); rr.release(); } @@ -1986,20 +1986,16 @@ public final class RIL extends BaseCommands implements CommandsInterface { return; } - if (error != 0) { - rr.onError(error); - rr.release(); - return; - } + Object ret = null; - Object ret; - - try {switch (rr.mRequest) { -/* + if (error == 0 || p.dataAvail() > 0) { + // either command succeeds or command fails but with data payload + try {switch (rr.mRequest) { + /* cat libs/telephony/ril_commands.h \ | egrep "^ *{RIL_" \ | sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: ret = \2(p); break;/' -*/ + */ case RIL_REQUEST_GET_SIM_STATUS: ret = responseIccCardStatus(p); break; case RIL_REQUEST_ENTER_SIM_PIN: ret = responseVoid(p); break; case RIL_REQUEST_ENTER_SIM_PUK: ret = responseVoid(p); break; @@ -2104,17 +2100,24 @@ public final class RIL extends BaseCommands implements CommandsInterface { default: throw new RuntimeException("Unrecognized solicited response: " + rr.mRequest); //break; - }} catch (Throwable tr) { - // Exceptions here usually mean invalid RIL responses + }} catch (Throwable tr) { + // Exceptions here usually mean invalid RIL responses - Log.w(LOG_TAG, rr.serialString() + "< " - + requestToString(rr.mRequest) - + " exception, possible invalid RIL response", tr); + Log.w(LOG_TAG, rr.serialString() + "< " + + requestToString(rr.mRequest) + + " exception, possible invalid RIL response", tr); - if (rr.mResult != null) { - AsyncResult.forMessage(rr.mResult, null, tr); - rr.mResult.sendToTarget(); + if (rr.mResult != null) { + AsyncResult.forMessage(rr.mResult, null, tr); + rr.mResult.sendToTarget(); + } + rr.release(); + return; } + } + + if (error != 0) { + rr.onError(error, ret); rr.release(); return; } -- cgit v1.2.3-59-g8ed1b From de15ddc86ce4d3c3acdff297c75ef56f65e10457 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 2 Jun 2009 18:10:08 -0700 Subject: simplify this test --- .../com/android/lightingtest/ClearActivity.java | 181 +++++---------------- 1 file changed, 37 insertions(+), 144 deletions(-) diff --git a/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java index 3dc31cc617e2..3ae8c5ca29d7 100644 --- a/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java +++ b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java @@ -34,8 +34,6 @@ import android.view.MotionEvent; public class ClearActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { - instance = counter++; - Log.e("ClearActivity", ":::::: onCreate: instance" + instance + " is created"); super.onCreate(savedInstanceState); mGLView = new ClearGLSurfaceView(this); setContentView(mGLView); @@ -43,96 +41,37 @@ public class ClearActivity extends Activity { @Override protected void onPause() { - Log.e("ClearActivity", ":::::: instance" + instance + " onPause: is called"); super.onPause(); mGLView.onPause(); } @Override protected void onResume() { - Log.e("ClearActivity", ":::::: instance" + instance + " onResume: is called"); super.onResume(); mGLView.onResume(); } - - @Override - protected void onStop() { - Log.e("ClearActivity", ":::::: instance" + instance + " onStop: is called"); - super.onStop(); - } - - @Override - protected void onDestroy() { - Log.e("ClearActivity", ":::::: instance" + instance + " onDestroy: is called"); - super.onDestroy(); - } - private GLSurfaceView mGLView; - - private static int counter = 0; - private int instance; } class ClearGLSurfaceView extends GLSurfaceView { public ClearGLSurfaceView(Context context) { super(context); - instance = counter++; - Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " is created"); mRenderer = new ClearRenderer(); setRenderer(mRenderer); } - public boolean onTouchEvent(final MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_MOVE: {// falling through on purpose here - Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onTouchEvent: handling down or move action"); - queueEvent(new Runnable(){ - public void run() { - mRenderer.setColor(event.getX() / getWidth(), - event.getY() / getHeight(), 1.0f); - }} - ); - return true; - } - case MotionEvent.ACTION_UP: { - // launch a second instance of the same activity - Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onTouchEvent: handling up action"); - // Intent intent = new Intent(); - // intent.setClass(getContext(), ClearActivity.class); - // getContext().startActivity(intent); - } - - } - return true; - } - - @Override - protected void onDetachedFromWindow() { - Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onDetachedFromWindow: is called"); - super.onDetachedFromWindow(); - } - ClearRenderer mRenderer; - - private static int counter = 0; - private int instance; } class ClearRenderer implements GLSurfaceView.Renderer { public ClearRenderer() { - instance = counter++; - Log.e("ClearRenderer", ":::::: instance" + instance + " is created"); } public void onSurfaceCreated(GL10 gl, EGLConfig config) { // Do nothing special. - Log.e("ClearRenderer", ":::::: instance" + instance + " onSurfaceCreated: is called"); } public void onSurfaceChanged(GL10 gl, int w, int h) { - Log.e("ClearRenderer", ":::::: instance" + instance + " onSurfaceChanged: is called"); - // Compute the projection matrix gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); @@ -153,129 +92,83 @@ class ClearRenderer implements GLSurfaceView.Renderer { } public void onDrawFrame(GL10 gl) { - // gl.glClearColor(mRed, mGreen, mBlue, 1.0f); gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); - float lightOff[] = {0.0f, 0.0f, 0.0f, 1.0f}; - float lightAmbient[] = {5.0f, 0.0f, 0.0f, 1.0f}; - float lightDiffuse[] = {0.0f, 2.0f, 0.0f, 0.0f}; - float lightPosAmbient[] = {0.0f, 0.0f, 0.0f, 1.0f}; - float lightPosSpot[] = {0.0f, 0.0f, -8.0f, 1.0f}; + final float lightOff[] = {0.0f, 0.0f, 0.0f, 1.0f}; + final float lightAmbient[] = {5.0f, 0.0f, 0.0f, 1.0f}; + final float lightDiffuse[] = {0.0f, 2.0f, 0.0f, 0.0f}; + final float lightPosSpot[] = {0.0f, 0.0f, -8.0f, 1.0f}; + final float pos[] = { + -5.0f, -1.5f, 0.0f, + 0.0f, -1.5f, 0.0f, + 5.0f, -1.5f, 0.0f, + }; - float v[] = new float[9]; + final float v[] = new float[9]; ByteBuffer vbb = ByteBuffer.allocateDirect(v.length*4); vbb.order(ByteOrder.nativeOrder()); FloatBuffer vb = vbb.asFloatBuffer(); gl.glDisable(GL10.GL_DITHER); - gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, lightOff, 0); - gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightOff, 0); gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0); - gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosAmbient, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, lightOff, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosSpot, 0); gl.glEnable(GL10.GL_LIGHT0); - - gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_SPECULAR, lightOff, 0); - gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_DIFFUSE, lightDiffuse, 0); - gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_AMBIENT, lightOff, 0); - gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_POSITION, lightPosSpot, 0); - gl.glLightf(GL10.GL_LIGHT1, GL10.GL_CONSTANT_ATTENUATION, 1.0f); - gl.glLightf(GL10.GL_LIGHT1, GL10.GL_LINEAR_ATTENUATION, 0.0f); - gl.glLightf(GL10.GL_LIGHT1, GL10.GL_QUADRATIC_ATTENUATION, 0.022f); - gl.glEnable(GL10.GL_LIGHT1); - + gl.glEnable(GL10.GL_LIGHTING); - // draw upper left triangle - gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); - v[0] = -6f; v[1] = 0.5f; v[2] = -10f; - v[3] = -5f; v[4] = 2.5f; v[5] = -10f; - v[6] = -4f; v[7] = 0.5f; v[8] = -10f; - vb.put(v).position(0); - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); - gl.glNormal3f(0, 0, 1); - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); - // draw upper middle triangle gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); - v[0] = -1f; v[1] = 0.5f; v[2] = -10f; - v[3] = 0f; v[4] = 2.5f; v[5] = -10f; - v[6] = 1f; v[7] = 0.5f; v[8] = -10f; - vb.put(v).position(0); - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); gl.glNormal3f(0, 0, 1); - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + - // draw upper right triangle - gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); - v[0] = 4f; v[1] = 0.5f; v[2] = -10f; - v[3] = 5f; v[4] = 2.5f; v[5] = -10f; - v[6] = 6f; v[7] = 0.5f; v[8] = -10f; + // draw first 3 triangles, without using transforms + for (int i=0 ; i<3 ; i++) { + v[0] = -1; v[1] =-1; v[2] = -10; + v[3] = 0; v[4] = 1; v[5] = -10; + v[6] = 1; v[7] =-1; v[8] = -10; + for (int j=0 ; j<3 ; j++) { + v[j*3+0] -= pos[i*3+0]; + v[j*3+1] -= pos[i*3+1]; + v[j*3+2] -= pos[i*3+2]; + } + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + } + + // draw the 2nd batch this time with transforms + v[0] = -1; v[1] =-1; v[2] = -10; + v[3] = 0; v[4] = 1; v[5] = -10; + v[6] = 1; v[7] =-1; v[8] = -10; vb.put(v).position(0); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); - gl.glNormal3f(0, 0, 1); - gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); // draw lower left triangle gl.glPushMatrix(); - gl.glTranslatef(-5.0f, -1.5f, 0.0f); - gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); - v[0] = -1; v[1] = -1; v[2] = -10; - v[3] = 0; v[4] = 1; v[5] = -10; - v[6] = 1; v[7] = -1; v[8] = -10; - vb.put(v).position(0); - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); - gl.glNormal3f(0, 0, 1); + gl.glTranslatef(pos[0], pos[1], pos[2]); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); gl.glPopMatrix(); // draw lower middle triangle gl.glPushMatrix(); - gl.glTranslatef(0.0f, -1.5f, 0.0f); - gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); - v[0] = -1; v[1] = -1; v[2] = -10; - v[3] = 0; v[4] = 1; v[5] = -10; - v[6] = 1; v[7] = -1; v[8] = -10; - vb.put(v).position(0); - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); - gl.glNormal3f(0, 0, 1); + gl.glTranslatef(pos[3], pos[4], pos[5]); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); gl.glPopMatrix(); // draw lower right triangle gl.glPushMatrix(); - gl.glTranslatef(5.0f, -1.5f, 0.0f); - gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); - v[0] = -1; v[1] = -1; v[2] = -10; - v[3] = 0; v[4] = 1; v[5] = -10; - v[6] = 1; v[7] = -1; v[8] = -10; - vb.put(v).position(0); - gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); - gl.glNormal3f(0, 0, 1); + gl.glTranslatef(pos[6], pos[7], pos[8]); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); gl.glPopMatrix(); - } public int[] getConfigSpec() { - Log.e("ClearRenderer", ":::::: instance" + instance + " getConfigSpec: is called"); int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE }; return configSpec; } - - public void setColor(float r, float g, float b) { - Log.e("ClearRenderer", ":::::: instance" + instance + " setColor: is called"); - mRed = r; - mGreen = g; - mBlue = b; - } - - private float mRed; - private float mGreen; - private float mBlue; - - private static int counter = 0; - private int instance; } -- cgit v1.2.3-59-g8ed1b From 9af0b4f7be14f2b3ed0ecc843c57ea47ec288e55 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Tue, 2 Jun 2009 21:56:27 -0700 Subject: Add new listener to GestureOverlayView. This listener fires whenever the overlay thinks the user is starting a new gesture. This allows Home to snap the workspace back to its original position during a gesture operation. --- api/current.xml | 83 +++++++++++++++++++++-- core/java/android/gesture/GestureOverlayView.java | 47 +++++++++++-- 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/api/current.xml b/api/current.xml index 3a310347188c..9e9eaad45e0e 100644 --- a/api/current.xml +++ b/api/current.xml @@ -1145,33 +1145,33 @@ visibility="public" > - - - + + + + + + + + + + + + + + + + + + + + mOnGesturePerformedListeners = new ArrayList(); + // TODO: Make this a list of WeakReferences + private final ArrayList mOnGesturingListeners = + new ArrayList(); private boolean mHandleGestureActions; @@ -319,6 +322,18 @@ public class GestureOverlayView extends FrameLayout { mHandleGestureActions = false; } + public void addOnGesturingListener(OnGesturingListener listener) { + mOnGesturingListeners.add(listener); + } + + public void removeOnGesturingListener(OnGesturingListener listener) { + mOnGesturingListeners.remove(listener); + } + + public void removeAllOnGesturingListeners() { + mOnGesturingListeners.clear(); + } + public boolean isGesturing() { return mIsGesturing; } @@ -401,7 +416,7 @@ public class GestureOverlayView extends FrameLayout { MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); final ArrayList listeners = mOnGestureListeners; - final int count = listeners.size(); + int count = listeners.size(); for (int i = 0; i < count; i++) { listeners.get(i).onGestureCancelled(this, event); } @@ -411,6 +426,12 @@ public class GestureOverlayView extends FrameLayout { clear(false); mIsGesturing = false; mStrokeBuffer.clear(); + + final ArrayList otherListeners = mOnGesturingListeners; + count = otherListeners.size(); + for (int i = 0; i < count; i++) { + otherListeners.get(i).onGesturingEnded(this); + } } @Override @@ -577,6 +598,12 @@ public class GestureOverlayView extends FrameLayout { mIsGesturing = true; setCurrentColor(mCertainGestureColor); + + final ArrayList listeners = mOnGesturingListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGesturingStarted(this); + } } } } @@ -621,6 +648,12 @@ public class GestureOverlayView extends FrameLayout { mStrokeBuffer.clear(); mIsGesturing = false; + + final ArrayList listeners = mOnGesturingListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGesturingEnded(this); + } } private void cancelGesture(MotionEvent event) { @@ -635,12 +668,10 @@ public class GestureOverlayView extends FrameLayout { } private void fireOnGesturePerformed() { - final ArrayList actionListeners = - mOnGesturePerformedListeners; + final ArrayList actionListeners = mOnGesturePerformedListeners; final int count = actionListeners.size(); for (int i = 0; i < count; i++) { - actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, - mCurrentGesture); + actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture); } } @@ -683,6 +714,12 @@ public class GestureOverlayView extends FrameLayout { } } + public static interface OnGesturingListener { + void onGesturingStarted(GestureOverlayView overlay); + + void onGesturingEnded(GestureOverlayView overlay); + } + public static interface OnGestureListener { void onGestureStarted(GestureOverlayView overlay, MotionEvent event); -- cgit v1.2.3-59-g8ed1b From 7c7dface993a66778c506179ae11cadd6a88f7b1 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 2 Jun 2009 22:51:09 -0700 Subject: fix a bug in GL lighting where the specular component could be ommited when vertex material was disabled. the specular enable flag wasn't computed in that case. --- opengl/libagl/light.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opengl/libagl/light.cpp b/opengl/libagl/light.cpp index 25c41d0c5b9d..bc9449c0bdef 100644 --- a/opengl/libagl/light.cpp +++ b/opengl/libagl/light.cpp @@ -318,6 +318,11 @@ void lightVertexMaterial(ogles_context_t* c, vertex_t* v) vmul3(l.implicitAmbient.v, material.ambient.v, l.ambient.v); vmul3(l.implicitDiffuse.v, material.diffuse.v, l.diffuse.v); vmul3(l.implicitSpecular.v, material.specular.v, l.specular.v); + // this is just a flag to tell if we have a specular component + l.implicitSpecular.v[3] = + l.implicitSpecular.r | + l.implicitSpecular.g | + l.implicitSpecular.b; } // emission and ambient for the whole scene vmla3( c->lighting.implicitSceneEmissionAndAmbient.v, -- cgit v1.2.3-59-g8ed1b From 8dbe612dc60526d635e57257b58627b33a099678 Mon Sep 17 00:00:00 2001 From: Satish Sampath Date: Tue, 2 Jun 2009 23:35:54 +0100 Subject: Adding internal method replacePreferredActivity. This was required because we need a way to set the preferred activity for a particular intent filter based on user selection (in our case the ACTION_WEB_SEARCH intent filter for selecting the preferred search engine from the list of available search engines providers). The current addPreferredActivity call was not sufficient since it leaves the existing preferred activities in the list and does not remove them, which this call does. --- core/java/android/app/ApplicationContext.java | 10 ++++++ core/java/android/content/pm/IPackageManager.aidl | 5 +++ core/java/android/content/pm/PackageManager.java | 20 ++++++++++++ .../com/android/server/PackageManagerService.java | 37 ++++++++++++++++++++++ .../android/test/mock/MockPackageManager.java | 10 ++++++ 5 files changed, 82 insertions(+) diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index 2c2310a030d4..2d6381a632df 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -2424,6 +2424,16 @@ class ApplicationContext extends Context { } } + @Override + public void replacePreferredActivity(IntentFilter filter, + int match, ComponentName[] set, ComponentName activity) { + try { + mPM.replacePreferredActivity(filter, match, set, activity); + } catch (RemoteException e) { + // Should never happen! + } + } + @Override public void clearPackagePreferredActivities(String packageName) { try { diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index bb913cdca90c..5f62248bcd14 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -167,7 +167,12 @@ interface IPackageManager { void addPreferredActivity(in IntentFilter filter, int match, in ComponentName[] set, in ComponentName activity); + + void replacePreferredActivity(in IntentFilter filter, int match, + in ComponentName[] set, in ComponentName activity); + void clearPackagePreferredActivities(String packageName); + int getPreferredActivities(out List outFilters, out List outActivities, String packageName); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 238a98afd683..a2c82e890e41 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1629,6 +1629,26 @@ public abstract class PackageManager { public abstract void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity); + /** + * Replaces an existing preferred activity mapping to the system, and if that were not present + * adds a new preferred activity. This will be used + * to automatically select the given activity component when + * {@link Context#startActivity(Intent) Context.startActivity()} finds + * multiple matching activities and also matches the given filter. + * + * @param filter The set of intents under which this activity will be + * made preferred. + * @param match The IntentFilter match category that this preference + * applies to. + * @param set The set of activities that the user was picking from when + * this preference was made. + * @param activity The component name of the activity that is to be + * preferred. + * @hide + */ + public abstract void replacePreferredActivity(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity); + /** * Remove all preferred activity mappings, previously added with * {@link #addPreferredActivity}, from the diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index c9bdd3c7ebef..8da40acf9372 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -19,6 +19,7 @@ package com.android.server; import com.android.internal.app.ResolverActivity; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; +import com.android.server.PackageManagerService.PreferredActivity; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -4507,6 +4508,42 @@ class PackageManagerService extends IPackageManager.Stub { } } + public void replacePreferredActivity(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null); + if (filter.countActions() != 1) { + throw new IllegalArgumentException( + "replacePreferredActivity expects filter to have only 1 action."); + } + if (filter.countCategories() != 1) { + throw new IllegalArgumentException( + "replacePreferredActivity expects filter to have only 1 category."); + } + if (filter.countDataAuthorities() != 0 + || filter.countDataPaths() != 0 + || filter.countDataSchemes() != 0 + || filter.countDataTypes() != 0) { + throw new IllegalArgumentException( + "replacePreferredActivity expects filter to have no data authorities, " + + "paths, schemes or types."); + } + synchronized (mPackages) { + Iterator it = mSettings.mPreferredActivities.filterIterator(); + String action = filter.getAction(0); + String category = filter.getCategory(0); + while (it.hasNext()) { + PreferredActivity pa = it.next(); + if (pa.getAction(0).equals(action) && pa.getCategory(0).equals(category)) { + it.remove(); + Log.i(TAG, "Removed preferred activity " + pa.mActivity + ":"); + filter.dump(new LogPrinter(Log.INFO, TAG), " "); + } + } + addPreferredActivity(filter, match, set, activity); + } + } + public void clearPackagePreferredActivities(String packageName) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null); diff --git a/test-runner/android/test/mock/MockPackageManager.java b/test-runner/android/test/mock/MockPackageManager.java index 73ae3b91308e..6ef5539ad0c8 100644 --- a/test-runner/android/test/mock/MockPackageManager.java +++ b/test-runner/android/test/mock/MockPackageManager.java @@ -392,6 +392,16 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** + * @hide - to match hiding in superclass + */ + @Override + public void replacePreferredActivity(IntentFilter filter, + int match, ComponentName[] set, ComponentName activity) { + throw new UnsupportedOperationException(); + } + + @Override public void clearPackagePreferredActivities(String packageName) { throw new UnsupportedOperationException(); -- cgit v1.2.3-59-g8ed1b From 963cd006c45716b034f656bf7e7179e6476f7e4d Mon Sep 17 00:00:00 2001 From: Bjorn Bringert Date: Fri, 29 May 2009 14:05:12 +0100 Subject: Allow creating AssetFileDescriptors for MemoryFiles. This allows content providers to use in-memory data to implement ContentProvider.openAssetFile(), instead of just normal files and sockets as before. To test cross-process use of AssetFileDescriptors for MemoryFiles, a test content provider and a client for it are added to AndroidTests. Fixes http://b/issue?id=1871731 --- .../android/content/res/AssetFileDescriptor.java | 88 +++++++++ core/java/android/os/MemoryFile.java | 117 +++++++++++- core/jni/android_os_MemoryFile.cpp | 30 ++- tests/AndroidTests/AndroidManifest.xml | 6 + .../unit_tests/content/MemoryFileProvider.java | 211 +++++++++++++++++++++ .../unit_tests/content/MemoryFileProviderTest.java | 83 ++++++++ .../com/android/unit_tests/os/MemoryFileTest.java | 52 ++++- 7 files changed, 575 insertions(+), 12 deletions(-) create mode 100644 tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProvider.java create mode 100644 tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java index 231e3e24a27c..a37e4e8cc3bf 100644 --- a/core/java/android/content/res/AssetFileDescriptor.java +++ b/core/java/android/content/res/AssetFileDescriptor.java @@ -16,6 +16,7 @@ package android.content.res; +import android.os.MemoryFile; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; @@ -24,6 +25,8 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.FileChannel; /** * File descriptor of an entry in the AssetManager. This provides your own @@ -123,6 +126,13 @@ public class AssetFileDescriptor implements Parcelable { mFd.close(); } + /** + * Checks whether this file descriptor is for a memory file. + */ + private boolean isMemoryFile() throws IOException { + return MemoryFile.isMemoryFile(mFd.getFileDescriptor()); + } + /** * Create and return a new auto-close input stream for this asset. This * will either return a full asset {@link AutoCloseInputStream}, or @@ -132,6 +142,12 @@ public class AssetFileDescriptor implements Parcelable { * should only call this once for a particular asset. */ public FileInputStream createInputStream() throws IOException { + if (isMemoryFile()) { + if (mLength > Integer.MAX_VALUE) { + throw new IOException("File length too large for a memory file: " + mLength); + } + return new AutoCloseMemoryFileInputStream(mFd, (int)mLength); + } if (mLength < 0) { return new ParcelFileDescriptor.AutoCloseInputStream(mFd); } @@ -261,6 +277,66 @@ public class AssetFileDescriptor implements Parcelable { } } + /** + * An input stream that reads from a MemoryFile and closes it when the stream is closed. + * This extends FileInputStream just because {@link #createInputStream} returns + * a FileInputStream. All the FileInputStream methods are + * overridden to use the MemoryFile instead. + */ + private static class AutoCloseMemoryFileInputStream extends FileInputStream { + private ParcelFileDescriptor mParcelFd; + private MemoryFile mFile; + private InputStream mStream; + + public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length) + throws IOException { + super(fd.getFileDescriptor()); + mParcelFd = fd; + mFile = new MemoryFile(fd.getFileDescriptor(), length, "r"); + mStream = mFile.getInputStream(); + } + + @Override + public int available() throws IOException { + return mStream.available(); + } + + @Override + public void close() throws IOException { + mParcelFd.close(); // must close ParcelFileDescriptor, not just the file descriptor, + // since it could be a subclass of ParcelFileDescriptor. + // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases + // a content provider + mFile.close(); // to unmap the memory file from the address space. + mStream.close(); // doesn't actually do anything + } + + @Override + public FileChannel getChannel() { + return null; + } + + @Override + public int read() throws IOException { + return mStream.read(); + } + + @Override + public int read(byte[] buffer, int offset, int count) throws IOException { + return mStream.read(buffer, offset, count); + } + + @Override + public int read(byte[] buffer) throws IOException { + return mStream.read(buffer); + } + + @Override + public long skip(long count) throws IOException { + return mStream.skip(count); + } + } + /** * An OutputStream you can create on a ParcelFileDescriptor, which will * take care of calling {@link ParcelFileDescriptor#close @@ -345,4 +421,16 @@ public class AssetFileDescriptor implements Parcelable { return new AssetFileDescriptor[size]; } }; + + /** + * Creates an AssetFileDescriptor from a memory file. + * + * @hide + */ + public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile) + throws IOException { + ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor(); + return new AssetFileDescriptor(fd, 0, memoryFile.length()); + } + } diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java index 65e83c77957a..7e4cf8ae3284 100644 --- a/core/java/android/os/MemoryFile.java +++ b/core/java/android/os/MemoryFile.java @@ -37,9 +37,14 @@ public class MemoryFile { private static String TAG = "MemoryFile"; + // mmap(2) protection flags from + private static final int PROT_READ = 0x1; + private static final int PROT_WRITE = 0x2; + private static native FileDescriptor native_open(String name, int length) throws IOException; // returns memory address for ashmem region - private static native int native_mmap(FileDescriptor fd, int length) throws IOException; + private static native int native_mmap(FileDescriptor fd, int length, int mode) + throws IOException; private static native void native_munmap(int addr, int length) throws IOException; private static native void native_close(FileDescriptor fd); private static native int native_read(FileDescriptor fd, int address, byte[] buffer, @@ -47,14 +52,16 @@ public class MemoryFile private static native void native_write(FileDescriptor fd, int address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; + private static native boolean native_is_ashmem_region(FileDescriptor fd) throws IOException; private FileDescriptor mFD; // ashmem file descriptor private int mAddress; // address of ashmem memory private int mLength; // total length of our ashmem region private boolean mAllowPurging = false; // true if our ashmem region is unpinned + private final boolean mOwnsRegion; // false if this is a ref to an existing ashmem region /** - * MemoryFile constructor. + * Allocates a new ashmem region. The region is initially not purgable. * * @param name optional name for the file (can be null). * @param length of the memory file in bytes. @@ -63,11 +70,43 @@ public class MemoryFile public MemoryFile(String name, int length) throws IOException { mLength = length; mFD = native_open(name, length); - mAddress = native_mmap(mFD, length); + mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); + mOwnsRegion = true; + } + + /** + * Creates a reference to an existing memory file. Changes to the original file + * will be available through this reference. + * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail. + * + * @param fd File descriptor for an existing memory file, as returned by + * {@link #getFileDescriptor()}. This file descriptor will be closed + * by {@link #close()}. + * @param length Length of the memory file in bytes. + * @param mode File mode. Currently only "r" for read-only access is supported. + * @throws NullPointerException if fd is null. + * @throws IOException If fd does not refer to an existing memory file, + * or if the file mode of the existing memory file is more restrictive + * than mode. + * + * @hide + */ + public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException { + if (fd == null) { + throw new NullPointerException("File descriptor is null."); + } + if (!isMemoryFile(fd)) { + throw new IllegalArgumentException("Not a memory file."); + } + mLength = length; + mFD = fd; + mAddress = native_mmap(mFD, length, modeToProt(mode)); + mOwnsRegion = false; } /** - * Closes and releases all resources for the memory file. + * Closes the memory file. If there are no other open references to the memory + * file, it will be deleted. */ public void close() { deactivate(); @@ -76,7 +115,14 @@ public class MemoryFile } } - private void deactivate() { + /** + * Unmaps the memory file from the process's memory space, but does not close it. + * After this method has been called, read and write operations through this object + * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor. + * + * @hide + */ + public void deactivate() { if (!isDeactivated()) { try { native_munmap(mAddress, mLength); @@ -135,6 +181,9 @@ public class MemoryFile * @return previous value of allowPurging */ synchronized public boolean allowPurging(boolean allowPurging) throws IOException { + if (!mOwnsRegion) { + throw new IOException("Only the owner can make ashmem regions purgable."); + } boolean oldValue = mAllowPurging; if (oldValue != allowPurging) { native_pin(mFD, !allowPurging); @@ -210,6 +259,64 @@ public class MemoryFile native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); } + /** + * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()} + * for caveats. This must be here to allow classes outside android.osfd is not a valid file descriptor. + * + * @hide + */ + public static boolean isMemoryFile(FileDescriptor fd) throws IOException { + return native_is_ashmem_region(fd); + } + + /** + * Converts a file mode string to a prot value as expected by + * native_mmap(). + * + * @throws IllegalArgumentException if the file mode is invalid. + */ + private static int modeToProt(String mode) { + if ("r".equals(mode)) { + return PROT_READ; + } else { + throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'"); + } + } + private class MemoryInputStream extends InputStream { private int mMark = 0; diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp index 6c16150a4cc8..8643393f7e8a 100644 --- a/core/jni/android_os_MemoryFile.cpp +++ b/core/jni/android_os_MemoryFile.cpp @@ -39,17 +39,17 @@ static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring na if (result < 0) { jniThrowException(env, "java/io/IOException", "ashmem_create_region failed"); - return NULL; + return NULL; } return jniCreateFileDescriptor(env, result); } static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, - jint length) + jint length, jint prot) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - jint result = (jint)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0); if (!result) jniThrowException(env, "java/io/IOException", "mmap failed"); return result; @@ -118,14 +118,36 @@ static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDe } } +static jboolean android_os_MemoryFile_is_ashmem_region(JNIEnv* env, jobject clazz, + jobject fileDescriptor) { + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region. + // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel + // should return ENOTTY for all other valid file descriptors + int result = ashmem_get_size_region(fd); + if (result < 0) { + if (errno == ENOTTY) { + // ENOTTY means that the ioctl does not apply to this object, + // i.e., it is not an ashmem region. + return JNI_FALSE; + } + // Some other error, throw exception + jniThrowIOException(env, errno); + return JNI_FALSE; + } + return JNI_TRUE; +} + static const JNINativeMethod methods[] = { {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, - {"native_mmap", "(Ljava/io/FileDescriptor;I)I", (void*)android_os_MemoryFile_mmap}, + {"native_mmap", "(Ljava/io/FileDescriptor;II)I", (void*)android_os_MemoryFile_mmap}, {"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap}, {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, {"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read}, {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write}, {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, + {"native_is_ashmem_region", "(Ljava/io/FileDescriptor;)Z", + (void*)android_os_MemoryFile_is_ashmem_region} }; static const char* const kClassPathName = "android/os/MemoryFile"; diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml index 843d844379ae..fd6e6d8ec3d6 100644 --- a/tests/AndroidTests/AndroidManifest.xml +++ b/tests/AndroidTests/AndroidManifest.xml @@ -206,6 +206,12 @@ + + + + Date: Mon, 1 Jun 2009 10:53:06 +0100 Subject: Handle EOF correctly in MemoryFile input stream. Before, the variants of MemoryFile.MemoryInputStream.read() would throw IOException or IndexOutOfBoundsException if EOF was encountered before the requested number of bytes was read. This violates the contract of InputStream.read(). This patch makes read() return the number of bytes available, if any. If already at EOF, -1 is returned. The patch also adds new tests, which checks cases where MemoryFile.MemoryInputStream.read() should throw IndexOutOfBoundsException or return -1. several of these tests failed with the old code and pass now. This fixes http://b/issue?id=1881894 --- core/java/android/os/MemoryFile.java | 11 +++- .../unit_tests/content/MemoryFileProviderTest.java | 3 +- .../com/android/unit_tests/os/MemoryFileTest.java | 69 ++++++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java index 7e4cf8ae3284..c14925cd3839 100644 --- a/core/java/android/os/MemoryFile.java +++ b/core/java/android/os/MemoryFile.java @@ -353,13 +353,22 @@ public class MemoryFile } int result = read(mSingleByte, 0, 1); if (result != 1) { - throw new IOException("read() failed"); + return -1; } return mSingleByte[0]; } @Override public int read(byte buffer[], int offset, int count) throws IOException { + if (offset < 0 || count < 0 || offset + count > buffer.length) { + // readBytes() also does this check, but we need to do it before + // changing count. + throw new IndexOutOfBoundsException(); + } + count = Math.min(count, available()); + if (count < 1) { + return -1; + } int result = readBytes(buffer, mOffset, offset, count); if (result > 0) { mOffset += result; diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java index 2d8190a71b29..f88a9dac3962 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java @@ -40,8 +40,7 @@ public class MemoryFileProviderTest extends AndroidTestCase { assertNotNull(in); int count = in.read(buf); assertEquals(buf.length, count); - // TODO: MemoryFile throws IndexOutOfBoundsException for this, http://b/issue?id=1881894 - //assertEquals(-1, in.read()); + assertEquals(-1, in.read()); in.close(); assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf)); } diff --git a/tests/AndroidTests/src/com/android/unit_tests/os/MemoryFileTest.java b/tests/AndroidTests/src/com/android/unit_tests/os/MemoryFileTest.java index 66f2b508716c..18b3d6321e83 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/os/MemoryFileTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/os/MemoryFileTest.java @@ -97,6 +97,74 @@ public class MemoryFileTest extends AndroidTestCase { file.close(); } + // Tests for the IndexOutOfBoundsException cases in read(). + + private void readIndexOutOfBoundsException(int offset, int count, String msg) + throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", testString.length); + try { + file.writeBytes(testString, 0, 0, testString.length); + InputStream is = file.getInputStream(); + byte[] buffer = new byte[testString.length + 10]; + try { + is.read(buffer, offset, count); + fail(msg); + } catch (IndexOutOfBoundsException ex) { + // this is what should happen + } finally { + is.close(); + } + } finally { + file.close(); + } + } + + @SmallTest + public void testReadNegativeOffset() throws Exception { + readIndexOutOfBoundsException(-1, 5, + "read() with negative offset should throw IndexOutOfBoundsException"); + } + + @SmallTest + public void testReadNegativeCount() throws Exception { + readIndexOutOfBoundsException(5, -1, + "read() with negative length should throw IndexOutOfBoundsException"); + } + + @SmallTest + public void testReadOffsetOverflow() throws Exception { + readIndexOutOfBoundsException(testString.length + 10, 5, + "read() with offset outside buffer should throw IndexOutOfBoundsException"); + } + + @SmallTest + public void testReadOffsetCountOverflow() throws Exception { + readIndexOutOfBoundsException(testString.length, 11, + "read() with offset + count outside buffer should throw IndexOutOfBoundsException"); + } + + // Test behavior of read() at end of file + @SmallTest + public void testReadEOF() throws Exception { + MemoryFile file = new MemoryFile("MemoryFileTest", testString.length); + try { + file.writeBytes(testString, 0, 0, testString.length); + InputStream is = file.getInputStream(); + try { + byte[] buffer = new byte[testString.length + 10]; + // read() with count larger than data should succeed, and return # of bytes read + assertEquals(testString.length, is.read(buffer)); + compareBuffers(testString, buffer, testString.length); + // Read at EOF should return -1 + assertEquals(-1, is.read()); + } finally { + is.close(); + } + } finally { + file.close(); + } + } + // Tests that close() is idempotent @SmallTest public void testCloseClose() throws Exception { @@ -194,6 +262,7 @@ public class MemoryFileTest extends AndroidTestCase { } } + @SmallTest public void testFileDescriptor() throws Exception { MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); MemoryFile ref = new MemoryFile(file.getFileDescriptor(), file.length(), "r"); -- cgit v1.2.3-59-g8ed1b From c55e08e3fc14475b3b15b4c87aaef7e47ec8a172 Mon Sep 17 00:00:00 2001 From: Bjorn Bringert Date: Mon, 1 Jun 2009 21:50:09 +0100 Subject: Allow making AssetFileDescriptors from SQLite blobs. This change adds a new class SQLiteContentHelper, which contains a static method for creating an AssetFileDescriptor from an SQLite query that returns a blob. Internally, this uses a file descriptor for a MemoryFile. The implementation is temporary. Ideally, the data should be copied directly from SQLite to the MemoryFile ashmem region, using sqlite3_blob_read(). This is part of the implementation of http://b/issue?id=1871731 --- .../database/sqlite/SQLiteContentHelper.java | 92 ++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 core/java/android/database/sqlite/SQLiteContentHelper.java diff --git a/core/java/android/database/sqlite/SQLiteContentHelper.java b/core/java/android/database/sqlite/SQLiteContentHelper.java new file mode 100644 index 000000000000..2800d86279b1 --- /dev/null +++ b/core/java/android/database/sqlite/SQLiteContentHelper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.database.sqlite; + +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.os.MemoryFile; + +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * Some helper functions for using SQLite database to implement content providers. + * + * @hide + */ +public class SQLiteContentHelper { + + /** + * Runs an SQLite query and returns an AssetFileDescriptor for the + * blob in column 0 of the first row. If the first column does + * not contain a blob, an unspecified exception is thrown. + * + * @param db Handle to a readable database. + * @param sql SQL query, possibly with query arguments. + * @param selectionArgs Query argument values, or {@code null} for no argument. + * @return If no exception is thrown, a non-null AssetFileDescriptor is returned. + * @throws FileNotFoundException If the query returns no results or the + * value of column 0 is NULL, or if there is an error creating the + * asset file descriptor. + */ + public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, String sql, + String[] selectionArgs) throws FileNotFoundException { + try { + MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs); + if (file == null) { + throw new FileNotFoundException("No results."); + } + return AssetFileDescriptor.fromMemoryFile(file); + } catch (IOException ex) { + throw new FileNotFoundException(ex.toString()); + } + } + + /** + * Runs an SQLite query and returns a MemoryFile for the + * blob in column 0 of the first row. If the first column does + * not contain a blob, an unspecified exception is thrown. + * + * @return A memory file, or {@code null} if the query returns no results + * or the value column 0 is NULL. + * @throws IOException If there is an error creating the memory file. + */ + // TODO: make this native and use the SQLite blob API to reduce copying + private static MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql, + String[] selectionArgs) throws IOException { + Cursor cursor = db.rawQuery(sql, selectionArgs); + if (cursor == null) { + return null; + } + try { + if (!cursor.moveToFirst()) { + return null; + } + byte[] bytes = cursor.getBlob(0); + if (bytes == null) { + return null; + } + MemoryFile file = new MemoryFile(null, bytes.length); + file.writeBytes(bytes, 0, 0, bytes.length); + file.deactivate(); + return file; + } finally { + cursor.close(); + } + } + +} -- cgit v1.2.3-59-g8ed1b From 33a22dc9c84ef12006b0c12f6d169d2a74c15284 Mon Sep 17 00:00:00 2001 From: Bjorn Bringert Date: Tue, 2 Jun 2009 16:59:54 +0100 Subject: Close icon input stream in SuggestionsAdapter. Before, SuggestionsAdapter would not close input streams after reading icons from them. This leaks file descriptors and, in the case of MemoryFiles, virtual address space. --- core/java/android/app/SuggestionsAdapter.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java index 451697a43723..747bec9944dc 100644 --- a/core/java/android/app/SuggestionsAdapter.java +++ b/core/java/android/app/SuggestionsAdapter.java @@ -32,12 +32,13 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.widget.CursorAdapter; import android.widget.ImageView; import android.widget.ResourceCursorAdapter; import android.widget.TextView; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.WeakHashMap; /** @@ -376,9 +377,18 @@ class SuggestionsAdapter extends ResourceCursorAdapter { // Let the ContentResolver handle content, android.resource and file URIs. try { Uri uri = Uri.parse(drawableId); - drawable = Drawable.createFromStream( - mProviderContext.getContentResolver().openInputStream(uri), - null); + InputStream stream = mProviderContext.getContentResolver().openInputStream(uri); + if (stream != null) { + try { + drawable = Drawable.createFromStream(stream, null); + } finally { + try { + stream.close(); + } catch (IOException ex) { + Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex); + } + } + } if (DBG) Log.d(LOG_TAG, "Opened icon input stream: " + drawableId); } catch (FileNotFoundException fnfe) { if (DBG) Log.d(LOG_TAG, "Icon stream not found: " + drawableId); -- cgit v1.2.3-59-g8ed1b From 2e5c150e746647a1ce5c10e1708debbf06c45ea7 Mon Sep 17 00:00:00 2001 From: Derek Sollenberger Date: Wed, 3 Jun 2009 10:44:42 -0400 Subject: Centralized debug flags and enabled more granular control of debug settings. --- core/java/android/webkit/BrowserFrame.java | 8 +-- core/java/android/webkit/CacheManager.java | 4 +- core/java/android/webkit/CallbackProxy.java | 2 +- core/java/android/webkit/CookieManager.java | 12 ++-- core/java/android/webkit/CookieSyncManager.java | 4 +- core/java/android/webkit/DebugFlags.java | 48 ++++++++++++++++ core/java/android/webkit/FrameLoader.java | 8 +-- core/java/android/webkit/JWebCoreJavaBridge.java | 4 +- core/java/android/webkit/LoadListener.java | 36 ++++++------ core/java/android/webkit/Network.java | 14 ++--- core/java/android/webkit/SslErrorHandler.java | 12 ++-- core/java/android/webkit/StreamLoader.java | 2 +- core/java/android/webkit/URLUtil.java | 2 +- core/java/android/webkit/WebBackForwardList.java | 2 +- core/java/android/webkit/WebSettings.java | 2 +- core/java/android/webkit/WebSyncManager.java | 10 ++-- core/java/android/webkit/WebView.java | 70 ++++++++++++------------ core/java/android/webkit/WebViewCore.java | 22 ++++---- 18 files changed, 153 insertions(+), 109 deletions(-) create mode 100644 core/java/android/webkit/DebugFlags.java diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 08ca209fa9e2..e04ae720a6da 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -121,7 +121,7 @@ class BrowserFrame extends Handler { mDatabase = WebViewDatabase.getInstance(context); mWebViewCore = w; - if (WebView.LOGV_ENABLED) { + if (DebugFlags.BROWSER_FRAME) { Log.v(LOGTAG, "BrowserFrame constructor: this=" + this); } } @@ -343,7 +343,7 @@ class BrowserFrame extends Handler { switch (msg.what) { case FRAME_COMPLETED: { if (mSettings.getSavePassword() && hasPasswordField()) { - if (WebView.DEBUG) { + if (DebugFlags.BROWSER_FRAME) { Assert.assertNotNull(mCallbackProxy.getBackForwardList() .getCurrentItem()); } @@ -492,7 +492,7 @@ class BrowserFrame extends Handler { } if (mSettings.getSavePassword() && hasPasswordField()) { try { - if (WebView.DEBUG) { + if (DebugFlags.BROWSER_FRAME) { Assert.assertNotNull(mCallbackProxy.getBackForwardList() .getCurrentItem()); } @@ -540,7 +540,7 @@ class BrowserFrame extends Handler { // is this resource the main-frame top-level page? boolean isMainFramePage = mIsMainFrame; - if (WebView.LOGV_ENABLED) { + if (DebugFlags.BROWSER_FRAME) { Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method=" + method + ", postData=" + postData + ", isHighPriority=" + isHighPriority + ", isMainFramePage=" + isMainFramePage); diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index 0095b91f250b..4d471f7f6789 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -320,7 +320,7 @@ public final class CacheManager { } } - if (WebView.LOGV_ENABLED) { + if (DebugFlags.CACHE_MANAGER) { Log.v(LOGTAG, "getCacheFile for url " + url); } @@ -422,7 +422,7 @@ public final class CacheManager { mDataBase.addCache(url, cacheRet); - if (WebView.LOGV_ENABLED) { + if (DebugFlags.CACHE_MANAGER) { Log.v(LOGTAG, "saveCacheFile for url " + url); } } diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 667cb2c61e07..2fb696456ced 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -852,7 +852,7 @@ class CallbackProxy extends Handler { String password, Message resumeMsg) { // resumeMsg should be null at this point because we want to create it // within the CallbackProxy. - if (WebView.DEBUG) { + if (DebugFlags.CALLBACK_PROXY) { junit.framework.Assert.assertNull(resumeMsg); } resumeMsg = obtainMessage(NOTIFY); diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index e8c22798bd55..7b9172487876 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -262,7 +262,7 @@ public final class CookieManager { if (!mAcceptCookie || uri == null) { return; } - if (WebView.LOGV_ENABLED) { + if (DebugFlags.COOKIE_MANAGER) { Log.v(LOGTAG, "setCookie: uri: " + uri + " value: " + value); } @@ -427,12 +427,12 @@ public final class CookieManager { } } if (ret.length() > 0) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.COOKIE_MANAGER) { Log.v(LOGTAG, "getCookie: uri: " + uri + " value: " + ret); } return ret.toString(); } else { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.COOKIE_MANAGER) { Log.v(LOGTAG, "getCookie: uri: " + uri + " But can't find cookie."); } @@ -588,7 +588,7 @@ public final class CookieManager { Iterator> listIter = cookieLists.iterator(); while (listIter.hasNext() && count < MAX_RAM_COOKIES_COUNT) { ArrayList list = listIter.next(); - if (WebView.DEBUG) { + if (DebugFlags.COOKIE_MANAGER) { Iterator iter = list.iterator(); while (iter.hasNext() && count < MAX_RAM_COOKIES_COUNT) { Cookie cookie = iter.next(); @@ -608,7 +608,7 @@ public final class CookieManager { ArrayList retlist = new ArrayList(); if (mapSize >= MAX_RAM_DOMAIN_COUNT || count >= MAX_RAM_COOKIES_COUNT) { - if (WebView.DEBUG) { + if (DebugFlags.COOKIE_MANAGER) { Log.v(LOGTAG, count + " cookies used " + byteCount + " bytes with " + mapSize + " domains"); } @@ -616,7 +616,7 @@ public final class CookieManager { int toGo = mapSize / 10 + 1; while (toGo-- > 0){ String domain = domains[toGo].toString(); - if (WebView.LOGV_ENABLED) { + if (DebugFlags.COOKIE_MANAGER) { Log.v(LOGTAG, "delete domain: " + domain + " from RAM cache"); } diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java index aa6c76b432b0..14375d2b1322 100644 --- a/core/java/android/webkit/CookieSyncManager.java +++ b/core/java/android/webkit/CookieSyncManager.java @@ -170,7 +170,7 @@ public final class CookieSyncManager extends WebSyncManager { } protected void syncFromRamToFlash() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.COOKIE_SYNC_MANAGER) { Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash STARTS"); } @@ -187,7 +187,7 @@ public final class CookieSyncManager extends WebSyncManager { CookieManager.getInstance().deleteLRUDomain(); syncFromRamToFlash(lruList); - if (WebView.LOGV_ENABLED) { + if (DebugFlags.COOKIE_SYNC_MANAGER) { Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash DONE"); } } diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java new file mode 100644 index 000000000000..89cb606df165 --- /dev/null +++ b/core/java/android/webkit/DebugFlags.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +/** + * This class is a container for all of the debug flags used in the Java + * components of webkit. These flags must be final in order to ensure that + * the compiler optimizes the code that uses them out of the final executable. + * + * The name of each flags maps directly to the name of the class in which that + * flag is used. + * + */ +class DebugFlags { + + public static final boolean BROWSER_FRAME = false; + 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 = 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; + public static final boolean NETWORK = false; + public static final boolean SSL_ERROR_HANDLER = false; + public static final boolean STREAM_LOADER = false; + 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 = false; + public static final boolean WEB_VIEW = false; + public static final boolean WEB_VIEW_CORE = false; + +} diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java index 6f1b16047950..c33744e57388 100644 --- a/core/java/android/webkit/FrameLoader.java +++ b/core/java/android/webkit/FrameLoader.java @@ -120,7 +120,7 @@ class FrameLoader { } else if (handleLocalFile(url, mListener, mSettings)) { return true; } - if (WebView.LOGV_ENABLED) { + if (DebugFlags.FRAME_LOADER) { Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:" + mListener.url()); } @@ -180,7 +180,7 @@ class FrameLoader { return true; } - if (WebView.LOGV_ENABLED) { + if (DebugFlags.FRAME_LOADER) { Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: " + mListener.url()); } @@ -211,7 +211,7 @@ class FrameLoader { * setup a load from the byte stream in a CacheResult. */ private void startCacheLoad(CacheResult result) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.FRAME_LOADER) { Log.v(LOGTAG, "FrameLoader: loading from cache: " + mListener.url()); } @@ -285,7 +285,7 @@ class FrameLoader { // of it's state. If it is not in the cache, then go to the // network. case WebSettings.LOAD_CACHE_ELSE_NETWORK: { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.FRAME_LOADER) { Log.v(LOGTAG, "FrameLoader: checking cache: " + mListener.url()); } diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java index bf055189d6cb..878b690a7cf6 100644 --- a/core/java/android/webkit/JWebCoreJavaBridge.java +++ b/core/java/android/webkit/JWebCoreJavaBridge.java @@ -190,7 +190,7 @@ final class JWebCoreJavaBridge extends Handler { * @param timemillis The relative time when the timer should fire */ private void setSharedTimer(long timemillis) { - if (WebView.LOGV_ENABLED) Log.v(LOGTAG, "setSharedTimer " + timemillis); + if (DebugFlags.J_WEB_CORE_JAVA_BRIDGE) Log.v(LOGTAG, "setSharedTimer " + timemillis); if (timemillis <= 0) { // we don't accumulate the sharedTimer unless it is a delayed @@ -214,7 +214,7 @@ final class JWebCoreJavaBridge extends Handler { * Stop the shared timer. */ private void stopSharedTimer() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.J_WEB_CORE_JAVA_BRIDGE) { Log.v(LOGTAG, "stopSharedTimer removing all timers"); } removeMessages(TIMER_MESSAGE); diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index d3e26bd9d166..1ebdb79789d7 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -131,7 +131,7 @@ class LoadListener extends Handler implements EventHandler { LoadListener(Context context, BrowserFrame frame, String url, int nativeLoader, boolean synchronous, boolean isMainPageLoader) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener constructor url=" + url); } mContext = context; @@ -280,7 +280,7 @@ class LoadListener extends Handler implements EventHandler { * directly */ public void headers(Headers headers) { - if (WebView.LOGV_ENABLED) Log.v(LOGTAG, "LoadListener.headers"); + if (DebugFlags.LOAD_LISTENER) Log.v(LOGTAG, "LoadListener.headers"); sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers)); } @@ -422,7 +422,7 @@ class LoadListener extends Handler implements EventHandler { */ public void status(int majorVersion, int minorVersion, int code, /* Status-Code value */ String reasonPhrase) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener: from: " + mUrl + " major: " + majorVersion + " minor: " + minorVersion @@ -482,7 +482,7 @@ class LoadListener extends Handler implements EventHandler { * directly */ public void error(int id, String description) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.error url:" + url() + " id:" + id + " description:" + description); } @@ -510,7 +510,7 @@ class LoadListener extends Handler implements EventHandler { * mDataBuilder is a thread-safe structure. */ public void data(byte[] data, int length) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.data(): url: " + url()); } @@ -535,7 +535,7 @@ class LoadListener extends Handler implements EventHandler { * directly */ public void endData() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.endData(): url: " + url()); } sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED)); @@ -588,7 +588,7 @@ class LoadListener extends Handler implements EventHandler { // before calling it. if (mCacheLoader != null) { mCacheLoader.load(); - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener cache load url=" + url()); } return; @@ -638,7 +638,7 @@ class LoadListener extends Handler implements EventHandler { CacheManager.HEADER_KEY_IFNONEMATCH) && !headers.containsKey( CacheManager.HEADER_KEY_IFMODIFIEDSINCE)) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "FrameLoader: HTTP URL in cache " + "and usable: " + url()); } @@ -657,7 +657,7 @@ class LoadListener extends Handler implements EventHandler { * directly */ public void handleSslErrorRequest(SslError error) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.handleSslErrorRequest(): url:" + url() + " primary error: " + error.getPrimaryError() + @@ -723,7 +723,7 @@ class LoadListener extends Handler implements EventHandler { * are null, cancel the request. */ void handleAuthResponse(String username, String password) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.handleAuthResponse: url: " + mUrl + " username: " + username + " password: " + password); @@ -820,7 +820,7 @@ class LoadListener extends Handler implements EventHandler { } void attachRequestHandle(RequestHandle requestHandle) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " + "requestHandle: " + requestHandle); } @@ -828,7 +828,7 @@ class LoadListener extends Handler implements EventHandler { } void detachRequestHandle() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.detachRequestHandle(): " + "requestHandle: " + mRequestHandle); } @@ -867,7 +867,7 @@ class LoadListener extends Handler implements EventHandler { */ static boolean willLoadFromCache(String url) { boolean inCache = CacheManager.getCacheFile(url, null) != null; - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " + inCache); } @@ -1041,7 +1041,7 @@ class LoadListener extends Handler implements EventHandler { * EventHandler's method call. */ public void cancel() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { if (mRequestHandle == null) { Log.v(LOGTAG, "LoadListener.cancel(): no requestHandle"); } else { @@ -1173,7 +1173,7 @@ class LoadListener extends Handler implements EventHandler { tearDown(); } - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.onRedirect(): redirect to: " + redirectTo); } @@ -1187,7 +1187,7 @@ class LoadListener extends Handler implements EventHandler { Pattern.compile("^((?:[xX]-)?[a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$"); /* package */ void parseContentTypeHeader(String contentType) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "LoadListener.parseContentTypeHeader: " + "contentType: " + contentType); } @@ -1377,7 +1377,7 @@ class LoadListener extends Handler implements EventHandler { */ private String guessMimeTypeFromExtension() { // PENDING: need to normalize url - if (WebView.LOGV_ENABLED) { + if (DebugFlags.LOAD_LISTENER) { Log.v(LOGTAG, "guessMimeTypeFromExtension: mURL = " + mUrl); } @@ -1401,7 +1401,7 @@ class LoadListener extends Handler implements EventHandler { * Cycle through our messages for synchronous loads. */ /* package */ void loadSynchronousMessages() { - if (WebView.DEBUG && !mSynchronous) { + if (DebugFlags.LOAD_LISTENER && !mSynchronous) { throw new AssertionError(); } // Note: this can be called twice if it is a synchronous network load, diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java index c9b80ce77df6..8c2b09b1a7a1 100644 --- a/core/java/android/webkit/Network.java +++ b/core/java/android/webkit/Network.java @@ -132,7 +132,7 @@ class Network { * XXX: Must be created in the same thread as WebCore!!!!! */ private Network(Context context) { - if (WebView.DEBUG) { + if (DebugFlags.NETWORK) { Assert.assertTrue(Thread.currentThread(). getName().equals(WebViewCore.THREAD_NAME)); } @@ -232,7 +232,7 @@ class Network { * connecting through the proxy. */ public synchronized void setProxyUsername(String proxyUsername) { - if (WebView.DEBUG) { + if (DebugFlags.NETWORK) { Assert.assertTrue(isValidProxySet()); } @@ -252,7 +252,7 @@ class Network { * connecting through the proxy. */ public synchronized void setProxyPassword(String proxyPassword) { - if (WebView.DEBUG) { + if (DebugFlags.NETWORK) { Assert.assertTrue(isValidProxySet()); } @@ -266,7 +266,7 @@ class Network { * @return True iff succeeds. */ public boolean saveState(Bundle outState) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.NETWORK) { Log.v(LOGTAG, "Network.saveState()"); } @@ -280,7 +280,7 @@ class Network { * @return True iff succeeds. */ public boolean restoreState(Bundle inState) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.NETWORK) { Log.v(LOGTAG, "Network.restoreState()"); } @@ -300,7 +300,7 @@ class Network { * @param loader The loader that resulted in SSL errors. */ public void handleSslErrorRequest(LoadListener loader) { - if (WebView.DEBUG) Assert.assertNotNull(loader); + if (DebugFlags.NETWORK) Assert.assertNotNull(loader); if (loader != null) { mSslErrorHandler.handleSslErrorRequest(loader); } @@ -313,7 +313,7 @@ class Network { * authentication request. */ public void handleAuthRequest(LoadListener loader) { - if (WebView.DEBUG) Assert.assertNotNull(loader); + if (DebugFlags.NETWORK) Assert.assertNotNull(loader); if (loader != null) { mHttpAuthHandler.handleAuthRequest(loader); } diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java index 617a3145ee51..cc1e7501b1ad 100644 --- a/core/java/android/webkit/SslErrorHandler.java +++ b/core/java/android/webkit/SslErrorHandler.java @@ -120,7 +120,7 @@ public class SslErrorHandler extends Handler { * Handles SSL error(s) on the way up to the user. */ /* package */ synchronized void handleSslErrorRequest(LoadListener loader) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.SSL_ERROR_HANDLER) { Log.v(LOGTAG, "SslErrorHandler.handleSslErrorRequest(): " + "url=" + loader.url()); } @@ -157,14 +157,14 @@ public class SslErrorHandler extends Handler { SslError error = loader.sslError(); - if (WebView.DEBUG) { + if (DebugFlags.SSL_ERROR_HANDLER) { Assert.assertNotNull(error); } int primary = error.getPrimaryError(); String host = loader.host(); - if (WebView.DEBUG) { + if (DebugFlags.SSL_ERROR_HANDLER) { Assert.assertTrue(host != null && primary != 0); } @@ -205,11 +205,11 @@ public class SslErrorHandler extends Handler { */ /* package */ synchronized void handleSslErrorResponse(boolean proceed) { LoadListener loader = mLoaderQueue.poll(); - if (WebView.DEBUG) { + if (DebugFlags.SSL_ERROR_HANDLER) { Assert.assertNotNull(loader); } - if (WebView.LOGV_ENABLED) { + if (DebugFlags.SSL_ERROR_HANDLER) { Log.v(LOGTAG, "SslErrorHandler.handleSslErrorResponse():" + " proceed: " + proceed + " url:" + loader.url()); @@ -221,7 +221,7 @@ public class SslErrorHandler extends Handler { int primary = loader.sslError().getPrimaryError(); String host = loader.host(); - if (WebView.DEBUG) { + if (DebugFlags.SSL_ERROR_HANDLER) { Assert.assertTrue(host != null && primary != 0); } boolean hasKey = mSslPrefTable.containsKey(host); diff --git a/core/java/android/webkit/StreamLoader.java b/core/java/android/webkit/StreamLoader.java index 7d6d7cc5d3fc..eab3350d7265 100644 --- a/core/java/android/webkit/StreamLoader.java +++ b/core/java/android/webkit/StreamLoader.java @@ -113,7 +113,7 @@ abstract class StreamLoader extends Handler { * @see android.os.Handler#handleMessage(android.os.Message) */ public void handleMessage(Message msg) { - if (WebView.DEBUG && mHandler.isSynchronous()) { + if (DebugFlags.STREAM_LOADER && mHandler.isSynchronous()) { throw new AssertionError(); } if (mHandler.cancelled()) { diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java index d6ac3e9093bf..de70fc2d2b65 100644 --- a/core/java/android/webkit/URLUtil.java +++ b/core/java/android/webkit/URLUtil.java @@ -61,7 +61,7 @@ public final class URLUtil { webAddress = new WebAddress(inUrl); } catch (ParseException ex) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.URL_UTIL) { Log.v(LOGTAG, "smartUrlFilter: failed to parse url = " + inUrl); } return retVal; diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java index ffd6a118d9e6..62a55318d306 100644 --- a/core/java/android/webkit/WebBackForwardList.java +++ b/core/java/android/webkit/WebBackForwardList.java @@ -137,7 +137,7 @@ public class WebBackForwardList implements Cloneable, Serializable { // when removing the first item, we can assert that the index is 0. // This lets us change the current index without having to query the // native BackForwardList. - if (WebView.DEBUG && (index != 0)) { + if (DebugFlags.WEB_BACK_FORWARD_LIST && (index != 0)) { throw new AssertionError(); } final WebHistoryItem h = mArray.remove(index); diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index e7352928b06a..ea186fd5ff3d 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -1190,7 +1190,7 @@ public class WebSettings { /*package*/ synchronized void syncSettingsAndCreateHandler(BrowserFrame frame) { mBrowserFrame = frame; - if (WebView.DEBUG) { + if (DebugFlags.WEB_SETTINGS) { junit.framework.Assert.assertTrue(frame.mNativeFrame != 0); } nativeSync(frame.mNativeFrame); diff --git a/core/java/android/webkit/WebSyncManager.java b/core/java/android/webkit/WebSyncManager.java index ded17ed518c7..d3ec6031ace4 100644 --- a/core/java/android/webkit/WebSyncManager.java +++ b/core/java/android/webkit/WebSyncManager.java @@ -47,7 +47,7 @@ abstract class WebSyncManager implements Runnable { @Override public void handleMessage(Message msg) { if (msg.what == SYNC_MESSAGE) { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.WEB_SYNC_MANAGER) { Log.v(LOGTAG, "*** WebSyncManager sync ***"); } syncFromRamToFlash(); @@ -94,7 +94,7 @@ abstract class WebSyncManager implements Runnable { * sync() forces sync manager to sync now */ public void sync() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.WEB_SYNC_MANAGER) { Log.v(LOGTAG, "*** WebSyncManager sync ***"); } if (mHandler == null) { @@ -109,7 +109,7 @@ abstract class WebSyncManager implements Runnable { * resetSync() resets sync manager's timer */ public void resetSync() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.WEB_SYNC_MANAGER) { Log.v(LOGTAG, "*** WebSyncManager resetSync ***"); } if (mHandler == null) { @@ -124,7 +124,7 @@ abstract class WebSyncManager implements Runnable { * startSync() requests sync manager to start sync */ public void startSync() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.WEB_SYNC_MANAGER) { Log.v(LOGTAG, "*** WebSyncManager startSync ***, Ref count:" + mStartSyncRefCount); } @@ -142,7 +142,7 @@ abstract class WebSyncManager implements Runnable { * the queue to break the sync loop */ public void stopSync() { - if (WebView.LOGV_ENABLED) { + if (DebugFlags.WEB_SYNC_MANAGER) { Log.v(LOGTAG, "*** WebSyncManager stopSync ***, Ref count:" + mStartSyncRefCount); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 4072c224e553..e549d5fd0d5a 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -207,8 +207,6 @@ public class WebView extends AbsoluteLayout // keep debugging parameters near the top of the file static final String LOGTAG = "webview"; - static final boolean DEBUG = false; - static final boolean LOGV_ENABLED = DEBUG; private static class ExtendedZoomControls extends FrameLayout { public ExtendedZoomControls(Context context, AttributeSet attrs) { @@ -2643,7 +2641,7 @@ public class WebView extends AbsoluteLayout } float newX = scrollZoomX(scale); float newY = scrollZoomY(scale); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "scrollZoomDraw scale=" + scale + " + (" + newX + ", " + newY + ") mZoomScroll=(" + mZoomScrollX + ", " + mZoomScrollY + ")" + " invScale=" + invScale + " scale=" @@ -2690,7 +2688,7 @@ public class WebView extends AbsoluteLayout } canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX , mZoomScrollY + height * halfY); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=(" + width + ", " + height + ") half=(" + halfX + ", " + halfY + ")"); @@ -2719,7 +2717,7 @@ public class WebView extends AbsoluteLayout , Math.max(0, (int) ((x - left) / scale))); mZoomScrollY = Math.min(mContentHeight - height , Math.max(0, (int) ((y - top) / scale))); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "zoomScrollTap scale=" + scale + " + (" + left + ", " + top + ") mZoomScroll=(" + mZoomScrollX + ", " + mZoomScrollY + ")" + " x=" + x + " y=" + y); @@ -2736,7 +2734,7 @@ public class WebView extends AbsoluteLayout float y = (float) height / (float) mContentHeight; mZoomScrollLimit = Math.max(DEFAULT_MIN_ZOOM_SCALE, Math.min(x, y)); mZoomScrollInvLimit = 1.0f / mZoomScrollLimit; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "canZoomScrollOut" + " mInvActualScale=" + mInvActualScale + " mZoomScrollLimit=" + mZoomScrollLimit @@ -2783,7 +2781,7 @@ public class WebView extends AbsoluteLayout - (zoomFrame.height() >> 1)); scrollTo(0, 0); // triggers inval, starts animation clearTextEntry(); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=(" + mZoomScrollX + ", " + mZoomScrollY +")"); } @@ -2814,7 +2812,7 @@ public class WebView extends AbsoluteLayout if (maxZoomX > 0) { int maxScreenX = width - (int) Math.ceil(width * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "moveZoomScrollWindow-X" + " maxScreenX=" + maxScreenX + " width=" + width + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x); @@ -2827,7 +2825,7 @@ public class WebView extends AbsoluteLayout if (maxZoomY > 0) { int maxScreenY = height - (int) Math.ceil(height * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "moveZoomScrollWindow-Y" + " maxScreenY=" + maxScreenY + " height=" + height + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y); @@ -2839,7 +2837,7 @@ public class WebView extends AbsoluteLayout if (oldX != mZoomScrollX || oldY != mZoomScrollY) { invalidate(); } - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "moveZoomScrollWindow" + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")" + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")" @@ -3107,7 +3105,7 @@ public class WebView extends AbsoluteLayout @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() + ", " + event); } @@ -3232,7 +3230,7 @@ public class WebView extends AbsoluteLayout @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() + ", " + event); } @@ -3299,7 +3297,7 @@ public class WebView extends AbsoluteLayout if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { zoomScrollOut(); } else { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE"); } mPrivateHandler.sendMessageDelayed(mPrivateHandler @@ -3478,7 +3476,7 @@ public class WebView extends AbsoluteLayout @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); } if (focused) { @@ -3586,7 +3584,7 @@ public class WebView extends AbsoluteLayout return false; } - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" + mTouchMode); } @@ -3642,7 +3640,7 @@ public class WebView extends AbsoluteLayout mSelectX = mScrollX + (int) x; mSelectY = mScrollY + (int) y; mTouchMode = TOUCH_SELECT_MODE; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY); } nativeMoveSelection(viewToContent(mSelectX) @@ -3687,7 +3685,7 @@ public class WebView extends AbsoluteLayout if (mTouchMode == TOUCH_SELECT_MODE) { mSelectX = mScrollX + (int) x; mSelectY = mScrollY + (int) y; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY); } nativeMoveSelection(viewToContent(mSelectX) @@ -3839,7 +3837,7 @@ public class WebView extends AbsoluteLayout // no action during scroll animation break; case SCROLL_ZOOM_OUT: - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "ACTION_UP SCROLL_ZOOM_OUT" + " eventTime - mLastTouchTime=" + (eventTime - mLastTouchTime)); @@ -3961,7 +3959,7 @@ public class WebView extends AbsoluteLayout && !mLastFocusBounds.equals(nativeGetCursorRingBounds())) { nativeSelectBestAt(mLastFocusBounds); } - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "onTrackballEvent down ev=" + ev + " time=" + time + " mLastFocusTime=" + mLastFocusTime); @@ -3981,7 +3979,7 @@ public class WebView extends AbsoluteLayout mExtendSelection = true; } } - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "onTrackballEvent up ev=" + ev + " time=" + time ); @@ -3989,26 +3987,26 @@ public class WebView extends AbsoluteLayout return false; // let common code in onKeyUp at it } if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent gmail quit"); + if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit"); return false; } // no move if we're still waiting on SWITCH_TO_CLICK timeout if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent 2 click quit"); + if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent 2 click quit"); return true; } if (mTrackballDown) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent down quit"); + if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit"); return true; // discard move if trackball is down } if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); + if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); return true; } // TODO: alternatively we can do panning as touch does switchOutDrawHistory(); if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "onTrackballEvent time=" + time + " last=" + mTrackballLastTime); } @@ -4016,7 +4014,7 @@ public class WebView extends AbsoluteLayout mTrackballXMove = mTrackballYMove = 0; } mTrackballLastTime = time; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); } mTrackballRemainsX += ev.getX(); @@ -4038,7 +4036,7 @@ public class WebView extends AbsoluteLayout , mSelectX)); mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET , mSelectY)); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "moveSelection" + " mSelectX=" + mSelectX + " mSelectY=" + mSelectY @@ -4121,7 +4119,7 @@ public class WebView extends AbsoluteLayout float ax = Math.abs(xRate); float ay = Math.abs(yRate); float maxA = Math.max(ax, ay); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "doTrackball elapsed=" + elapsed + " xRate=" + xRate + " yRate=" + yRate @@ -4138,7 +4136,7 @@ public class WebView extends AbsoluteLayout int maxWH = Math.max(width, height); mZoomScrollX += scaleTrackballX(xRate, maxWH); mZoomScrollY += scaleTrackballY(yRate, maxWH); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "doTrackball SCROLL_ZOOM_OUT" + " mZoomScrollX=" + mZoomScrollX + " mZoomScrollY=" + mZoomScrollY); @@ -4163,7 +4161,7 @@ public class WebView extends AbsoluteLayout mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT; count = Math.min(count, TRACKBALL_MOVE_COUNT); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode + " count=" + count + " mTrackballRemainsX=" + mTrackballRemainsX @@ -4177,7 +4175,7 @@ public class WebView extends AbsoluteLayout if (count >= TRACKBALL_SCROLL_COUNT) { int xMove = scaleTrackballX(xRate, width); int yMove = scaleTrackballY(yRate, height); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "doTrackball pinScrollBy" + " count=" + count + " xMove=" + xMove + " yMove=" + yMove @@ -4601,7 +4599,7 @@ public class WebView extends AbsoluteLayout class PrivateHandler extends Handler { @Override public void handleMessage(Message msg) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) : HandlerDebugString[msg.what - REMEMBER_PASSWORD]); @@ -4701,7 +4699,7 @@ public class WebView extends AbsoluteLayout && viewSize.y == mLastHeightSent; recordNewContentSize(draw.mWidthHeight.x, draw.mWidthHeight.y, updateLayout); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Rect b = draw.mInvalRegion.getBounds(); Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + b.left+","+b.top+","+b.right+","+b.bottom+"}"); @@ -4833,7 +4831,7 @@ public class WebView extends AbsoluteLayout break; case UPDATE_CLIPBOARD: String str = (String) msg.obj; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str); } try { @@ -5196,7 +5194,7 @@ public class WebView extends AbsoluteLayout mLastFocusTime = time; mLastFocusBounds = nativeGetCursorRingBounds(); boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds + " mLastFocusTime=" + mLastFocusTime + " handled=" + keyHandled); @@ -5228,7 +5226,7 @@ public class WebView extends AbsoluteLayout } if (mLastFocusBounds.isEmpty()) return keyHandled; if (mLastFocusBounds.equals(contentFocus)) return keyHandled; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "navHandledKey contentFocus=" + contentFocus); } requestRectangleOnScreen(viewFocus); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 1d91f52e00e5..614323c61611 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -41,8 +41,6 @@ import junit.framework.Assert; final class WebViewCore { private static final String LOGTAG = "webcore"; - static final boolean DEBUG = false; - static final boolean LOGV_ENABLED = DEBUG; static { // Load libwebcore during static initialization. This happens in the @@ -694,7 +692,7 @@ final class WebViewCore { mHandler = new Handler() { @Override public void handleMessage(Message msg) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW_CORE) { Log.v(LOGTAG, msg.what < LOAD_URL || msg.what > SET_ACTIVE ? Integer.toString(msg.what) : HandlerDebugString[msg.what - LOAD_URL]); @@ -1164,7 +1162,7 @@ final class WebViewCore { //------------------------------------------------------------------------- void stopLoading() { - if (LOGV_ENABLED) Log.v(LOGTAG, "CORE stopLoading"); + if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "CORE stopLoading"); if (mBrowserFrame != null) { mBrowserFrame.stopLoading(); } @@ -1240,12 +1238,12 @@ final class WebViewCore { //------------------------------------------------------------------------- private void loadUrl(String url) { - if (LOGV_ENABLED) Log.v(LOGTAG, " CORE loadUrl " + url); + if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, " CORE loadUrl " + url); mBrowserFrame.loadUrl(url); } private void key(KeyEvent evt, boolean isDown) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW_CORE) { Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", " + evt); } @@ -1263,7 +1261,7 @@ final class WebViewCore { // notify webkit that our virtual view size changed size (after inv-zoom) private void viewSizeChanged(int w, int h, float scale) { - if (LOGV_ENABLED) Log.v(LOGTAG, "CORE onSizeChanged"); + if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "CORE onSizeChanged"); if (w == 0) { Log.w(LOGTAG, "skip viewSizeChanged as w is 0"); return; @@ -1303,7 +1301,7 @@ final class WebViewCore { if (needInvalidate) { // ensure {@link #webkitDraw} is called as we were blocking in // {@link #contentDraw} when mCurrentViewWidth is 0 - if (LOGV_ENABLED) Log.v(LOGTAG, "viewSizeChanged"); + if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "viewSizeChanged"); contentDraw(); } mEventHub.sendMessage(Message.obtain(null, @@ -1342,17 +1340,17 @@ final class WebViewCore { private void webkitDraw() { mDrawIsScheduled = false; DrawData draw = new DrawData(); - if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw start"); + if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start"); if (nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight) == false) { - if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw abort"); + if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort"); return; } if (mWebView != null) { // Send the native view size that was used during the most recent // layout. draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight); - if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID"); + if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID"); Message.obtain(mWebView.mPrivateHandler, WebView.NEW_PICTURE_MSG_ID, draw).sendToTarget(); if (mWebkitScrollX != 0 || mWebkitScrollY != 0) { @@ -1439,7 +1437,7 @@ final class WebViewCore { synchronized (core) { core.mDrawIsScheduled = false; core.mDrawIsPaused = false; - if (LOGV_ENABLED) Log.v(LOGTAG, "resumeUpdate"); + if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "resumeUpdate"); core.contentDraw(); } } -- cgit v1.2.3-59-g8ed1b From 2f1d60cd55933f444842991d3bf05a8552cdd30d Mon Sep 17 00:00:00 2001 From: Cary Clark Date: Wed, 3 Jun 2009 08:05:53 -0400 Subject: remove unused text parameters from WebView The additional parameters are no longer used. --- core/java/android/webkit/WebView.java | 17 ++++++----------- core/java/android/webkit/WebViewCore.java | 31 +++++++++---------------------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index e549d5fd0d5a..9437236e71b2 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -205,7 +205,6 @@ public class WebView extends AbsoluteLayout // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK private boolean mAutoRedraw; - // keep debugging parameters near the top of the file static final String LOGTAG = "webview"; private static class ExtendedZoomControls extends FrameLayout { @@ -2923,8 +2922,7 @@ public class WebView extends AbsoluteLayout */ /* package */ void deleteSelection(int start, int end) { mTextGeneration++; - mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end, - cursorData()); + mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end); } /** @@ -2934,8 +2932,7 @@ public class WebView extends AbsoluteLayout * @param end End of selection. */ /* package */ void setSelection(int start, int end) { - mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end, - cursorData()); + mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end); } // Called by JNI when a touch event puts a textfield into focus. @@ -3133,7 +3130,7 @@ public class WebView extends AbsoluteLayout return false; } - if (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false + if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { mExtendSelection = false; @@ -3217,7 +3214,7 @@ public class WebView extends AbsoluteLayout } // TODO: should we pass all the keys to DOM or check the meta tag - if (nativeFocusNodeWantsKeyEvents() || true) { + if (nativeCursorWantsKeyEvents() || true) { // pass the key to DOM mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); // return true as DOM handles the key @@ -3326,7 +3323,7 @@ public class WebView extends AbsoluteLayout } // TODO: should we pass all the keys to DOM or check the meta tag - if (nativeFocusNodeWantsKeyEvents() || true) { + if (nativeCursorWantsKeyEvents() || true) { // pass the key to DOM mWebViewCore.sendMessage(EventHub.KEY_UP, event); // return true as DOM handles the key @@ -4559,7 +4556,6 @@ public class WebView extends AbsoluteLayout /* package */ void replaceTextfieldText(int oldStart, int oldEnd, String replace, int newStart, int newEnd) { HashMap arg = new HashMap(); - arg.put("focusData", cursorData()); arg.put("replace", replace); arg.put("start", Integer.valueOf(newStart)); arg.put("end", Integer.valueOf(newEnd)); @@ -4569,7 +4565,6 @@ public class WebView extends AbsoluteLayout /* package */ void passToJavaScript(String currentText, KeyEvent event) { HashMap arg = new HashMap(); - arg.put("focusData", cursorData()); arg.put("event", event); arg.put("currentText", currentText); // Increase our text generation number, and pass it to webcore thread @@ -5269,6 +5264,7 @@ public class WebView extends AbsoluteLayout private native boolean nativeCursorIsAnchor(); private native boolean nativeCursorIsTextInput(); private native String nativeCursorText(); + private native boolean nativeCursorWantsKeyEvents(); private native void nativeDebugDump(); private native void nativeDestroy(); private native void nativeDrawCursorRing(Canvas content); @@ -5293,7 +5289,6 @@ public class WebView extends AbsoluteLayout * Returns true if the native focus nodes says it wants to handle key events * (ala plugins). This can only be called if mNativeClass is non-zero! */ - private native boolean nativeFocusNodeWantsKeyEvents(); private native Rect nativeGetCursorRingBounds(); private native Region nativeGetSelection(); private native boolean nativeHasCursorNode(); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 614323c61611..706e6284e779 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -359,11 +359,10 @@ final class WebViewCore { private native int nativeGetContentMinPrefWidth(); // Start: functions that deal with text editing - private native void nativeReplaceTextfieldText(int frame, int node, int x, - int y, int oldStart, int oldEnd, String replace, int newStart, - int newEnd); + private native void nativeReplaceTextfieldText( + int oldStart, int oldEnd, String replace, int newStart, int newEnd); - private native void passToJs(int frame, int node, int x, int y, int gen, + private native void passToJs(int gen, String currentText, int keyCode, int keyValue, boolean down, boolean cap, boolean fn, boolean sym); @@ -409,8 +408,7 @@ final class WebViewCore { * @param start Beginning of selection to delete. * @param end End of selection to delete. */ - private native void nativeDeleteSelection(int frame, int node, int x, int y, - int start, int end); + private native void nativeDeleteSelection(int start, int end); /** * Set the selection to (start, end) in the focused textfield. If start and @@ -418,8 +416,7 @@ final class WebViewCore { * @param start Beginning of selection. * @param end End of selection. */ - private native void nativeSetSelection(int frame, int node, int x, int y, - int start, int end); + private native void nativeSetSelection(int start, int end); private native String nativeGetSelection(Region sel); @@ -871,26 +868,22 @@ final class WebViewCore { case REPLACE_TEXT: HashMap jMap = (HashMap) msg.obj; - CursorData fData = (CursorData) jMap.get("focusData"); String replace = (String) jMap.get("replace"); int newStart = ((Integer) jMap.get("start")).intValue(); int newEnd = ((Integer) jMap.get("end")).intValue(); - nativeReplaceTextfieldText(fData.mFrame, - fData.mNode, fData.mX, fData.mY, msg.arg1, + nativeReplaceTextfieldText(msg.arg1, msg.arg2, replace, newStart, newEnd); break; case PASS_TO_JS: { HashMap jsMap = (HashMap) msg.obj; - CursorData fDat = (CursorData) jsMap.get("focusData"); KeyEvent evt = (KeyEvent) jsMap.get("event"); int keyCode = evt.getKeyCode(); int keyValue = evt.getUnicodeChar(); int generation = msg.arg1; - passToJs(fDat.mFrame, fDat.mNode, fDat.mX, fDat.mY, - generation, + passToJs(generation, (String) jsMap.get("currentText"), keyCode, keyValue, @@ -999,17 +992,11 @@ final class WebViewCore { break; case DELETE_SELECTION: - CursorData delData = (CursorData) msg.obj; - nativeDeleteSelection(delData.mFrame, - delData.mNode, delData.mX, - delData.mY, msg.arg1, msg.arg2); + nativeDeleteSelection(msg.arg1, msg.arg2); break; case SET_SELECTION: - CursorData selData = (CursorData) msg.obj; - nativeSetSelection(selData.mFrame, - selData.mNode, selData.mX, - selData.mY, msg.arg1, msg.arg2); + nativeSetSelection(msg.arg1, msg.arg2); break; case LISTBOX_CHOICES: -- cgit v1.2.3-59-g8ed1b From 8aeac9408ff74126a1b3e9bb58b86056350b708d Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Wed, 3 Jun 2009 09:43:14 -0700 Subject: Cleaning up makefile for libttssynthproxy for the simulator. --- tts/jni/Android.mk | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tts/jni/Android.mk b/tts/jni/Android.mk index bb76583c52d6..665d6d20fc41 100755 --- a/tts/jni/Android.mk +++ b/tts/jni/Android.mk @@ -14,13 +14,10 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libcutils -ifneq ($(TARGET_SIMULATOR),true) -LOCAL_SHARED_LIBRARIES += \ - libdl -endif - -ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true) -LOCAL_LDLIBS += -ldl +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl endif -- cgit v1.2.3-59-g8ed1b From 82aa2f5eecddf723af2e4e134de15bbf5c6b32f4 Mon Sep 17 00:00:00 2001 From: Leon Scroggins Date: Wed, 3 Jun 2009 13:01:32 -0400 Subject: Remove obsolete flags from WebViewCore and WebView Remove NO_FOCUS_CHANGE_BLOCK and BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, which were part of SET_FINAL_FOCUS (which is no longer used) from WebViewCore. In WebView, do not pass BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP with SET_MOVE_MOUSE, since the receiver does not care about it. --- core/java/android/webkit/WebView.java | 4 +--- core/java/android/webkit/WebViewCore.java | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 9437236e71b2..032c0737d605 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -3310,9 +3310,7 @@ public class WebView extends AbsoluteLayout // coordinates should be in content coordinates. if (nativeCursorIntersects(visibleRect)) { nativeSetFollowedLink(true); - mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, - EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, - cursorData()); + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, cursorData()); playSoundEffect(SoundEffectConstants.CLICK); return true; } else if (nativeHasCursorNode()) { diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 706e6284e779..78d0b4de2996 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -658,10 +658,6 @@ final class WebViewCore { // private message ids private static final int DESTROY = 200; - // flag values passed to message SET_FINAL_FOCUS - static final int NO_FOCUS_CHANGE_BLOCK = 0; - static final int BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP = 1; - // Private handler for WebCore messages. private Handler mHandler; // Message queue for containing messages before the WebCore thread is -- cgit v1.2.3-59-g8ed1b From ceaafa5f1f0504ec85a7d6cdf45381cf748f54aa Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Wed, 3 Jun 2009 10:46:44 -0700 Subject: Adding a new type: aggregation_exception. --- core/java/android/provider/ContactsContract.java | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 3763f9ae64f9..835a5f646e51 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -706,4 +706,47 @@ public final class ContactsContract { } } + /** + * Constants for the contact aggregation exceptions table, which contains + * aggregation rules overriding those used by automatic aggregation. + */ + public static final class AggregationExceptions { + /** + * This utility class cannot be instantiated + */ + private AggregationExceptions() {} + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = + Uri.withAppendedPath(AUTHORITY_URI, "aggregation_exception"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of data. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/aggregation_exception"; + + /** + * The type of exception: {@link #TYPE_NEVER_MATCH} or {@link #TYPE_ALWAYS_MATCH}. + * + *

    Type: INTEGER

    + */ + public static final String TYPE = "type"; + + public static final int TYPE_NEVER_MATCH = 0; + public static final int TYPE_ALWAYS_MATCH = 1; + + /** + * A reference to the {@link android.provider.ContactsContract.Contacts#_ID} of one of + * the contacts that the rule applies to. + */ + public static final String CONTACT_ID1 = "contact_id1"; + + /** + * A reference to the {@link android.provider.ContactsContract.Contacts#_ID} of the other + * contact that the rule applies to. + */ + public static final String CONTACT_ID2 = "contact_id2"; + } } -- cgit v1.2.3-59-g8ed1b From 3e8950c0c73f9c1574ce3388c754009edf6bc930 Mon Sep 17 00:00:00 2001 From: Guang Zhu Date: Wed, 3 Jun 2009 12:23:09 -0700 Subject: Added new parameter to enable a manual pause between pages --- .../DumpRenderTree/assets/run_reliability_tests.py | 22 +++++++++++++++------- .../dumprendertree/LayoutTestsAutoRunner.java | 18 ++++++++++++------ .../android/dumprendertree/ReliabilityTest.java | 5 +++-- .../dumprendertree/ReliabilityTestActivity.java | 16 +++++++++++++--- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/tests/DumpRenderTree/assets/run_reliability_tests.py b/tests/DumpRenderTree/assets/run_reliability_tests.py index 84b9501620da..6aab00929a1c 100755 --- a/tests/DumpRenderTree/assets/run_reliability_tests.py +++ b/tests/DumpRenderTree/assets/run_reliability_tests.py @@ -75,6 +75,11 @@ def main(options, args): else: timedout_file = options.timeout_file + if not options.delay: + manual_delay = 0 + else: + manual_delay = options.delay + adb_cmd = "adb " if options.adb_options: adb_cmd += options.adb_options + " " @@ -110,8 +115,8 @@ def main(options, args): # Call ReliabilityTestsAutoTest#startReliabilityTests test_cmd = (test_cmd_prefix + " -e class " "com.android.dumprendertree.ReliabilityTest#" - "runReliabilityTest -e timeout %s %s" % - (str(timeout_ms), test_cmd_postfix)) + "runReliabilityTest -e timeout %s -e delay %s %s" % + (str(timeout_ms), str(manual_delay), test_cmd_postfix)) adb_output = subprocess.Popen(test_cmd, shell=True, stdout=subprocess.PIPE, @@ -153,20 +158,23 @@ def main(options, args): if "__main__" == __name__: option_parser = optparse.OptionParser() - option_parser.add_option("", "--time-out-ms", + option_parser.add_option("-t", "--time-out-ms", default=60000, help="set the timeout for each test") - option_parser.add_option("", "--verbose", action="store_true", + option_parser.add_option("-v", "--verbose", action="store_true", default=False, help="include debug-level logging") - option_parser.add_option("", "--adb-options", + option_parser.add_option("-a", "--adb-options", default=None, help="pass options to adb, such as -d -e, etc") - option_parser.add_option("", "--crash-file", + option_parser.add_option("-c", "--crash-file", default="reliability_crashed_sites.txt", help="the list of sites that cause browser to crash") - option_parser.add_option("", "--timeout-file", + option_parser.add_option("-f", "--timeout-file", default="reliability_timedout_sites.txt", help="the list of sites that timedout during test.") + option_parser.add_option("-d", "--delay", + default=0, + help="add a manual delay between pages (in ms)") opts, arguments = option_parser.parse_args() main(opts, arguments) diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java index ebdc9c72305c..57e06a1cbcde 100755 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java @@ -16,14 +16,11 @@ package com.android.dumprendertree; -import junit.framework.TestSuite; -import com.android.dumprendertree.LayoutTestsAutoTest; - +import android.os.Bundle; import android.test.InstrumentationTestRunner; import android.test.InstrumentationTestSuite; -import android.util.Log; -import android.content.Intent; -import android.os.Bundle; + +import junit.framework.TestSuite; /** @@ -61,6 +58,14 @@ public class LayoutTestsAutoRunner extends InstrumentationTestRunner { } } + String delay_str = (String) icicle.get("delay"); + if(delay_str != null) { + try { + this.mDelay = Integer.parseInt(delay_str); + } catch (Exception e) { + } + } + String r = (String)icicle.get("rebaseline"); this.mRebaseline = (r != null && r.toLowerCase().equals("true")); super.onCreate(icicle); @@ -68,6 +73,7 @@ public class LayoutTestsAutoRunner extends InstrumentationTestRunner { public String mTestPath = null; public int mTimeoutInMillis = 0; + public int mDelay = 0; public boolean mRebaseline = false; } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java index aa3940ed9b04..e63aa95b558f 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java @@ -25,7 +25,7 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2 Date: Wed, 3 Jun 2009 15:53:13 -0400 Subject: Remove handling of ENTER key from WebView. WebView was (incorrectly) handling the ENTER key as if it could start a long press like CENTER key does. Separate the two behaviors. Now, the ENTER key does not do anything unless it is part of a textfield or the DOM otherwise handles it. As a result, I was able to remove some orphaned code. Before this change, pressing ENTER would play a click noise, and animate the focus ring as if its link were being followed, but do nothing. In WebViewCore, do not pass the ENTER key back to the Activity. --- core/java/android/webkit/WebView.java | 79 ++++++++++++------------------- core/java/android/webkit/WebViewCore.java | 7 ++- 2 files changed, 35 insertions(+), 51 deletions(-) diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 032c0737d605..2940983dcd2c 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -451,7 +451,7 @@ public class WebView extends AbsoluteLayout static final int MARK_NODE_INVALID_ID = 21; static final int UPDATE_CLIPBOARD = 22; - static final int LONG_PRESS_ENTER = 23; + static final int LONG_PRESS_CENTER = 23; static final int PREVENT_TOUCH_ID = 24; static final int WEBCORE_NEED_TOUCH_EVENTS = 25; // obj=Rect in doc coordinates @@ -480,7 +480,7 @@ public class WebView extends AbsoluteLayout "20", "MARK_NODE_INVALID_ID", // = 21; "UPDATE_CLIPBOARD", // = 22; - "LONG_PRESS_ENTER", // = 23; + "LONG_PRESS_CENTER", // = 23; "PREVENT_TOUCH_ID", // = 24; "WEBCORE_NEED_TOUCH_EVENTS", // = 25; "INVAL_RECT_MSG_ID" // = 26; @@ -2411,7 +2411,7 @@ public class WebView extends AbsoluteLayout // need to check it again. nativeRecordButtons(hasFocus() && hasWindowFocus(), mTouchMode == TOUCH_SHORTPRESS_START_MODE - || mTrackballDown || mGotEnterDown, false); + || mTrackballDown || mGotCenterDown, false); drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing); } canvas.restoreToCount(sc); @@ -3096,9 +3096,9 @@ public class WebView extends AbsoluteLayout } } - // This is used to determine long press with the enter key, or - // a center key. Does not affect long press with the trackball/touch. - private boolean mGotEnterDown = false; + // This is used to determine long press with the center key. Does not + // affect long press with the trackball/touch. + private boolean mGotCenterDown = false; @Override public boolean onKeyDown(int keyCode, KeyEvent event) { @@ -3160,13 +3160,12 @@ public class WebView extends AbsoluteLayout return false; } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER - || keyCode == KeyEvent.KEYCODE_ENTER) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { switchOutDrawHistory(); if (event.getRepeatCount() == 0) { - mGotEnterDown = true; + mGotCenterDown = true; mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT); + .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); // Already checked mNativeClass, so we do not need to check it // again. nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); @@ -3280,44 +3279,26 @@ public class WebView extends AbsoluteLayout return false; } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER - || keyCode == KeyEvent.KEYCODE_ENTER) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { // remove the long press message first - mPrivateHandler.removeMessages(LONG_PRESS_ENTER); - mGotEnterDown = false; + mPrivateHandler.removeMessages(LONG_PRESS_CENTER); + mGotCenterDown = false; - if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) { - if (mShiftIsPressed) { - return false; - } - if (getSettings().supportZoom()) { - if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { - zoomScrollOut(); - } else { - if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE"); - } - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(SWITCH_TO_CLICK), TAP_TIMEOUT); - mTouchMode = TOUCH_DOUBLECLICK_MODE; - } - return true; - } + if (mShiftIsPressed) { + return false; } - - Rect visibleRect = sendOurVisibleRect(); - // Note that sendOurVisibleRect calls viewToContent, so the - // coordinates should be in content coordinates. - if (nativeCursorIntersects(visibleRect)) { - nativeSetFollowedLink(true); - mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, cursorData()); - playSoundEffect(SoundEffectConstants.CLICK); - return true; - } else if (nativeHasCursorNode()) { - return true; + if (getSettings().supportZoom() + && mTouchMode == TOUCH_DOUBLECLICK_MODE) { + zoomScrollOut(); + } else { + mPrivateHandler.sendMessageDelayed(mPrivateHandler + .obtainMessage(SWITCH_TO_CLICK), TAP_TIMEOUT); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE"); + } + mTouchMode = TOUCH_DOUBLECLICK_MODE; } - // Bubble up the key event as WebView doesn't handle it - return false; + return true; } // TODO: should we pass all the keys to DOM or check the meta tag @@ -3963,8 +3944,8 @@ public class WebView extends AbsoluteLayout return false; // let common code in onKeyDown at it } if (ev.getAction() == MotionEvent.ACTION_UP) { - // LONG_PRESS_ENTER is set in common onKeyDown - mPrivateHandler.removeMessages(LONG_PRESS_ENTER); + // LONG_PRESS_CENTER is set in common onKeyDown + mPrivateHandler.removeMessages(LONG_PRESS_CENTER); mTrackballDown = false; mTrackballUpTime = time; if (mShiftIsPressed) { @@ -4839,12 +4820,12 @@ public class WebView extends AbsoluteLayout WebViewCore.resumeUpdate(mWebViewCore); break; - case LONG_PRESS_ENTER: + case LONG_PRESS_CENTER: // as this is shared by keydown and trackballdown, reset all // the states - mGotEnterDown = false; + mGotCenterDown = false; mTrackballDown = false; - // LONG_PRESS_ENTER is sent as a delayed message. If we + // LONG_PRESS_CENTER is sent as a delayed message. If we // switch to windows overview, the WebView will be // temporarily removed from the view system. In that case, // do nothing. diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 78d0b4de2996..de142ae5565a 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -1230,10 +1230,13 @@ final class WebViewCore { Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", " + evt); } - if (!nativeKey(evt.getKeyCode(), evt.getUnicodeChar(), + int keyCode = evt.getKeyCode(); + if (!nativeKey(keyCode, evt.getUnicodeChar(), evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(), - isDown)) { + isDown) && keyCode != KeyEvent.KEYCODE_ENTER) { // bubble up the event handling + // but do not bubble up the ENTER key, which would open the search + // bar without any text. mCallbackProxy.onUnhandledKeyEvent(evt); } } -- cgit v1.2.3-59-g8ed1b From b9a39cd300998a1a4577ac7eb87f9b505b8621dc Mon Sep 17 00:00:00 2001 From: Guang Zhu Date: Wed, 3 Jun 2009 14:14:27 -0700 Subject: Adding missing callback onJsConfirm to dismiss any confirmation dialogs --- .../src/com/android/dumprendertree/ReliabilityTestActivity.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java index a374a414c85d..cbec104d1d61 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java @@ -214,6 +214,12 @@ public class ReliabilityTestActivity extends Activity { return true; } + @Override + public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { + result.confirm(); + return true; + } + @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { -- cgit v1.2.3-59-g8ed1b From 2a2c5cd74128a7750f05683614c9824c9262addc Mon Sep 17 00:00:00 2001 From: Wei-Ta Chen Date: Wed, 3 Jun 2009 14:08:04 -0700 Subject: Modify the decoding logic in the FD case when a purgeable flag is set, and lower the threshold of bitmap size for using ashmem(). For the decoding logic, we now go through the "non-purgeable" path if isShareable is false, irrespective of the value of the purgeable flag. --- core/jni/android/graphics/BitmapFactory.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 1fd15d687baf..137707fa93bf 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -311,7 +311,7 @@ static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream, int sampleSize) { SkPixelRef* pr; // only use ashmem for large images, since mmaps come at a price - if (bitmap->getSize() >= 32 * 65536) { + if (bitmap->getSize() >= 32 * 1024) { pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize); } else { pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize); @@ -520,7 +520,10 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, */ AutoFDSeek as(descriptor); - return doDecode(env, stream, padding, bitmapFactoryOptions, true); + /* Allow purgeable iff we own the FD, i.e., in the puregeable and + shareable case. + */ + return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD); } /* make a deep copy of the asset, and return it as a stream, or NULL if there -- cgit v1.2.3-59-g8ed1b From cede1ed3e1721dc4a697a540388ef0f4b51c60eb Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 2 Jun 2009 22:05:04 -0700 Subject: fix [1610840] Positional light doesn't work correctly on emulator This bug was introduced when lighting computations was changed from eye-space to object-space. The light position need to be transformed back to object-space each time the modelview matrix changes which requires us to compute the inverse of the modelview matrix. This computation was done with the assumption that normals where transformed (which was the case when the computation was made in eye-space), however, normals only require the inverse of the upper 3x3 matrix while transforming positions requires the inverse of the whole matrix. This caused the interesting behavior that lights were more-or-less transformed properly, but not translated at all, which caused improper lighting with directional lights in particular. There was also another smaller bug affecting directional lights: when vertices are read, only the active component are read, the other ones are ignored, later, the transformation operations are set up to ignore the unset values, howver, in the case of lighting, we use the vertex in object space (that is, before it is transformed), and therefore were using uninitalized values; in particular w. --- opengl/libagl/array.cpp | 4 +++ opengl/libagl/light.cpp | 30 ++++++++--------- opengl/libagl/matrix.cpp | 83 ++++++++++++------------------------------------ 3 files changed, 39 insertions(+), 78 deletions(-) diff --git a/opengl/libagl/array.cpp b/opengl/libagl/array.cpp index 8fa7566aacc0..3e9c6a542709 100644 --- a/opengl/libagl/array.cpp +++ b/opengl/libagl/array.cpp @@ -951,6 +951,8 @@ void compileElement__generic(ogles_context_t* c, v->index = first; first &= vertex_cache_t::INDEX_MASK; const GLubyte* vp = c->arrays.vertex.element(first); + v->obj.z = 0; + v->obj.w = 0x10000; c->arrays.vertex.fetch(c, v->obj.v, vp); c->arrays.mvp_transform(&c->transforms.mvp, &v->clip, &v->obj); c->arrays.perspective(c, v); @@ -966,6 +968,8 @@ void compileElements__generic(ogles_context_t* c, do { v->flags = 0; v->index = first++; + v->obj.z = 0; + v->obj.w = 0x10000; c->arrays.vertex.fetch(c, v->obj.v, vp); c->arrays.mvp_transform(mvp, &v->clip, &v->obj); c->arrays.perspective(c, v); diff --git a/opengl/libagl/light.cpp b/opengl/libagl/light.cpp index bc9449c0bdef..8ae32cc0f0e1 100644 --- a/opengl/libagl/light.cpp +++ b/opengl/libagl/light.cpp @@ -38,13 +38,14 @@ static void lightVertex(ogles_context_t* c, vertex_t* v); static void lightVertexMaterial(ogles_context_t* c, vertex_t* v); static inline void vscale3(GLfixed* d, const GLfixed* m, GLfixed s); -static inline void vsub3w(GLfixed* d, const GLfixed* a, const GLfixed* b); static __attribute__((noinline)) void vnorm3(GLfixed* d, const GLfixed* a); static inline void vsa3(GLfixed* d, const GLfixed* m, GLfixed s, const GLfixed* a); +static inline void vss3(GLfixed* d, + const GLfixed* m, GLfixed s, const GLfixed* a); static inline void vmla3(GLfixed* d, const GLfixed* m0, const GLfixed* m1, const GLfixed* a); static inline void vmul3(GLfixed* d, @@ -151,18 +152,10 @@ void vsa3(GLfixed* d, const GLfixed* m, GLfixed s, const GLfixed* a) { } static inline -void vsub3w(GLfixed* d, const GLfixed* a, const GLfixed* b) { - const GLfixed wa = a[3]; - const GLfixed wb = b[3]; - if (ggl_likely(wa == wb)) { - d[0] = a[0] - b[0]; - d[1] = a[1] - b[1]; - d[2] = a[2] - b[2]; - } else { - d[0] = gglMulSubx(a[0], wb, gglMulx(b[0], wa)); - d[1] = gglMulSubx(a[1], wb, gglMulx(b[1], wa)); - d[2] = gglMulSubx(a[2], wb, gglMulx(b[2], wa)); - } +void vss3(GLfixed* d, const GLfixed* m, GLfixed s, const GLfixed* a) { + d[0] = gglMulSubx(m[0], s, a[0]); + d[1] = gglMulSubx(m[1], s, a[1]); + d[2] = gglMulSubx(m[2], s, a[2]); } static inline @@ -227,7 +220,7 @@ static inline void validate_light_mvi(ogles_context_t* c) const int i = 31 - gglClz(en); en &= ~(1<lighting.lights[i]; - c->transforms.mvui.point3(&c->transforms.mvui, + c->transforms.mvui.point4(&c->transforms.mvui, &l.objPosition, &l.position); vnorm3(l.normalizedObjPosition.v, l.objPosition.v); } @@ -348,7 +341,11 @@ void lightVertex(ogles_context_t* c, vertex_t* v) vec4_t n; c->arrays.normal.fetch(c, n.v, c->arrays.normal.element(v->index & vertex_cache_t::INDEX_MASK)); - if (c->transforms.rescaleNormals == GL_NORMALIZE) + + // TODO: right now we handle GL_RESCALE_NORMALS as if ti were + // GL_NORMALIZE. We could optimize this by scaling mvui + // appropriately instead. + if (c->transforms.rescaleNormals) vnorm3(n.v, n.v); const material_t& material = c->lighting.front; @@ -365,7 +362,8 @@ void lightVertex(ogles_context_t* c, vertex_t* v) // compute vertex-to-light vector if (ggl_unlikely(l.position.w)) { - vsub3w(d.v, l.objPosition.v, v->obj.v); + // lightPos/1.0 - vertex/vertex.w == lightPos*vertex.w - vertex + vss3(d.v, l.objPosition.v, v->obj.w, v->obj.v); sqDist = dot3(d.v, d.v); vscale3(d.v, d.v, gglSqrtRecipx(sqDist)); } else { diff --git a/opengl/libagl/matrix.cpp b/opengl/libagl/matrix.cpp index f175cdad6e6a..0b68dc06f5e3 100644 --- a/opengl/libagl/matrix.cpp +++ b/opengl/libagl/matrix.cpp @@ -55,7 +55,7 @@ static void normal__nop(transform_t const*, vec4_t* c, vec4_t const* o); static void point2__generic(transform_t const*, vec4_t* c, vec4_t const* o); static void point3__generic(transform_t const*, vec4_t* c, vec4_t const* o); static void point4__generic(transform_t const*, vec4_t* c, vec4_t const* o); -static void normal__generic(transform_t const*, vec4_t* c, vec4_t const* o); +static void point4__mvui(transform_t const*, vec4_t* c, vec4_t const* o); // ---------------------------------------------------------------------------- #if 0 @@ -209,7 +209,8 @@ void mvui_transform_t::picker() { flags = 0; ops = OP_ALL; - point3 = normal__generic; + point3 = point4__mvui; + point4 = point4__mvui; } void transform_t::dump(const char* what) @@ -596,66 +597,19 @@ void transform_state_t::update_mvit() void transform_state_t::update_mvui() { + GLfloat r[16]; const GLfloat* const mv = modelview.top().elements(); - - /* - When transforming normals, we can use the upper 3x3 matrix, see: - http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node26.html - */ - // Also note that: - // l(obj) = tr(M).l(eye) for infinite light - // l(obj) = inv(M).l(eye) for local light - - const uint32_t ops = modelview.top_ops() & ~OP_TRANSLATE; - if (ggl_likely((!(ops & ~OP_ROTATE)) || - (rescaleNormals && modelview.isRigidBody()))) { - // if the modelview matrix is a rigid body transformation - // (translation, rotation, uniform scaling), then we can bypass - // the inverse by transposing the matrix. - GLfloat rescale = 1.0f; - if (rescaleNormals == GL_RESCALE_NORMAL) { - if (!(ops & ~OP_UNIFORM_SCALE)) { - rescale = reciprocalf(mv[I(0,0)]); - } else { - rescale = rsqrtf( - sqrf(mv[I(2,0)]) + sqrf(mv[I(2,1)]) + sqrf(mv[I(2,2)])); - } - } - GLfixed* const x = mvui.matrix.m; - for (int i=0 ; i<3 ; i++) { - x[I(i,0)] = gglFloatToFixed(mv[I(0,i)] * rescale); - x[I(i,1)] = gglFloatToFixed(mv[I(1,i)] * rescale); - x[I(i,2)] = gglFloatToFixed(mv[I(2,i)] * rescale); - } - mvui.picker(); - return; - } - - GLfloat r[3][3]; - r[0][0] = det22(mv[I(1,1)], mv[I(2,1)], mv[I(1,2)], mv[I(2,2)]); - r[0][1] =ndet22(mv[I(0,1)], mv[I(2,1)], mv[I(0,2)], mv[I(2,2)]); - r[0][2] = det22(mv[I(0,1)], mv[I(1,1)], mv[I(0,2)], mv[I(1,2)]); - r[1][0] =ndet22(mv[I(1,0)], mv[I(2,0)], mv[I(1,2)], mv[I(2,2)]); - r[1][1] = det22(mv[I(0,0)], mv[I(2,0)], mv[I(0,2)], mv[I(2,2)]); - r[1][2] =ndet22(mv[I(0,0)], mv[I(1,0)], mv[I(0,2)], mv[I(1,2)]); - r[2][0] = det22(mv[I(1,0)], mv[I(2,0)], mv[I(1,1)], mv[I(2,1)]); - r[2][1] =ndet22(mv[I(0,0)], mv[I(2,0)], mv[I(0,1)], mv[I(2,1)]); - r[2][2] = det22(mv[I(0,0)], mv[I(1,0)], mv[I(0,1)], mv[I(1,1)]); - - GLfloat rdet; - if (rescaleNormals == GL_RESCALE_NORMAL) { - rdet = rsqrtf(sqrf(r[0][2]) + sqrf(r[1][2]) + sqrf(r[2][2])); - } else { - rdet = reciprocalf( - r[0][0]*mv[I(0,0)] + r[0][1]*mv[I(1,0)] + r[0][2]*mv[I(2,0)]); - } + // TODO: we need a faster invert, especially for when the modelview + // is a rigid-body matrix + invert(r, mv); GLfixed* const x = mvui.matrix.m; - for (int i=0 ; i<3 ; i++) { - x[I(i,0)] = gglFloatToFixed(r[i][0] * rdet); - x[I(i,1)] = gglFloatToFixed(r[i][1] * rdet); - x[I(i,2)] = gglFloatToFixed(r[i][2] * rdet); + for (int i=0 ; i<4 ; i++) { + x[I(i,0)] = gglFloatToFixed(r[I(i,0)]); + x[I(i,1)] = gglFloatToFixed(r[I(i,1)]); + x[I(i,2)] = gglFloatToFixed(r[I(i,2)]); + x[I(i,4)] = gglFloatToFixed(r[I(i,3)]); } mvui.picker(); } @@ -783,14 +737,19 @@ void point4__generic(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) { lhs->w = mla4(rx, m[ 3], ry, m[ 7], rz, m[11], rw, m[15]); } -void normal__generic(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) { +void point4__mvui(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) { + // this used for transforming light positions back to object space. + // Lights have 3 components positions, so w is always 1. + // however, it is used as a switch for directional lights, so we need + // to preserve it. const GLfixed* const m = mx->matrix.m; const GLfixed rx = rhs->x; const GLfixed ry = rhs->y; const GLfixed rz = rhs->z; - lhs->x = mla3(rx, m[ 0], ry, m[ 4], rz, m[ 8]); - lhs->y = mla3(rx, m[ 1], ry, m[ 5], rz, m[ 9]); - lhs->z = mla3(rx, m[ 2], ry, m[ 6], rz, m[10]); + lhs->x = mla3a(rx, m[ 0], ry, m[ 4], rz, m[ 8], m[12]); + lhs->y = mla3a(rx, m[ 1], ry, m[ 5], rz, m[ 9], m[13]); + lhs->z = mla3a(rx, m[ 2], ry, m[ 6], rz, m[10], m[14]); + lhs->w = rhs->w; } -- cgit v1.2.3-59-g8ed1b From ab5742dd63f4e62ee0f55f786854c024ef8c5bb4 Mon Sep 17 00:00:00 2001 From: Evan Millar Date: Tue, 2 Jun 2009 16:21:45 -0700 Subject: Adds "is_primary" and "is_super_primary" columns to DataColumns. Replaces the "primary" values stored in generic data fields with standard "is_primary" and "is_super_primary" fields in the DataColumns class. This makes it much easier to watch for changes to these fields and enforce the uniqueness of primary fields. --- core/java/android/provider/ContactsContract.java | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 835a5f646e51..62e95e8ab749 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -290,6 +290,19 @@ public final class ContactsContract { */ public static final String CONTACT_ID = "contact_id"; + /** + * Whether this is the primary entry of its kind for the contact it belongs to + *

    Type: INTEGER (if set, non-0 means true)

    + */ + public static final String IS_PRIMARY = "is_primary"; + + /** + * Whether this is the primary entry of its kind for the aggregate it belongs to. Any data + * record that is "super primary" must also be "primary". + *

    Type: INTEGER (if set, non-0 means true)

    + */ + public static final String IS_SUPER_PRIMARY = "is_super_primary"; + /** Generic data column, the meaning is {@link #MIMETYPE} specific */ public static final String DATA1 = "data1"; /** Generic data column, the meaning is {@link #MIMETYPE} specific */ @@ -409,12 +422,6 @@ public final class ContactsContract { *

    Type: TEXT

    */ public static final String LABEL = "data3"; - - /** - * Whether this is the primary entry of its kind for the contact it belongs to - *

    Type: INTEGER (if set, non-0 means true)

    - */ - public static final String ISPRIMARY = "data4"; } /** @@ -642,12 +649,6 @@ public final class ContactsContract { *

    Type: TEXT

    */ public static final String TITLE = "data4"; - - /** - * Whether this is the primary organization - *

    Type: INTEGER (if set, non-0 means true)

    - */ - public static final String ISPRIMARY = "data5"; } /** -- cgit v1.2.3-59-g8ed1b From eaeb663bcd7a82b654954b42663232cbd7bef7e7 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Wed, 3 Jun 2009 15:16:10 -0700 Subject: Track activity foreground CPU usage for battery stats. Track the foreground CPU time of an activity so that we can tell if apps are spending more time in the background compared to foreground. Update power profile values for screen backlight and GPS. Fix some javadoc bugs (milliseconds vs. microseconds). --- core/java/android/os/BatteryStats.java | 23 +++++--- .../com/android/internal/os/BatteryStatsImpl.java | 63 ++++++++++++++++++++-- core/res/res/xml/power_profile_default.xml | 3 +- services/java/com/android/server/ProcessStats.java | 16 +++++- .../android/server/am/ActivityManagerService.java | 33 +++++++++++- .../java/com/android/server/am/HistoryRecord.java | 1 + 6 files changed, 123 insertions(+), 16 deletions(-) diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 358a5463b04b..528def5c4011 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -307,6 +307,13 @@ public abstract class BatteryStats implements Parcelable { * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. */ public abstract int getStarts(int which); + + /** + * Returns the cpu time spent in microseconds while the process was in the foreground. + * @param which one of STATS_TOTAL, STATS_LAST, STATS_CURRENT or STATS_UNPLUGGED + * @return foreground cpu time in microseconds + */ + public abstract long getForegroundTime(int which); } /** @@ -364,7 +371,7 @@ public abstract class BatteryStats implements Parcelable { public abstract int getStartCount(); /** - * Returns the time in milliseconds that the screen has been on while the device was + * Returns the time in microseconds that the screen has been on while the device was * running on battery. * * {@hide} @@ -384,7 +391,7 @@ public abstract class BatteryStats implements Parcelable { public static final int NUM_SCREEN_BRIGHTNESS_BINS = 5; /** - * Returns the time in milliseconds that the screen has been on with + * Returns the time in microseconds that the screen has been on with * the given brightness * * {@hide} @@ -395,7 +402,7 @@ public abstract class BatteryStats implements Parcelable { public abstract int getInputEventCount(int which); /** - * Returns the time in milliseconds that the phone has been on while the device was + * Returns the time in microseconds that the phone has been on while the device was * running on battery. * * {@hide} @@ -415,7 +422,7 @@ public abstract class BatteryStats implements Parcelable { public static final int NUM_SIGNAL_STRENGTH_BINS = 5; /** - * Returns the time in milliseconds that the phone has been running with + * Returns the time in microseconds that the phone has been running with * the given signal strength. * * {@hide} @@ -443,7 +450,7 @@ public abstract class BatteryStats implements Parcelable { public static final int NUM_DATA_CONNECTION_TYPES = 5; /** - * Returns the time in milliseconds that the phone has been running with + * Returns the time in microseconds that the phone has been running with * the given data connection. * * {@hide} @@ -460,7 +467,7 @@ public abstract class BatteryStats implements Parcelable { public abstract int getPhoneDataConnectionCount(int dataType, int which); /** - * Returns the time in milliseconds that wifi has been on while the device was + * Returns the time in microseconds that wifi has been on while the device was * running on battery. * * {@hide} @@ -468,7 +475,7 @@ public abstract class BatteryStats implements Parcelable { public abstract long getWifiOnTime(long batteryRealtime, int which); /** - * Returns the time in milliseconds that wifi has been on and the driver has + * Returns the time in microseconds that wifi has been on and the driver has * been in the running state while the device was running on battery. * * {@hide} @@ -476,7 +483,7 @@ public abstract class BatteryStats implements Parcelable { public abstract long getWifiRunningTime(long batteryRealtime, int which); /** - * Returns the time in milliseconds that bluetooth has been on while the device was + * Returns the time in microseconds that bluetooth has been on while the device was * running on battery. * * {@hide} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 51f3b025f84d..99a381c5ae13 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -53,7 +53,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 37; + private static final int VERSION = 38; private final File mFile; private final File mBackupFile; @@ -1342,11 +1342,13 @@ public final class BatteryStatsImpl extends BatteryStats { public Map getPackageStats() { return mPackageStats; } - + + @Override public int getUid() { return mUid; } - + + @Override public long getTcpBytesReceived(int which) { if (which == STATS_LAST) { return mLoadedTcpBytesReceived; @@ -1365,7 +1367,8 @@ public final class BatteryStatsImpl extends BatteryStats { return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0 ? (NetStat.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0); } - + + @Override public long getTcpBytesSent(int which) { if (which == STATS_LAST) { return mLoadedTcpBytesSent; @@ -1754,7 +1757,8 @@ public final class BatteryStatsImpl extends BatteryStats { public Timer getSensorTime() { return mTimer; } - + + @Override public int getHandle() { return mHandle; } @@ -1779,6 +1783,11 @@ public final class BatteryStatsImpl extends BatteryStats { */ int mStarts; + /** + * Amount of time the process was running in the foreground. + */ + long mForegroundTime; + /** * The amount of user time loaded from a previous save. */ @@ -1794,6 +1803,11 @@ public final class BatteryStatsImpl extends BatteryStats { */ int mLoadedStarts; + /** + * The amount of foreground time loaded from a previous save. + */ + long mLoadedForegroundTime; + /** * The amount of user time loaded from the previous run. */ @@ -1809,6 +1823,11 @@ public final class BatteryStatsImpl extends BatteryStats { */ int mLastStarts; + /** + * The amount of foreground time loaded from the previous run + */ + long mLastForegroundTime; + /** * The amount of user time when last unplugged. */ @@ -1824,6 +1843,11 @@ public final class BatteryStatsImpl extends BatteryStats { */ int mUnpluggedStarts; + /** + * The amount of foreground time since unplugged. + */ + long mUnpluggedForegroundTime; + Proc() { mUnpluggables.add(this); } @@ -1832,6 +1856,7 @@ public final class BatteryStatsImpl extends BatteryStats { mUnpluggedUserTime = mUserTime; mUnpluggedSystemTime = mSystemTime; mUnpluggedStarts = mStarts; + mUnpluggedForegroundTime = mForegroundTime; } public void plug(long batteryUptime, long batteryRealtime) { @@ -1843,30 +1868,38 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(mUserTime); out.writeLong(mSystemTime); + out.writeLong(mForegroundTime); out.writeInt(mStarts); out.writeLong(mLoadedUserTime); out.writeLong(mLoadedSystemTime); + out.writeLong(mLoadedForegroundTime); out.writeInt(mLoadedStarts); out.writeLong(mLastUserTime); out.writeLong(mLastSystemTime); + out.writeLong(mLastForegroundTime); out.writeInt(mLastStarts); out.writeLong(mUnpluggedUserTime); out.writeLong(mUnpluggedSystemTime); + out.writeLong(mUnpluggedForegroundTime); out.writeInt(mUnpluggedStarts); } void readFromParcelLocked(Parcel in) { mUserTime = in.readLong(); mSystemTime = in.readLong(); + mForegroundTime = in.readLong(); mStarts = in.readInt(); mLoadedUserTime = in.readLong(); mLoadedSystemTime = in.readLong(); + mLoadedForegroundTime = in.readLong(); mLoadedStarts = in.readInt(); mLastUserTime = in.readLong(); mLastSystemTime = in.readLong(); + mLastForegroundTime = in.readLong(); mLastStarts = in.readInt(); mUnpluggedUserTime = in.readLong(); mUnpluggedSystemTime = in.readLong(); + mUnpluggedForegroundTime = in.readLong(); mUnpluggedStarts = in.readInt(); } @@ -1879,6 +1912,10 @@ public final class BatteryStatsImpl extends BatteryStats { mSystemTime += stime; } + public void addForegroundTimeLocked(long ttime) { + mForegroundTime += ttime; + } + public void incStartsLocked() { mStarts++; } @@ -1915,6 +1952,22 @@ public final class BatteryStatsImpl extends BatteryStats { return val; } + @Override + public long getForegroundTime(int which) { + long val; + if (which == STATS_LAST) { + val = mLastForegroundTime; + } else { + val = mForegroundTime; + if (which == STATS_CURRENT) { + val -= mLoadedForegroundTime; + } else if (which == STATS_UNPLUGGED) { + val -= mUnpluggedForegroundTime; + } + } + return val; + } + @Override public int getStarts(int which) { int val; diff --git a/core/res/res/xml/power_profile_default.xml b/core/res/res/xml/power_profile_default.xml index d265b46ef62c..ceecb1a7e790 100644 --- a/core/res/res/xml/power_profile_default.xml +++ b/core/res/res/xml/power_profile_default.xml @@ -22,7 +22,7 @@ 30 103 5 - 144 + 114 23 200 200 @@ -33,4 +33,5 @@ 100 3 175 + 120 diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index 55adabbfa7a0..58f8980c004d 100644 --- a/services/java/com/android/server/ProcessStats.java +++ b/services/java/com/android/server/ProcessStats.java @@ -54,7 +54,10 @@ public class ProcessStats { PROC_SPACE_TERM|PROC_OUT_LONG // 14: stime }; + /** Stores user time and system time in 100ths of a second. */ private final long[] mProcessStatsData = new long[2]; + /** Stores user time and system time in 100ths of a second. */ + private final long[] mSinglePidStatsData = new long[2]; private static final int[] PROCESS_FULL_STATS_FORMAT = new int[] { PROC_SPACE_TERM, @@ -418,7 +421,18 @@ public class ProcessStats { return pids; } - + + public long getCpuTimeForPid(int pid) { + final String statFile = "/proc/" + pid + "/stat"; + final long[] statsData = mSinglePidStatsData; + if (Process.readProcFile(statFile, PROCESS_STATS_FORMAT, + null, statsData, null)) { + long time = statsData[0] + statsData[1]; + return time; + } + return 0; + } + final public int getLastUserTime() { return mRelUserTime; } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 3b26cb78d721..965079067c32 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -62,6 +62,7 @@ import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Uri; +import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -1438,7 +1439,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (mProcessStatsThread) { final long now = SystemClock.uptimeMillis(); boolean haveNewCpuStats = false; - + if (MONITOR_CPU_USAGE && mLastCpuTime < (now-MONITOR_CPU_MIN_TIME)) { mLastCpuTime = now; @@ -2063,6 +2064,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (prev != null) { prev.resumeKeyDispatchingLocked(); } + + if (prev.app != null && prev.cpuTimeAtResume > 0 && mBatteryStatsService.isOnBattery()) { + long diff = 0; + synchronized (mProcessStatsThread) { + diff = mProcessStats.getCpuTimeForPid(prev.app.pid) - prev.cpuTimeAtResume; + } + if (diff > 0) { + BatteryStatsImpl bsi = mBatteryStatsService.getActiveStatistics(); + synchronized (bsi) { + BatteryStatsImpl.Uid.Proc ps = + bsi.getProcessStatsLocked(prev.info.applicationInfo.uid, + prev.info.packageName); + if (ps != null) { + ps.addForegroundTimeLocked(diff); + } + } + } + } + prev.cpuTimeAtResume = 0; // reset it } /** @@ -2095,6 +2115,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen next.resumeKeyDispatchingLocked(); ensureActivitiesVisibleLocked(null, 0); mWindowManager.executeAppTransition(); + + // Mark the point when the activity is resuming + // TODO: To be more accurate, the mark should be before the onCreate, + // not after the onResume. But for subsequent starts, onResume is fine. + if (next.app != null) { + synchronized (mProcessStatsThread) { + next.cpuTimeAtResume = mProcessStats.getCpuTimeForPid(next.app.pid); + } + } else { + next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process + } } /** diff --git a/services/java/com/android/server/am/HistoryRecord.java b/services/java/com/android/server/am/HistoryRecord.java index 1789687973fa..944ea02dbf79 100644 --- a/services/java/com/android/server/am/HistoryRecord.java +++ b/services/java/com/android/server/am/HistoryRecord.java @@ -66,6 +66,7 @@ class HistoryRecord extends IApplicationToken.Stub { int theme; // resource identifier of activity's theme. TaskRecord task; // the task this is in. long startTime; // when we starting launching this activity + long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity Configuration configuration; // configuration activity was last running in HistoryRecord resultTo; // who started this entry, so will get our reply final String resultWho; // additional identifier for use by resultTo. -- cgit v1.2.3-59-g8ed1b From 701f5164c1230cc1416b1a1f3b0091ca68f6caec Mon Sep 17 00:00:00 2001 From: Suchi Amalapurapu Date: Wed, 3 Jun 2009 15:47:55 -0700 Subject: Grant permissions to older package when deleting an updated system application. When a system app gets updated, the permissions are granted to the new pkg. Similary when this updated pkg(from data partition) gets removed, the older pkg from system partition is restored. but the permissions are'nt being granted explicitly and so the restore fails. This fix addresses specific bugs related to uninstall of updated system apps. These code paths will be revisited later but this fix is needed for OTA's that might fall back to older versions of system apps. --- services/java/com/android/server/PackageManagerService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 8da40acf9372..6a2e62fd1259 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -4147,6 +4147,7 @@ class PackageManagerService extends IPackageManager.Stub { return false; } synchronized (mPackages) { + grantPermissionsLP(newPkg, true); mSettings.writeLP(); } return true; -- cgit v1.2.3-59-g8ed1b From 53003de64e1d2b8a4ed4433d5192b540653d79ec Mon Sep 17 00:00:00 2001 From: Guang Zhu Date: Wed, 3 Jun 2009 16:01:58 -0700 Subject: Skip empty lines in test url list. --- .../DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java index e63aa95b558f..22c458cf926a 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java @@ -55,6 +55,9 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2 Date: Wed, 3 Jun 2009 16:04:54 -0700 Subject: Generalize bitmap support and add remaining GL formats. Fix bug in command fifo looping case. --- libs/rs/rsAllocation.cpp | 98 +++++++++++++++++++++++++++++++++--------- libs/rs/rsElement.cpp | 104 +++++++++++++++++++++++++++++++++++++++++++++ libs/rs/rsElement.h | 2 + libs/rs/rsLocklessFifo.cpp | 10 +++-- libs/rs/rsUtils.h | 12 ++++-- 5 files changed, 198 insertions(+), 28 deletions(-) diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp index 7b8bc8034ff2..3a01a7531782 100644 --- a/libs/rs/rsAllocation.cpp +++ b/libs/rs/rsAllocation.cpp @@ -47,6 +47,7 @@ Allocation::Allocation(const Type *type) Allocation::~Allocation() { + LOGE("Allocation %p destryed", this); } void Allocation::setCpuWritable(bool) @@ -77,6 +78,13 @@ void Allocation::uploadToTexture(uint32_t lodOffset) //LOGE("uploadToTexture %i, lod %i", mTextureID, lodOffset); + GLenum type = mType->getElement()->getGLType(); + GLenum format = mType->getElement()->getGLFormat(); + + if (!type || !format) { + return; + } + if (!mTextureID) { glGenTextures(1, &mTextureID); } @@ -87,9 +95,9 @@ void Allocation::uploadToTexture(uint32_t lodOffset) adapt.setLOD(lod+lodOffset); uint16_t * ptr = static_cast(adapt.getElement(0,0)); - glTexImage2D(GL_TEXTURE_2D, lod, GL_RGB, - adapt.getDimX(), adapt.getDimY(), - 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, ptr); + glTexImage2D(GL_TEXTURE_2D, lod, format, + adapt.getDimX(), adapt.getDimY(), + 0, format, type, ptr); } } @@ -121,7 +129,7 @@ void Allocation::subData(uint32_t xoff, uint32_t count, const void *data) memcpy(ptr, data, count * eSize); } -void Allocation::subData(uint32_t xoff, uint32_t yoff, +void Allocation::subData(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h, const void *data) { uint32_t eSize = mType->getElementSizeBytes(); @@ -147,7 +155,7 @@ void Allocation::subData(uint32_t xoff, uint32_t yoff, uint32_t zoff, ///////////////// -// +// namespace android { @@ -192,7 +200,7 @@ void rsi_AllocationDestroy(Context *rsc, RsAllocation) { } -static void mip(const Adapter2D &out, const Adapter2D &in) +static void mip565(const Adapter2D &out, const Adapter2D &in) { uint32_t w = out.getDimX(); uint32_t h = out.getDimY(); @@ -203,7 +211,26 @@ static void mip(const Adapter2D &out, const Adapter2D &in) const uint16_t *i2 = static_cast(in.getElement(0, y*2+1)); for (uint32_t x=0; x < h; x++) { - *oPtr = rsBoxFilter565(i1[0], i1[2], i2[0], i2[1]); + *oPtr = rsBoxFilter565(i1[0], i1[1], i2[0], i2[1]); + oPtr ++; + i1 += 2; + i2 += 2; + } + } +} + +static void mip8888(const Adapter2D &out, const Adapter2D &in) +{ + uint32_t w = out.getDimX(); + uint32_t h = out.getDimY(); + + for (uint32_t y=0; y < w; y++) { + uint32_t *oPtr = static_cast(out.getElement(0, y)); + const uint32_t *i1 = static_cast(in.getElement(0, y*2)); + const uint32_t *i2 = static_cast(in.getElement(0, y*2+1)); + + for (uint32_t x=0; x < h; x++) { + *oPtr = rsBoxFilter8888(i1[0], i1[1], i2[0], i2[1]); oPtr ++; i1 += 2; i2 += 2; @@ -255,21 +282,25 @@ static void elementConverter_8888_to_565(void *dst, const void *src, uint32_t co static ElementConverter_t pickConverter(RsElementPredefined dstFmt, RsElementPredefined srcFmt) { - if ((dstFmt == RS_ELEMENT_RGB_565) && + if ((dstFmt == RS_ELEMENT_RGB_565) && (srcFmt == RS_ELEMENT_RGB_565)) { return elementConverter_cpy_16; } - if ((dstFmt == RS_ELEMENT_RGB_565) && + if ((dstFmt == RS_ELEMENT_RGB_565) && (srcFmt == RS_ELEMENT_RGB_888)) { return elementConverter_888_to_565; } - if ((dstFmt == RS_ELEMENT_RGB_565) && + if ((dstFmt == RS_ELEMENT_RGB_565) && (srcFmt == RS_ELEMENT_RGBA_8888)) { return elementConverter_8888_to_565; } + if ((dstFmt == RS_ELEMENT_RGBA_8888) && + (srcFmt == RS_ELEMENT_RGBA_8888)) { + return elementConverter_cpy_32; + } LOGE("pickConverter, unsuported combo"); return 0; @@ -303,7 +334,7 @@ RsAllocation rsi_AllocationCreateFromBitmap(Context *rsc, uint32_t w, uint32_t h for(uint32_t lod=0; lod < (texAlloc->getType()->getLODCount() -1); lod++) { adapt.setLOD(lod); adapt2.setLOD(lod + 1); - mip(adapt2, adapt); + mip565(adapt2, adapt); } } @@ -312,6 +343,8 @@ RsAllocation rsi_AllocationCreateFromBitmap(Context *rsc, uint32_t w, uint32_t h RsAllocation rsi_AllocationCreateFromFile(Context *rsc, const char *file, bool genMips) { + bool use32bpp = false; + typedef struct _Win3xBitmapHeader { uint16_t type; @@ -351,7 +384,11 @@ RsAllocation rsi_AllocationCreateFromFile(Context *rsc, const char *file, bool g int32_t texWidth = rsHigherPow2(hdr.width); int32_t texHeight = rsHigherPow2(hdr.height); - rsi_TypeBegin(rsc, rsi_ElementGetPredefined(rsc, RS_ELEMENT_RGB_565)); + if (use32bpp) { + rsi_TypeBegin(rsc, rsi_ElementGetPredefined(rsc, RS_ELEMENT_RGBA_8888)); + } else { + rsi_TypeBegin(rsc, rsi_ElementGetPredefined(rsc, RS_ELEMENT_RGB_565)); + } rsi_TypeAdd(rsc, RS_DIMENSION_X, texWidth); rsi_TypeAdd(rsc, RS_DIMENSION_Y, texHeight); if (genMips) { @@ -372,14 +409,29 @@ RsAllocation rsi_AllocationCreateFromFile(Context *rsc, const char *file, bool g Adapter2D adapt(texAlloc); uint8_t * fileInBuf = new uint8_t[texWidth * 3]; uint32_t yOffset = (hdr.width - hdr.height) / 2; - uint16_t *tmp = static_cast(adapt.getElement(0, yOffset)); - - for (int y=0; y < hdr.height; y++) { - fseek(f, hdr.offset + (y*hdr.width*3), SEEK_SET); - fread(fileInBuf, 1, hdr.width * 3, f); - for(int x=0; x < hdr.width; x++) { - *tmp = rs888to565(fileInBuf[x*3], fileInBuf[x*3 + 1], fileInBuf[x*3 + 2]); - tmp++; + + if (use32bpp) { + uint8_t *tmp = static_cast(adapt.getElement(0, yOffset)); + for (int y=0; y < hdr.height; y++) { + fseek(f, hdr.offset + (y*hdr.width*3), SEEK_SET); + fread(fileInBuf, 1, hdr.width * 3, f); + for(int x=0; x < hdr.width; x++) { + tmp[0] = fileInBuf[x*3 + 2]; + tmp[1] = fileInBuf[x*3 + 1]; + tmp[2] = fileInBuf[x*3]; + tmp[3] = 0xff; + tmp += 4; + } + } + } else { + uint16_t *tmp = static_cast(adapt.getElement(0, yOffset)); + for (int y=0; y < hdr.height; y++) { + fseek(f, hdr.offset + (y*hdr.width*3), SEEK_SET); + fread(fileInBuf, 1, hdr.width * 3, f); + for(int x=0; x < hdr.width; x++) { + *tmp = rs888to565(fileInBuf[x*3 + 2], fileInBuf[x*3 + 1], fileInBuf[x*3]); + tmp++; + } } } @@ -391,7 +443,11 @@ RsAllocation rsi_AllocationCreateFromFile(Context *rsc, const char *file, bool g for(uint32_t lod=0; lod < (texAlloc->getType()->getLODCount() -1); lod++) { adapt.setLOD(lod); adapt2.setLOD(lod + 1); - mip(adapt2, adapt); + if (use32bpp) { + mip8888(adapt2, adapt); + } else { + mip565(adapt2, adapt); + } } } diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp index bd11f725d535..5a44f474a986 100644 --- a/libs/rs/rsElement.cpp +++ b/libs/rs/rsElement.cpp @@ -16,6 +16,8 @@ #include "rsContext.h" +#include + using namespace android; using namespace android::renderscript; @@ -235,6 +237,108 @@ size_t Element::getComponentOffsetBits(uint32_t componentNumber) const return offset; } +uint32_t Element::getGLType() const +{ + int bits[4]; + + if (mComponentCount > 4) { + return 0; + } + + for (uint32_t ct=0; ct < mComponentCount; ct++) { + bits[ct] = mComponents[ct]->getBits(); + if (mComponents[ct]->getType() != Component::UNSIGNED) { + return 0; + } + if (!mComponents[ct]->getIsNormalized()) { + return 0; + } + } + + switch(mComponentCount) { + case 1: + if (bits[0] == 8) { + return GL_UNSIGNED_BYTE; + } + return 0; + case 2: + if ((bits[0] == 8) && + (bits[1] == 8)) { + return GL_UNSIGNED_BYTE; + } + return 0; + case 3: + if ((bits[0] == 8) && + (bits[1] == 8) && + (bits[2] == 8)) { + return GL_UNSIGNED_BYTE; + } + if ((bits[0] == 5) && + (bits[1] == 6) && + (bits[2] == 5)) { + return GL_UNSIGNED_SHORT_5_6_5; + } + return 0; + case 4: + if ((bits[0] == 8) && + (bits[1] == 8) && + (bits[2] == 8) && + (bits[3] == 8)) { + return GL_UNSIGNED_BYTE; + } + if ((bits[0] == 4) && + (bits[1] == 4) && + (bits[2] == 4) && + (bits[3] == 4)) { + return GL_UNSIGNED_SHORT_4_4_4_4; + } + if ((bits[0] == 5) && + (bits[1] == 5) && + (bits[2] == 5) && + (bits[3] == 1)) { + return GL_UNSIGNED_SHORT_5_5_5_1; + } + } + return 0; +} + +uint32_t Element::getGLFormat() const +{ + switch(mComponentCount) { + case 1: + if (mComponents[0]->getKind() == Component::ALPHA) { + return GL_ALPHA; + } + if (mComponents[0]->getKind() == Component::LUMINANCE) { + return GL_LUMINANCE; + } + break; + case 2: + if ((mComponents[0]->getKind() == Component::LUMINANCE) && + (mComponents[1]->getKind() == Component::ALPHA)) { + return GL_LUMINANCE_ALPHA; + } + break; + case 3: + if ((mComponents[0]->getKind() == Component::RED) && + (mComponents[1]->getKind() == Component::GREEN) && + (mComponents[2]->getKind() == Component::BLUE)) { + return GL_RGB; + } + break; + case 4: + if ((mComponents[0]->getKind() == Component::RED) && + (mComponents[1]->getKind() == Component::GREEN) && + (mComponents[2]->getKind() == Component::BLUE) && + (mComponents[3]->getKind() == Component::ALPHA)) { + return GL_RGBA; + } + break; + } + return 0; +} + + ElementState::ElementState() { } diff --git a/libs/rs/rsElement.h b/libs/rs/rsElement.h index 7852ffcb33ef..2434977dbc2a 100644 --- a/libs/rs/rsElement.h +++ b/libs/rs/rsElement.h @@ -36,6 +36,8 @@ public: void setComponent(uint32_t idx, Component *c); + uint32_t getGLType() const; + uint32_t getGLFormat() const; size_t getSizeBits() const; diff --git a/libs/rs/rsLocklessFifo.cpp b/libs/rs/rsLocklessFifo.cpp index 3f51e04c2c16..67ab434cf4df 100644 --- a/libs/rs/rsLocklessFifo.cpp +++ b/libs/rs/rsLocklessFifo.cpp @@ -74,6 +74,7 @@ uint32_t LocklessCommandFifo::getFreeSpace() const freeSpace = 0; } + //LOGE("free %i", freeSpace); return freeSpace; } @@ -85,8 +86,8 @@ bool LocklessCommandFifo::isEmpty() const void * LocklessCommandFifo::reserve(uint32_t sizeInBytes) { - // Add space for command header; - sizeInBytes += 4; + // Add space for command header and loop token; + sizeInBytes += 8; //dumpState("reserve"); if (getFreeSpace() < sizeInBytes) { @@ -153,16 +154,17 @@ void LocklessCommandFifo::next() void LocklessCommandFifo::makeSpace(uint32_t bytes) { + //dumpState("make space"); if ((mPut+bytes) > mEnd) { // Need to loop regardless of where get is. - while((mGet > mPut) && (mPut+4 >= mGet)) { + while((mGet > mPut) && (mBuffer+4 >= mGet)) { sleep(1); } // Toss in a reset then the normal wait for space will do the rest. reinterpret_cast(mPut)[0] = 0; reinterpret_cast(mPut)[1] = 0; - mPut += 4; + mPut = mBuffer; } // it will fit here so we just need to wait for space. diff --git a/libs/rs/rsUtils.h b/libs/rs/rsUtils.h index f40e2ced0129..5a43fb3f8e57 100644 --- a/libs/rs/rsUtils.h +++ b/libs/rs/rsUtils.h @@ -96,13 +96,19 @@ static inline uint16_t rs888to565(uint32_t r, uint32_t g, uint32_t b) static inline uint16_t rsBoxFilter565(uint16_t i1, uint16_t i2, uint16_t i3, uint16_t i4) { uint32_t r = ((i1 & 0x1f) + (i2 & 0x1f) + (i3 & 0x1f) + (i4 & 0x1f)); - uint32_t g = ((i1 >> 5) & 0x3f) + ((i2 >> 5) & 0x3f) + ((i3 >> 5) & 0x3f) + ((i1 >> 5) & 0x3f); + uint32_t g = ((i1 >> 5) & 0x3f) + ((i2 >> 5) & 0x3f) + ((i3 >> 5) & 0x3f) + ((i4 >> 5) & 0x3f); uint32_t b = ((i1 >> 11) + (i2 >> 11) + (i3 >> 11) + (i4 >> 11)); return (r >> 2) | ((g >> 2) << 5) | ((b >> 2) << 11); } - - +static inline uint32_t rsBoxFilter8888(uint32_t i1, uint32_t i2, uint32_t i3, uint32_t i4) +{ + uint32_t r = (i1 & 0xff) + (i2 & 0xff) + (i3 & 0xff) + (i4 & 0xff); + uint32_t g = ((i1 >> 8) & 0xff) + ((i2 >> 8) & 0xff) + ((i3 >> 8) & 0xff) + ((i4 >> 8) & 0xff); + uint32_t b = ((i1 >> 16) & 0xff) + ((i2 >> 16) & 0xff) + ((i3 >> 16) & 0xff) + ((i4 >> 16) & 0xff); + uint32_t a = ((i1 >> 24) & 0xff) + ((i2 >> 24) & 0xff) + ((i3 >> 24) & 0xff) + ((i4 >> 24) & 0xff); + return (r >> 2) | ((g >> 2) << 8) | ((b >> 2) << 16) | ((a >> 2) << 24); +} -- cgit v1.2.3-59-g8ed1b From e87b2f02761744520c841f536d3f2d7be97fcc91 Mon Sep 17 00:00:00 2001 From: Wei Huang Date: Tue, 2 Jun 2009 15:16:04 -0700 Subject: add Gservices settings for adaptive heartbeat parameters and wifi heartbeat interval. --- core/java/android/provider/Settings.java | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b4338596ef62..68e4329d988f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2566,6 +2566,32 @@ public final class Settings { public static final String GTALK_SERVICE_NOSYNC_HEARTBEAT_INTERVAL_MS = "gtalk_nosync_heartbeat_ping_interval_ms"; + /** + * The maximum heartbeat interval used while on the WIFI network. + */ + public static final String GTALK_SERVICE_WIFI_MAX_HEARTBEAT_INTERVAL_MS = + "gtalk_wifi_max_heartbeat_ping_interval_ms"; + + /** + * The minimum interval for how frequently we send heartbeat pings to the GTalk server. + */ + public static final String GTALK_SERVICE_MIN_HEARTBEAT_INTERVAL_MS = + "gtalk_min_heartbeat_ping_interval_ms"; + + /** + * The scale down factor used by adaptive heartbeat logic (to scale down the heartbeat + * interval) when the previous interval fails to get a response from the server. + */ + public static final String GTALK_SERVICE_ADAPTIVE_HEARTBEAT_SCALER = + "gtalk_adaptive_heartbeat_scaler"; + + /** + * The trigger for adaptively scaling down the heartbeat interval. This is the number of + * consecutive times we failed to get a server response for sending the heartbeat ping. + */ + public static final String GTALK_SERVICE_ADAPTIVE_HEARTBEAT_TRIGGER = + "gtalk_adaptive_heartbeat_trigger"; + /** * How long we wait to receive a heartbeat ping acknowledgement (or another packet) * from the GTalk server, before deeming the connection dead. -- cgit v1.2.3-59-g8ed1b From 9189cabb0b6c6c28232fe6f412b7ba7a37352a6a Mon Sep 17 00:00:00 2001 From: Mitsuru Oshima Date: Wed, 3 Jun 2009 11:19:12 -0700 Subject: * Moved supports-density tag under manifest * Refactored Compatibility code * Added CompatibilityInfo class * Removed getApplicationScale from Context * Added Resources#getCompatibilityInfo so that RootView can get the compatibility info w/o going through Context * Expandable support * Added expandable tag under manifest * Old application w/o expandable is given the default screen size ([320, 480] x density). * The non-expandable window is centered. --- api/current.xml | 21 +++ core/java/android/app/ActivityThread.java | 79 ++-------- core/java/android/app/ApplicationContext.java | 16 +- core/java/android/content/Context.java | 10 -- core/java/android/content/ContextWrapper.java | 8 - core/java/android/content/pm/ApplicationInfo.java | 11 +- core/java/android/content/pm/PackageManager.java | 6 + core/java/android/content/pm/PackageParser.java | 49 +++--- .../android/content/res/CompatibilityInfo.java | 102 +++++++++++++ core/java/android/content/res/Resources.java | 44 +++++- core/java/android/util/DisplayMetrics.java | 51 +++++-- core/java/android/view/SurfaceView.java | 66 ++++---- core/java/android/view/ViewRoot.java | 166 +++++++++++++-------- core/res/res/values/attrs_manifest.xml | 11 +- .../android/server/am/ActivityManagerService.java | 2 +- test-runner/android/test/mock/MockContext.java | 8 - .../android/layoutlib/bridge/BridgeContext.java | 8 - 17 files changed, 412 insertions(+), 246 deletions(-) create mode 100644 core/java/android/content/res/CompatibilityInfo.java diff --git a/api/current.xml b/api/current.xml index 9e9eaad45e0e..41f8c2ce5030 100644 --- a/api/current.xml +++ b/api/current.xml @@ -34943,6 +34943,16 @@ visibility="public" >
    + + + + wr = mActiveResources.get(appDir); @@ -181,23 +181,17 @@ public final class ActivityThread { if (assets.addAssetPath(appDir) == 0) { return null; } - DisplayMetrics metrics = getDisplayMetricsLocked(false); - // density used to load resources - // scaledDensity is calculated in Resources constructor - // - boolean usePreloaded = true; - - // TODO: use explicit flag to indicate the compatibility mode. - if (applicationScale != 1.0f) { - usePreloaded = false; - DisplayMetrics newMetrics = new DisplayMetrics(); - newMetrics.setTo(metrics); - float newDensity = metrics.density / applicationScale; - newMetrics.updateDensity(newDensity); - metrics = newMetrics; + ApplicationInfo appInfo; + try { + appInfo = getPackageManager().getApplicationInfo( + pkgInfo.getPackageName(), + PackageManager.GET_SUPPORTS_DENSITIES | PackageManager.GET_EXPANDABLE); + } catch (RemoteException e) { + throw new AssertionError(e); } //Log.i(TAG, "Resource:" + appDir + ", display metrics=" + metrics); - r = new Resources(assets, metrics, getConfiguration(), usePreloaded); + DisplayMetrics metrics = getDisplayMetricsLocked(false); + r = new Resources(assets, metrics, getConfiguration(), appInfo); //Log.i(TAG, "Created app resources " + r + ": " + r.getConfiguration()); // XXX need to remove entries when weak references go away mActiveResources.put(appDir, new WeakReference(r)); @@ -225,7 +219,6 @@ public final class ActivityThread { private Resources mResources; private ClassLoader mClassLoader; private Application mApplication; - private float mApplicationScale; private final HashMap> mReceivers = new HashMap>(); @@ -268,8 +261,6 @@ public final class ActivityThread { mClassLoader = mSystemContext.getClassLoader(); mResources = mSystemContext.getResources(); } - - mApplicationScale = -1.0f; } public PackageInfo(ActivityThread activityThread, String name, @@ -288,7 +279,6 @@ public final class ActivityThread { mIncludeCode = true; mClassLoader = systemContext.getClassLoader(); mResources = systemContext.getResources(); - mApplicationScale = systemContext.getApplicationScale(); } public String getPackageName() { @@ -299,45 +289,6 @@ public final class ActivityThread { return mSecurityViolation; } - public float getApplicationScale() { - if (mApplicationScale > 0.0f) { - return mApplicationScale; - } - DisplayMetrics metrics = mActivityThread.getDisplayMetricsLocked(false); - // Find out the density scale (relative to 160) of the supported density that - // is closest to the system's density. - try { - ApplicationInfo ai = getPackageManager().getApplicationInfo( - mPackageName, PackageManager.GET_SUPPORTS_DENSITIES); - - float appScale = -1.0f; - if (ai.supportsDensities != null) { - int minDiff = Integer.MAX_VALUE; - for (int density : ai.supportsDensities) { - int tmpDiff = (int) Math.abs(DisplayMetrics.DEVICE_DENSITY - density); - if (tmpDiff == 0) { - appScale = 1.0f; - break; - } - // prefer higher density (appScale>1.0), unless that's only option. - if (tmpDiff < minDiff && appScale < 1.0f) { - appScale = DisplayMetrics.DEVICE_DENSITY / density; - minDiff = tmpDiff; - } - } - } - if (appScale < 0.0f) { - mApplicationScale = metrics.density; - } else { - mApplicationScale = appScale; - } - } catch (RemoteException e) { - throw new AssertionError(e); - } - if (localLOGV) Log.v(TAG, "appScale=" + mApplicationScale + ", pkg=" + mPackageName); - return mApplicationScale; - } - /** * Gets the array of shared libraries that are listed as * used by the given package. @@ -495,7 +446,7 @@ public final class ActivityThread { public Resources getResources(ActivityThread mainThread) { if (mResources == null) { - mResources = mainThread.getTopLevelResources(mResDir, getApplicationScale()); + mResources = mainThread.getTopLevelResources(mResDir, this); } return mResources; } @@ -3606,8 +3557,6 @@ public final class ActivityThread { } mConfiguration.updateFrom(config); DisplayMetrics dm = getDisplayMetricsLocked(true); - DisplayMetrics appDm = new DisplayMetrics(); - appDm.setTo(dm); // set it for java, this also affects newly created Resources if (config.locale != null) { @@ -3627,11 +3576,7 @@ public final class ActivityThread { WeakReference v = it.next(); Resources r = v.get(); if (r != null) { - // keep the original density based on application cale. - appDm.updateDensity(r.getDisplayMetrics().density); - r.updateConfiguration(config, appDm); - // reset - appDm.setTo(dm); + r.updateConfiguration(config, dm); //Log.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index 2d6381a632df..98bbf7b82ddf 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -551,19 +551,6 @@ class ApplicationContext extends Context { } } - /** - * @hide - */ - @Override - public float getApplicationScale() { - if (mPackageInfo != null) { - return mPackageInfo.getApplicationScale(); - } else { - // same as system density - return 1.0f; - } - } - @Override public void setWallpaper(Bitmap bitmap) throws IOException { try { @@ -2028,8 +2015,7 @@ class ApplicationContext extends Context { ActivityThread.PackageInfo pi = mContext.mMainThread.getPackageInfoNoCheck(app); Resources r = mContext.mMainThread.getTopLevelResources( app.uid == Process.myUid() ? app.sourceDir - : app.publicSourceDir, - pi.getApplicationScale()); + : app.publicSourceDir, pi); if (r != null) { return r; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index c328d161499d..ec847a472d5f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -526,16 +526,6 @@ public abstract class Context { */ public abstract int getWallpaperDesiredMinimumHeight(); - /** - * Returns the scale in which the application will be drawn on the - * screen. This is usually 1.0f if the application supports the device's - * resolution/density. This will be 1.5f, for example, if the application - * that supports only 160 density runs on 240 density screen. - * - * @hide - */ - public abstract float getApplicationScale(); - /** * Change the current system wallpaper to a bitmap. The given bitmap is * converted to a PNG and stored as the wallpaper. On success, the intent diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 25b2caeb7279..36e1c340d1d9 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -419,12 +419,4 @@ public class ContextWrapper extends Context { throws PackageManager.NameNotFoundException { return mBase.createPackageContext(packageName, flags); } - - /** - * @hide - */ - @Override - public float getApplicationScale() { - return mBase.getApplicationScale(); - } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index f16eb74b6ff1..f3dfc5af938b 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -186,7 +186,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public int uid; - /** * The list of densities in DPI that application supprots. This * field is only set if the {@link PackageManager#GET_SUPPORTS_DENSITIES} flag was @@ -194,6 +193,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public int[] supportsDensities; + /** + * True when the application's window can be expanded over default window + * size in target density (320x480 for 1.0 density, 480x720 for 1.5 density etc) + */ + public boolean expandable = false; + /** * The minimum SDK version this application targets. It may run on earilier * versions, but it knows how to work with any new behavior added at this @@ -228,6 +233,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName); pw.println(prefix + "description=0x"+Integer.toHexString(descriptionRes)); pw.println(prefix + "supportsDensities=" + supportsDensities); + pw.println(prefix + "expandable=" + expandable); super.dumpBack(pw, prefix); } @@ -275,6 +281,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { manageSpaceActivityName = orig.manageSpaceActivityName; descriptionRes = orig.descriptionRes; supportsDensities = orig.supportsDensities; + expandable = orig.expandable; } @@ -307,6 +314,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString(backupAgentName); dest.writeInt(descriptionRes); dest.writeIntArray(supportsDensities); + dest.writeInt(expandable ? 1 : 0); } public static final Parcelable.Creator CREATOR @@ -338,6 +346,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { backupAgentName = source.readString(); descriptionRes = source.readInt(); supportsDensities = source.createIntArray(); + expandable = source.readInt() != 0; } /** diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a2c82e890e41..65783917f909 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -179,6 +179,12 @@ public abstract class PackageManager { */ public static final int MATCH_DEFAULT_ONLY = 0x00010000; + /** + * {@link ApplicationInfo} flag: return the + * {link ApplicationInfo#expandable} boolean flag of the package. + */ + public static final int GET_EXPANDABLE = 0x00020000; + /** * Permission check result: this is returned by {@link #checkPermission} * if the permission has been granted to the given package. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 212b59017160..e2c0fe69f345 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -835,6 +835,26 @@ public class PackageParser { + parser.getName(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; + + + } else if (tagName.equals("supports-density")) { + sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestSupportsDensity); + + int density = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestSupportsDensity_density, -1); + + sa.recycle(); + + if (density != -1 && !pkg.supportsDensityList.contains(density)) { + pkg.supportsDensityList.add(density); + } + + XmlUtils.skipCurrentTag(parser); + + } else if (tagName.equals("expandable")) { + pkg.expandable = true; + XmlUtils.skipCurrentTag(parser); } else { Log.w(TAG, "Bad element under : " + parser.getName()); @@ -866,7 +886,8 @@ public class PackageParser { pkg.usesLibraryFiles = new String[pkg.usesLibraries.size()]; pkg.usesLibraries.toArray(pkg.usesLibraryFiles); } - + // TODO: enable all density & expandable if target sdk is higher than donut + int size = pkg.supportsDensityList.size(); if (size > 0) { int densities[] = pkg.supportsDensities = new int[size]; @@ -1345,21 +1366,6 @@ public class PackageParser { XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals("supports-density")) { - sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.AndroidManifestSupportsDensity); - - int density = sa.getInteger( - com.android.internal.R.styleable.AndroidManifestSupportsDensity_density, -1); - - sa.recycle(); - - if (density != -1 && !owner.supportsDensityList.contains(density)) { - owner.supportsDensityList.add(density); - } - - XmlUtils.skipCurrentTag(parser); - } else { if (!RIGID_PARSER) { Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); @@ -2244,6 +2250,9 @@ public class PackageParser { public final ArrayList supportsDensityList = new ArrayList(); public int[] supportsDensities = null; + // If the application's window is expandable. + public boolean expandable; + // If this is a 3rd party app, this is the path of the zip file. public String mPath; @@ -2415,7 +2424,10 @@ public class PackageParser { return true; } if ((flags & PackageManager.GET_SUPPORTS_DENSITIES) != 0 - && p.supportsDensities != null) { + && p.supportsDensities != null) { + return true; + } + if ((flags & PackageManager.GET_EXPANDABLE) != 0) { return true; } return false; @@ -2438,6 +2450,9 @@ public class PackageParser { if ((flags & PackageManager.GET_SUPPORTS_DENSITIES) != 0) { ai.supportsDensities = p.supportsDensities; } + if ((flags & PackageManager.GET_EXPANDABLE) != 0) { + ai.expandable = p.expandable; + } return ai; } diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java new file mode 100644 index 000000000000..8a6a6f0390d2 --- /dev/null +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res; + +import android.content.pm.ApplicationInfo; +import android.util.DisplayMetrics; +import android.view.Gravity; + +/** + * CompatibilityInfo class keeps the information about compatibility mode that the application is + * running under. + * + * {@hide} + */ +public class CompatibilityInfo { + /** default compatibility info object for compatible applications */ + public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo(); + + /** + * The default width of the screen in portrait mode. + */ + public static final int DEFAULT_PORTRAIT_WIDTH = 320; + + /** + * The default height of the screen in portrait mode. + */ + public static final int DEFAULT_PORTRAIT_HEIGHT = 480; + + /** + * Application's scale. + */ + public final float mApplicationScale; + + /** + * Application's inverted scale. + */ + public final float mApplicationInvertedScale; + + /** + * + * A boolean flag to indicates that the application can expand over the original size. + */ + public final boolean mExpandable; + + /** + * A boolean flag to tell if the application needs scaling (when mApplicationScale != 1.0f) + */ + public final boolean mScalingRequired; + + public CompatibilityInfo(ApplicationInfo appInfo) { + mExpandable = appInfo.expandable; + float packageDensityScale = -1.0f; + if (appInfo.supportsDensities != null) { + int minDiff = Integer.MAX_VALUE; + for (int density : appInfo.supportsDensities) { + int tmpDiff = Math.abs(DisplayMetrics.DEVICE_DENSITY - density); + if (tmpDiff == 0) { + packageDensityScale = 1.0f; + break; + } + // prefer higher density (appScale>1.0), unless that's only option. + if (tmpDiff < minDiff && packageDensityScale < 1.0f) { + packageDensityScale = DisplayMetrics.DEVICE_DENSITY / (float) density; + minDiff = tmpDiff; + } + } + } + if (packageDensityScale > 0.0f) { + mApplicationScale = packageDensityScale; + } else { + mApplicationScale = DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY; + } + mApplicationInvertedScale = 1.0f / mApplicationScale; + mScalingRequired = mApplicationScale != 1.0f; + } + + private CompatibilityInfo() { + mApplicationScale = mApplicationInvertedScale = 1.0f; + mExpandable = true; + mScalingRequired = false; + } + + @Override + public String toString() { + return "CompatibilityInfo{scale=" + mApplicationScale + + ", expandable=" + mExpandable + "}"; + } +} diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 665e40ca362f..976b6189310f 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -22,6 +22,8 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.app.ActivityThread.PackageInfo; +import android.content.pm.ApplicationInfo; import android.graphics.Movie; import android.graphics.drawable.Drawable; import android.graphics.drawable.BitmapDrawable; @@ -84,7 +86,9 @@ public class Resources { private final Configuration mConfiguration = new Configuration(); /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics(); PluralRules mPluralRule; - + + private final CompatibilityInfo mCompatibilityInfo; + private static final SparseArray EMPTY_ARRAY = new SparseArray() { @Override public void put(int k, Object o) { @@ -126,23 +130,36 @@ public class Resources { */ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { - this(assets, metrics, config, true); + this(assets, metrics, config, null); } /** - * Create a resource with an additional flag for preloaded - * drawable cache. Used by {@link ActivityThread}. - * + * Creates a new Resources object with ApplicationInfo. + * + * @param assets Previously created AssetManager. + * @param metrics Current display metrics to consider when + * selecting/computing resource values. + * @param config Desired device configuration to consider when + * selecting/computing resource values (optional). + * @param appInfo this resource's application info. * @hide */ public Resources(AssetManager assets, DisplayMetrics metrics, - Configuration config, boolean usePreloadedCache) { + Configuration config, ApplicationInfo appInfo) { mAssets = assets; mConfiguration.setToDefaults(); mMetrics.setToDefaults(); + if (appInfo != null) { + mCompatibilityInfo = new CompatibilityInfo(appInfo); + if (DEBUG_CONFIG) { + Log.d(TAG, "compatibility for " + appInfo.packageName + " : " + mCompatibilityInfo); + } + } else { + mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + } updateConfiguration(config, metrics); assets.ensureStringBlocks(); - if (usePreloadedCache) { + if (!mCompatibilityInfo.mScalingRequired) { mPreloadedDrawables = sPreloadedDrawables; } else { mPreloadedDrawables = emptySparseArray(); @@ -1251,6 +1268,7 @@ public class Resources { } if (metrics != null) { mMetrics.setTo(metrics); + mMetrics.updateMetrics(mCompatibilityInfo, mConfiguration); } mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; @@ -1356,6 +1374,17 @@ public class Resources { public Configuration getConfiguration() { return mConfiguration; } + + /** + * Return the compatibility mode information for the application. + * The returned object should be treated as read-only. + * + * @return compatibility info. null if the app does not require compatibility mode. + * @hide + */ + public CompatibilityInfo getCompatibilityInfo() { + return mCompatibilityInfo; + } /** * Return a resource identifier for the given resource name. A fully @@ -1920,5 +1949,6 @@ public class Resources { updateConfiguration(null, null); mAssets.ensureStringBlocks(); mPreloadedDrawables = sPreloadedDrawables; + mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; } } diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index e4dd020e1a81..987be2b3ce2f 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -16,6 +16,8 @@ package android.util; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.os.*; @@ -101,17 +103,46 @@ public class DisplayMetrics { } /** - * Set the display metrics' density and update parameters depend on it. - * @hide + * Update the display metrics based on the compatibility info and configuration. + * {@hide} */ - public void updateDensity(float newDensity) { - float ratio = newDensity / density; - density = newDensity; - scaledDensity = density; - widthPixels *= ratio; - heightPixels *= ratio; - xdpi *= ratio; - ydpi *= ratio; + public void updateMetrics(CompatibilityInfo compatibilityInfo, Configuration configuration) { + if (compatibilityInfo.mScalingRequired) { + float invertedRatio = compatibilityInfo.mApplicationInvertedScale; + density *= invertedRatio; + scaledDensity *= invertedRatio; + xdpi *= invertedRatio; + ydpi *= invertedRatio; + widthPixels *= invertedRatio; + heightPixels *= invertedRatio; + } + if (!compatibilityInfo.mExpandable) { + // Note: this assume that configuration is updated before calling + // updateMetrics method. + int defaultWidth; + int defaultHeight; + switch (configuration.orientation) { + case Configuration.ORIENTATION_LANDSCAPE: { + defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density); + defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density); + break; + } + case Configuration.ORIENTATION_UNDEFINED: + case Configuration.ORIENTATION_PORTRAIT: + case Configuration.ORIENTATION_SQUARE: + default: { + defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density); + defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density); + } + } + // adjust the size only when the device's screen is bigger. + if (defaultWidth < widthPixels) { + widthPixels = defaultWidth; + } + if (defaultHeight < heightPixels) { + heightPixels = defaultHeight; + } + } } public String toString() { diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 0dc257027065..082cca24e15f 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -17,6 +17,7 @@ package android.view; import android.content.Context; +import android.content.res.CompatibilityInfo; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.PorterDuff; @@ -137,28 +138,24 @@ public class SurfaceView extends View { int mFormat = -1; int mType = -1; final Rect mSurfaceFrame = new Rect(); - private final float mAppScale; - private final float mAppScaleInverted; + private final CompatibilityInfo mCompatibilityInfo; public SurfaceView(Context context) { super(context); setWillNotDraw(true); - mAppScale = context.getApplicationScale(); - mAppScaleInverted = 1.0f / mAppScale; + mCompatibilityInfo = context.getResources().getCompatibilityInfo(); } public SurfaceView(Context context, AttributeSet attrs) { super(context, attrs); setWillNotDraw(true); - mAppScale = context.getApplicationScale(); - mAppScaleInverted = 1.0f / mAppScale; + mCompatibilityInfo = context.getResources().getCompatibilityInfo(); } public SurfaceView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setWillNotDraw(true); - mAppScale = context.getApplicationScale(); - mAppScaleInverted = 1.0f / mAppScale; + mCompatibilityInfo = context.getResources().getCompatibilityInfo(); } /** @@ -261,9 +258,9 @@ public class SurfaceView extends View { public boolean dispatchTouchEvent(MotionEvent event) { // SurfaceView uses pre-scaled size unless fixed size is requested. This hook // scales the event back to the pre-scaled coordinates for such surface. - if (mRequestedWidth < 0 && mAppScale != 1.0f) { + if (mRequestedWidth < 0 && mCompatibilityInfo.mScalingRequired) { MotionEvent scaledBack = MotionEvent.obtain(event); - scaledBack.scale(mAppScale); + scaledBack.scale(mCompatibilityInfo.mApplicationScale); try { return super.dispatchTouchEvent(scaledBack); } finally { @@ -300,6 +297,7 @@ public class SurfaceView extends View { if (!mHaveFrame) { return; } + float appScale = mCompatibilityInfo.mApplicationScale; int myWidth = mRequestedWidth; if (myWidth <= 0) myWidth = getWidth(); @@ -307,9 +305,9 @@ public class SurfaceView extends View { if (myHeight <= 0) myHeight = getHeight(); // Use original size for surface unless fixed size is requested. - if (mRequestedWidth <= 0) { - myWidth *= mAppScale; - myHeight *= mAppScale; + if (mRequestedWidth <= 0 && mCompatibilityInfo.mScalingRequired) { + myWidth *= appScale; + myHeight *= appScale; } getLocationInWindow(mLocation); @@ -337,11 +335,11 @@ public class SurfaceView extends View { mFormat = mRequestedFormat; mType = mRequestedType; - // Scaling window's layout here beause mLayout is not used elsewhere. - mLayout.x = (int) (mLeft * mAppScale); - mLayout.y = (int) (mTop * mAppScale); - mLayout.width = (int) (getWidth() * mAppScale); - mLayout.height = (int) (getHeight() * mAppScale); + // Scaling window's layout here because mLayout is not used elsewhere. + mLayout.x = (int) (mLeft * appScale); + mLayout.y = (int) (mTop * appScale); + mLayout.width = (int) (getWidth() * appScale); + mLayout.height = (int) (getHeight() * appScale); mLayout.format = mRequestedFormat; mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_SCALED @@ -367,14 +365,18 @@ public class SurfaceView extends View { mSurfaceLock.lock(); mDrawingStopped = !visible; + final int relayoutResult = mSession.relayout( mWindow, mLayout, mWidth, mHeight, visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets, mVisibleInsets, mSurface); - mContentInsets.scale(mAppScaleInverted); - mVisibleInsets.scale(mAppScaleInverted); - mWinFrame.scale(mAppScaleInverted); + if (mCompatibilityInfo.mScalingRequired) { + float invertedScale = mCompatibilityInfo.mApplicationInvertedScale; + mContentInsets.scale(invertedScale); + mVisibleInsets.scale(invertedScale); + mWinFrame.scale(invertedScale); + } if (localLOGV) Log.i(TAG, "New surface: " + mSurface + ", vis=" + visible + ", frame=" + mWinFrame); @@ -444,23 +446,23 @@ public class SurfaceView extends View { private static class MyWindow extends IWindow.Stub { private final WeakReference mSurfaceView; - private final float mAppScale; - private final float mAppScaleInverted; + private final CompatibilityInfo mCompatibilityInfo; public MyWindow(SurfaceView surfaceView) { mSurfaceView = new WeakReference(surfaceView); - mAppScale = surfaceView.getContext().getApplicationScale(); - mAppScaleInverted = 1.0f / mAppScale; + mCompatibilityInfo = surfaceView.getContext().getResources().getCompatibilityInfo(); } public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets, boolean reportDraw) { SurfaceView surfaceView = mSurfaceView.get(); - float scale = mAppScaleInverted; - w *= scale; - h *= scale; - coveredInsets.scale(scale); - visibleInsets.scale(scale); + if (mCompatibilityInfo.mScalingRequired) { + float scale = mCompatibilityInfo.mApplicationInvertedScale; + w *= scale; + h *= scale; + coveredInsets.scale(scale); + visibleInsets.scale(scale); + } if (surfaceView != null) { if (localLOGV) Log.v( @@ -624,7 +626,9 @@ public class SurfaceView extends View { Canvas c = null; if (!mDrawingStopped && mWindow != null) { Rect frame = dirty != null ? dirty : mSurfaceFrame; - frame.scale(mAppScale); + if (mCompatibilityInfo.mScalingRequired) { + frame.scale(mCompatibilityInfo.mApplicationScale); + } try { c = mSurface.lockCanvas(frame); } catch (Exception e) { diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 7cd65e229d65..d8bab56a79d0 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -30,6 +30,7 @@ import android.os.Process; import android.os.SystemProperties; import android.util.AndroidRuntimeException; import android.util.Config; +import android.util.DisplayMetrics; import android.util.Log; import android.util.EventLog; import android.util.SparseArray; @@ -40,6 +41,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.content.pm.PackageManager; +import android.content.res.CompatibilityInfo; import android.content.Context; import android.app.ActivityManagerNative; import android.Manifest; @@ -125,9 +127,8 @@ public final class ViewRoot extends Handler implements ViewParent, int mHeight; Rect mDirty; // will be a graphics.Region soon boolean mIsAnimating; - // TODO: change these to scalar class. - private float mAppScale; - private float mAppScaleInverted; // = 1.0f / mAppScale + + private CompatibilityInfo mCompatibilityInfo; private int[] mWindowLayoutParamsBackup = null; final View.AttachInfo mAttachInfo; @@ -386,12 +387,15 @@ public final class ViewRoot extends Handler implements ViewParent, synchronized (this) { if (mView == null) { mView = view; - mAppScale = mView.getContext().getApplicationScale(); - if (mAppScale != 1.0f) { + mWindowAttributes.copyFrom(attrs); + mCompatibilityInfo = + mView.getContext().getResources().getCompatibilityInfo(); + if (mCompatibilityInfo.mScalingRequired) { mWindowLayoutParamsBackup = new int[4]; } - mAppScaleInverted = 1.0f / mAppScale; - mWindowAttributes.copyFrom(attrs); + if (!mCompatibilityInfo.mExpandable) { + adjustWindowAttributesForCompatibleMode(mWindowAttributes); + } mSoftInputMode = attrs.softInputMode; mWindowAttributesChanged = true; mAttachInfo.mRootView = view; @@ -406,9 +410,8 @@ public final class ViewRoot extends Handler implements ViewParent, // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); - try { - res = sWindowSession.add(mWindow, attrs, + res = sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets); } catch (RemoteException e) { mAdded = false; @@ -417,7 +420,10 @@ public final class ViewRoot extends Handler implements ViewParent, unscheduleTraversals(); throw new RuntimeException("Adding window failed", e); } - mAttachInfo.mContentInsets.scale(mAppScaleInverted); + if (mCompatibilityInfo.mScalingRequired) { + mAttachInfo.mContentInsets.scale( + mCompatibilityInfo.mApplicationInvertedScale); + } mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingVisibleInsets.set(0, 0, 0, 0); if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow); @@ -529,13 +535,13 @@ public final class ViewRoot extends Handler implements ViewParent, public void invalidateChild(View child, Rect dirty) { checkThread(); if (LOCAL_LOGV) Log.v(TAG, "Invalidate child: " + dirty); - if (mCurScrollY != 0 || mAppScale != 1.0f) { + if (mCurScrollY != 0 || mCompatibilityInfo.mScalingRequired) { mTempRect.set(dirty); if (mCurScrollY != 0) { mTempRect.offset(0, -mCurScrollY); } - if (mAppScale != 1.0f) { - mTempRect.scale(mAppScale); + if (mCompatibilityInfo.mScalingRequired) { + mTempRect.scale(mCompatibilityInfo.mApplicationScale); } dirty = mTempRect; } @@ -615,6 +621,8 @@ public final class ViewRoot extends Handler implements ViewParent, boolean viewVisibilityChanged = mViewVisibility != viewVisibility || mNewSurfaceNeeded; + float appScale = mCompatibilityInfo.mApplicationScale; + WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; @@ -625,9 +633,10 @@ public final class ViewRoot extends Handler implements ViewParent, fullRedrawNeeded = true; mLayoutRequested = true; - Display d = new Display(0); - desiredWindowWidth = (int) (d.getWidth() * mAppScaleInverted); - desiredWindowHeight = (int) (d.getHeight() * mAppScaleInverted); + DisplayMetrics packageMetrics = + mView.getContext().getResources().getDisplayMetrics(); + desiredWindowWidth = packageMetrics.widthPixels; + desiredWindowHeight = packageMetrics.heightPixels; // For the very first time, tell the view hierarchy that it // is attached to the window. Note that at this point the surface @@ -696,9 +705,10 @@ public final class ViewRoot extends Handler implements ViewParent, || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowResizesToFitContent = true; - Display d = new Display(0); - desiredWindowWidth = (int) (d.getWidth() * mAppScaleInverted); - desiredWindowHeight = (int) (d.getHeight() * mAppScaleInverted); + DisplayMetrics packageMetrics = + mView.getContext().getResources().getDisplayMetrics(); + desiredWindowWidth = packageMetrics.widthPixels; + desiredWindowHeight = packageMetrics.heightPixels; } } @@ -878,7 +888,7 @@ public final class ViewRoot extends Handler implements ViewParent, mHeight = frame.height(); if (initialized) { - mGlCanvas.setViewport((int) (mWidth * mAppScale), (int) (mHeight * mAppScale)); + mGlCanvas.setViewport((int) (mWidth * appScale), (int) (mHeight * appScale)); } boolean focusChangedDueToTouchMode = ensureTouchModeLocally( @@ -968,11 +978,7 @@ public final class ViewRoot extends Handler implements ViewParent, mTmpLocation[1] + host.mBottom - host.mTop); host.gatherTransparentRegion(mTransparentRegion); - - // TODO: scale the region, like: - // Region uses native methods. We probabl should have ScalableRegion class. - - // Region does not have equals method ? + mTransparentRegion.scale(appScale); if (!mTransparentRegion.equals(mPreviousTransparentRegion)) { mPreviousTransparentRegion.set(mTransparentRegion); // reconfigure window manager @@ -983,7 +989,6 @@ public final class ViewRoot extends Handler implements ViewParent, } } - if (DBG) { System.out.println("======================================"); System.out.println("performTraversals -- after setFrame"); @@ -1003,10 +1008,11 @@ public final class ViewRoot extends Handler implements ViewParent, givenContent.left = givenContent.top = givenContent.right = givenContent.bottom = givenVisible.left = givenVisible.top = givenVisible.right = givenVisible.bottom = 0; - insets.contentInsets.scale(mAppScale); - insets.visibleInsets.scale(mAppScale); - attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets); + if (mCompatibilityInfo.mScalingRequired) { + insets.contentInsets.scale(appScale); + insets.visibleInsets.scale(appScale); + } if (insetsPending || !mLastGivenInsets.equals(insets)) { mLastGivenInsets.set(insets); try { @@ -1154,6 +1160,8 @@ public final class ViewRoot extends Handler implements ViewParent, mCurScrollY = yoff; fullRedrawNeeded = true; } + float appScale = mCompatibilityInfo.mApplicationScale; + boolean scalingRequired = mCompatibilityInfo.mScalingRequired; Rect dirty = mDirty; if (mUseGL) { @@ -1169,12 +1177,11 @@ public final class ViewRoot extends Handler implements ViewParent, mAttachInfo.mIgnoreDirtyState = true; mView.mPrivateFlags |= View.DRAWN; - float scale = mAppScale; int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); try { canvas.translate(0, -yoff); - if (scale != 1.0f) { - canvas.scale(scale, scale); + if (scalingRequired) { + canvas.scale(appScale, appScale); } mView.draw(canvas); if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { @@ -1206,8 +1213,8 @@ public final class ViewRoot extends Handler implements ViewParent, } if (fullRedrawNeeded) { - mAttachInfo.mIgnoreDirtyState = true; - dirty.union(0, 0, (int) (mWidth * mAppScale), (int) (mHeight * mAppScale)); + mAttachInfo.mIgnoreDirtyState = true; + dirty.union(0, 0, (int) (mWidth * appScale), (int) (mHeight * appScale)); } if (DEBUG_ORIENTATION || DEBUG_DRAW) { @@ -1215,7 +1222,8 @@ public final class ViewRoot extends Handler implements ViewParent, + mWindowAttributes.getTitle() + ": dirty={" + dirty.left + "," + dirty.top + "," + dirty.right + "," + dirty.bottom + "} surface=" - + surface + " surface.isValid()=" + surface.isValid()); + + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" + + appScale + ", width=" + mWidth + ", height=" + mHeight); } Canvas canvas; @@ -1272,18 +1280,16 @@ public final class ViewRoot extends Handler implements ViewParent, mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); mView.mPrivateFlags |= View.DRAWN; - float scale = mAppScale; if (DEBUG_DRAW) { Context cxt = mView.getContext(); Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + - ", appScale=" + mAppScale); + ", metrics=" + mView.getContext().getResources().getDisplayMetrics()); } - int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); try { canvas.translate(0, -yoff); - if (scale != 1.0f) { - // re-scale this - canvas.scale(scale, scale); + if (scalingRequired) { + canvas.scale(appScale, appScale); } mView.draw(canvas); } finally { @@ -1586,8 +1592,8 @@ public final class ViewRoot extends Handler implements ViewParent, } else { didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE; } - if (event != null) { - event.scale(mAppScaleInverted); + if (event != null && mCompatibilityInfo.mScalingRequired) { + event.scale(mCompatibilityInfo.mApplicationInvertedScale); } try { @@ -1709,8 +1715,9 @@ public final class ViewRoot extends Handler implements ViewParent, if (mGlWanted && !mUseGL) { initializeGL(); if (mGlCanvas != null) { - mGlCanvas.setViewport((int) (mWidth * mAppScale), - (int) (mHeight * mAppScale)); + float appScale = mCompatibilityInfo.mApplicationScale; + mGlCanvas.setViewport( + (int) (mWidth * appScale), (int) (mHeight * appScale)); } } } @@ -1914,8 +1921,8 @@ public final class ViewRoot extends Handler implements ViewParent, } else { didFinish = false; } - if (event != null) { - event.scale(mAppScaleInverted); + if (event != null && mCompatibilityInfo.mScalingRequired) { + event.scale(mCompatibilityInfo.mApplicationInvertedScale); } if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event); @@ -2345,27 +2352,59 @@ public final class ViewRoot extends Handler implements ViewParent, private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { - boolean restore = false; - if (params != null && mAppScale != 1.0f) { + float appScale = mCompatibilityInfo.mApplicationScale; + boolean scalingRequired = mCompatibilityInfo.mScalingRequired; + + if (params != null && !mCompatibilityInfo.mExpandable) { + adjustWindowAttributesForCompatibleMode(params); + } + if (params != null && scalingRequired) { restore = true; - params.scale(mAppScale, mWindowLayoutParamsBackup); + params.scale(appScale, mWindowLayoutParamsBackup); } int relayoutResult = sWindowSession.relayout( mWindow, params, - (int) (mView.mMeasuredWidth * mAppScale), - (int) (mView.mMeasuredHeight * mAppScale), + (int) (mView.mMeasuredWidth * appScale), + (int) (mView.mMeasuredHeight * appScale), viewVisibility, insetsPending, mWinFrame, mPendingContentInsets, mPendingVisibleInsets, mSurface); if (restore) { params.restore(mWindowLayoutParamsBackup); } - - mPendingContentInsets.scale(mAppScaleInverted); - mPendingVisibleInsets.scale(mAppScaleInverted); - mWinFrame.scale(mAppScaleInverted); + if (scalingRequired) { + float invertedScale = mCompatibilityInfo.mApplicationInvertedScale; + mPendingContentInsets.scale(invertedScale); + mPendingVisibleInsets.scale(invertedScale); + mWinFrame.scale(invertedScale); + } return relayoutResult; } + + /** + * Adjust the window's layout parameter for compatibility mode. It replaces FILL_PARENT + * with the default window size, and centers if the window wanted to fill + * horizontally. + * + * @param attrs the window's layout params to adjust + */ + private void adjustWindowAttributesForCompatibleMode(WindowManager.LayoutParams attrs) { + // fix app windows only + if (attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + DisplayMetrics metrics = mView.getContext().getResources().getDisplayMetrics(); + // TODO: improve gravity logic + if (attrs.width == ViewGroup.LayoutParams.FILL_PARENT) { + attrs.width = metrics.widthPixels; + attrs.gravity |= Gravity.CENTER_HORIZONTAL; + } + if (attrs.height == ViewGroup.LayoutParams.FILL_PARENT) { + attrs.height = metrics.heightPixels; + } + if (DEBUG_LAYOUT) { + Log.d(TAG, "Attributes fixed for compatibility : " + attrs); + } + } + } /** * {@inheritDoc} @@ -2470,11 +2509,16 @@ public final class ViewRoot extends Handler implements ViewParent, + " visibleInsets=" + visibleInsets.toShortString() + " reportDraw=" + reportDraw); Message msg = obtainMessage(reportDraw ? RESIZED_REPORT :RESIZED); - - coveredInsets.scale(mAppScaleInverted); - visibleInsets.scale(mAppScaleInverted); - msg.arg1 = (int) (w * mAppScaleInverted); - msg.arg2 = (int) (h * mAppScaleInverted); + if (mCompatibilityInfo.mScalingRequired) { + float invertedScale = mCompatibilityInfo.mApplicationInvertedScale; + coveredInsets.scale(invertedScale); + visibleInsets.scale(invertedScale); + msg.arg1 = (int) (w * invertedScale); + msg.arg2 = (int) (h * invertedScale); + } else { + msg.arg1 = w; + msg.arg2 = h; + } msg.obj = new Rect[] { new Rect(coveredInsets), new Rect(visibleInsets) }; sendMessage(msg); } diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 817a56637ea4..0a2d2088992b 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -810,12 +810,19 @@ - + {@link #AndroidManifest manifest} tag. --> + + + + + + + com.google.android.providers.genie/.GenieLauncher + com.android.googlesearch/.GoogleSearch + com.android.websearch/.Search.1 + diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java index bdf67ba86ac7..a46f07dbb875 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java @@ -361,7 +361,8 @@ public class SearchablesTest extends AndroidTestCase { @Override public List queryIntentActivities(Intent intent, int flags) { assertNotNull(intent); - assertEquals(intent.getAction(), Intent.ACTION_SEARCH); + assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH) + || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)); switch (mSearchablesMode) { case SEARCHABLES_PASSTHROUGH: return mRealPackageManager.queryIntentActivities(intent, flags); -- cgit v1.2.3-59-g8ed1b From 6262ae5c9df44c0673cebaeaf7f655094f5b5485 Mon Sep 17 00:00:00 2001 From: Ben Murdoch Date: Fri, 17 Apr 2009 13:21:53 +0100 Subject: Implement handling of console messages from WebCore. Default implementation in WebChromeClient is to do nothing. --- core/java/android/webkit/CallbackProxy.java | 30 +++++++++++++++++++++++++++ core/java/android/webkit/WebChromeClient.java | 11 ++++++++++ core/java/android/webkit/WebViewCore.java | 10 +++++++++ 3 files changed, 51 insertions(+) diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 2fb696456ced..c40704410155 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -100,6 +100,7 @@ class CallbackProxy extends Handler { private static final int SWITCH_OUT_HISTORY = 125; private static final int EXCEEDED_DATABASE_QUOTA = 126; private static final int JS_TIMEOUT = 127; + private static final int ADD_MESSAGE_TO_CONSOLE = 128; // Message triggered by the client to resume execution private static final int NOTIFY = 200; @@ -581,6 +582,13 @@ class CallbackProxy extends Handler { case SWITCH_OUT_HISTORY: mWebView.switchOutDrawHistory(); break; + + case ADD_MESSAGE_TO_CONSOLE: + String message = msg.getData().getString("message"); + String sourceID = msg.getData().getString("sourceID"); + int lineNumber = msg.getData().getInt("lineNumber"); + mWebChromeClient.addMessageToConsole(message, lineNumber, sourceID); + break; } } @@ -1086,6 +1094,28 @@ class CallbackProxy extends Handler { sendMessage(exceededQuota); } + /** + * Called by WebViewCore when we have a message to be added to the JavaScript + * error console. Sends a message to the Java side with the details. + * @param message The message to add to the console. + * @param lineNumber The lineNumber of the source file on which the error + * occurred. + * @param sourceID The filename of the source file in which the error + * occurred. + * @hide pending API counsel. + */ + public void addMessageToConsole(String message, int lineNumber, String sourceID) { + if (mWebChromeClient == null) { + return; + } + + Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE); + msg.getData().putString("message", message); + msg.getData().putString("sourceID", sourceID); + msg.getData().putInt("lineNumber", lineNumber); + sendMessage(msg); + } + /** * @hide pending API council approval */ diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index bd64f897fe15..754b1d90adf4 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -189,4 +189,15 @@ public class WebChromeClient { public boolean onJsTimeout() { return true; } + + /** + * Add a JavaScript error message to the console. Clients should override + * this to process the log message as they see fit. + * @param message The error message to report. + * @param lineNumber The line number of the error. + * @param sourceID The name of the source file that caused the error. + * @hide pending API council. + */ + public void addMessageToConsole(String message, int lineNumber, String sourceID) { + } } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index de142ae5565a..f4b99b9e3bc7 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -226,6 +226,16 @@ final class WebViewCore { return mSettings; } + /** + * Add an error message to the client's console. + * @param message The message to add + * @param lineNumber the line on which the error occurred + * @param sourceID the filename of the source that caused the error. + */ + protected void addMessageToConsole(String message, int lineNumber, String sourceID) { + mCallbackProxy.addMessageToConsole(message, lineNumber, sourceID); + } + /** * Invoke a javascript alert. * @param message The message displayed in the alert. -- cgit v1.2.3-59-g8ed1b From 0e74aa0f7ed90d46e0bdde02bf9b7b29c6b95bd8 Mon Sep 17 00:00:00 2001 From: Satish Sampath Date: Fri, 5 Jun 2009 15:04:32 +0100 Subject: Fix broken Searchables unit tests. The newly added code was using methods which were not overridden by the unit test, fixed now. --- .../com/android/unit_tests/SearchablesTest.java | 35 +++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java index a46f07dbb875..6b56e6ce4b62 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java @@ -20,6 +20,7 @@ import android.app.SearchManager; 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.ProviderInfo; @@ -306,6 +307,14 @@ public class SearchablesTest extends AndroidTestCase { throws PackageManager.NameNotFoundException { return mRealContext.createPackageContext(packageName, flags); } + + /** + * Message broadcast. Pass through for now. + */ + @Override + public void sendBroadcast(Intent intent) { + mRealContext.sendBroadcast(intent); + } } /** @@ -376,7 +385,8 @@ public class SearchablesTest extends AndroidTestCase { @Override public ResolveInfo resolveActivity(Intent intent, int flags) { assertNotNull(intent); - assertEquals(intent.getAction(), SearchManager.INTENT_ACTION_GLOBAL_SEARCH); + assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH) + || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH)); switch (mSearchablesMode) { case SEARCHABLES_PASSTHROUGH: return mRealPackageManager.resolveActivity(intent, flags); @@ -439,6 +449,29 @@ public class SearchablesTest extends AndroidTestCase { throw new UnsupportedOperationException(); } } + + /** + * Get the activity information for a particular activity. + * + * @param name The name of the activity to find. + * @param flags Additional option flags. + * + * @return ActivityInfo Information about the activity, if found, else null. + */ + @Override + public ActivityInfo getActivityInfo(ComponentName name, int flags) + throws NameNotFoundException { + assertNotNull(name); + MoreAsserts.assertNotEqual(name, ""); + switch (mSearchablesMode) { + case SEARCHABLES_PASSTHROUGH: + return mRealPackageManager.getActivityInfo(name, flags); + case SEARCHABLES_MOCK_ZERO: + throw new NameNotFoundException(); + default: + throw new UnsupportedOperationException(); + } + } } } -- cgit v1.2.3-59-g8ed1b From 55e3d60da5626752ffe1d15150d35ccb8fa644e7 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 5 Jun 2009 14:56:35 -0700 Subject: break dependency on utils/ZipEntry.h and utils/ZipFile.h, get rid of inet_address.h and Socket.h which were not used --- include/utils/Socket.h | 80 --- include/utils/ZipEntry.h | 345 ----------- include/utils/ZipFile.h | 269 --------- include/utils/inet_address.h | 103 ---- libs/utils/Android.mk | 23 +- libs/utils/InetAddress.cpp | 236 -------- libs/utils/Socket.cpp | 388 ------------- libs/utils/ZipEntry.cpp | 696 ----------------------- libs/utils/ZipFile.cpp | 1296 ----------------------------------------- tools/aapt/AaptAssets.h | 2 +- tools/aapt/Android.mk | 5 +- tools/aapt/Command.cpp | 1 - tools/aapt/Main.cpp | 1 - tools/aapt/Main.h | 2 +- tools/aapt/Package.cpp | 1 - tools/aapt/ZipEntry.cpp | 696 +++++++++++++++++++++++ tools/aapt/ZipEntry.h | 345 +++++++++++ tools/aapt/ZipFile.cpp | 1297 ++++++++++++++++++++++++++++++++++++++++++ tools/aapt/ZipFile.h | 270 +++++++++ 19 files changed, 2615 insertions(+), 3441 deletions(-) delete mode 100644 include/utils/Socket.h delete mode 100644 include/utils/ZipEntry.h delete mode 100644 include/utils/ZipFile.h delete mode 100644 include/utils/inet_address.h delete mode 100644 libs/utils/InetAddress.cpp delete mode 100644 libs/utils/Socket.cpp delete mode 100644 libs/utils/ZipEntry.cpp delete mode 100644 libs/utils/ZipFile.cpp create mode 100644 tools/aapt/ZipEntry.cpp create mode 100644 tools/aapt/ZipEntry.h create mode 100644 tools/aapt/ZipFile.cpp create mode 100644 tools/aapt/ZipFile.h diff --git a/include/utils/Socket.h b/include/utils/Socket.h deleted file mode 100644 index 8b7f40617959..000000000000 --- a/include/utils/Socket.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2005 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. - */ - -// -// Socket class. Modeled after Java classes. -// -#ifndef _RUNTIME_SOCKET_H -#define _RUNTIME_SOCKET_H - -#include -#include - -namespace android { - -/* - * Basic socket class, needed to abstract away the differences between - * BSD sockets and WinSock. This establishes a streaming network - * connection (TCP/IP) to somebody. - */ -class Socket { -public: - Socket(void); - ~Socket(void); - - // Create a connection to somewhere. - // Return 0 on success. - int connect(const char* host, int port); - int connect(const InetAddress* addr, int port); - - - // Close the socket. Don't try to use this object again after - // calling this. Returns false on failure. - bool close(void); - - // If we created the socket without an address, we can use these - // to finish the connection. Returns 0 on success. - int bind(const SocketAddress& bindPoint); - int connect(const SocketAddress& endPoint); - - // Here we deviate from the traditional object-oriented fanciness - // and just provide read/write operators instead of getters for - // objects that abstract a stream. - // - // Standard read/write semantics. - int read(void* buf, ssize_t len) const; - int write(const void* buf, ssize_t len) const; - - // This must be called once, at program startup. - static bool bootInit(void); - static void finalShutdown(void); - -private: - // Internal function that establishes a connection. - int doConnect(const InetSocketAddress& addr); - - unsigned long mSock; // holds SOCKET or int - - static bool mBootInitialized; -}; - - -// debug -- unit tests -void TestSockets(void); - -}; // namespace android - -#endif // _RUNTIME_SOCKET_H diff --git a/include/utils/ZipEntry.h b/include/utils/ZipEntry.h deleted file mode 100644 index e4698dfbbc47..000000000000 --- a/include/utils/ZipEntry.h +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -// -// Zip archive entries. -// -// The ZipEntry class is tightly meshed with the ZipFile class. -// -#ifndef __LIBS_ZIPENTRY_H -#define __LIBS_ZIPENTRY_H - -#include "Errors.h" - -#include -#include - -namespace android { - -class ZipFile; - -/* - * ZipEntry objects represent a single entry in a Zip archive. - * - * You can use one of these to get or set information about an entry, but - * there are no functions here for accessing the data itself. (We could - * tuck a pointer to the ZipFile in here for convenience, but that raises - * the likelihood of using ZipEntry objects after discarding the ZipFile.) - * - * File information is stored in two places: next to the file data (the Local - * File Header, and possibly a Data Descriptor), and at the end of the file - * (the Central Directory Entry). The two must be kept in sync. - */ -class ZipEntry { -public: - friend class ZipFile; - - ZipEntry(void) - : mDeleted(false), mMarked(false) - {} - ~ZipEntry(void) {} - - /* - * Returns "true" if the data is compressed. - */ - bool isCompressed(void) const { - return mCDE.mCompressionMethod != kCompressStored; - } - int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } - - /* - * Return the uncompressed length. - */ - off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } - - /* - * Return the compressed length. For uncompressed data, this returns - * the same thing as getUncompresesdLen(). - */ - off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } - - /* - * Return the absolute file offset of the start of the compressed or - * uncompressed data. - */ - off_t getFileOffset(void) const { - return mCDE.mLocalHeaderRelOffset + - LocalFileHeader::kLFHLen + - mLFH.mFileNameLength + - mLFH.mExtraFieldLength; - } - - /* - * Return the data CRC. - */ - unsigned long getCRC32(void) const { return mCDE.mCRC32; } - - /* - * Return file modification time in UNIX seconds-since-epoch. - */ - time_t getModWhen(void) const; - - /* - * Return the archived file name. - */ - const char* getFileName(void) const { return (const char*) mCDE.mFileName; } - - /* - * Application-defined "mark". Can be useful when synchronizing the - * contents of an archive with contents on disk. - */ - bool getMarked(void) const { return mMarked; } - void setMarked(bool val) { mMarked = val; } - - /* - * Some basic functions for raw data manipulation. "LE" means - * Little Endian. - */ - static inline unsigned short getShortLE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8); - } - static inline unsigned long getLongLE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); - } - static inline void putShortLE(unsigned char* buf, short val) { - buf[0] = (unsigned char) val; - buf[1] = (unsigned char) (val >> 8); - } - static inline void putLongLE(unsigned char* buf, long val) { - buf[0] = (unsigned char) val; - buf[1] = (unsigned char) (val >> 8); - buf[2] = (unsigned char) (val >> 16); - buf[3] = (unsigned char) (val >> 24); - } - - /* defined for Zip archives */ - enum { - kCompressStored = 0, // no compression - // shrunk = 1, - // reduced 1 = 2, - // reduced 2 = 3, - // reduced 3 = 4, - // reduced 4 = 5, - // imploded = 6, - // tokenized = 7, - kCompressDeflated = 8, // standard deflate - // Deflate64 = 9, - // lib imploded = 10, - // reserved = 11, - // bzip2 = 12, - }; - - /* - * Deletion flag. If set, the entry will be removed on the next - * call to "flush". - */ - bool getDeleted(void) const { return mDeleted; } - -protected: - /* - * Initialize the structure from the file, which is pointing at - * our Central Directory entry. - */ - status_t initFromCDE(FILE* fp); - - /* - * Initialize the structure for a new file. We need the filename - * and comment so that we can properly size the LFH area. The - * filename is mandatory, the comment is optional. - */ - void initNew(const char* fileName, const char* comment); - - /* - * Initialize the structure with the contents of a ZipEntry from - * another file. - */ - status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); - - /* - * Add some pad bytes to the LFH. We do this by adding or resizing - * the "extra" field. - */ - status_t addPadding(int padding); - - /* - * Set information about the data for this entry. - */ - void setDataInfo(long uncompLen, long compLen, unsigned long crc32, - int compressionMethod); - - /* - * Set the modification date. - */ - void setModWhen(time_t when); - - /* - * Return the offset of the local file header. - */ - off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } - - /* - * Set the offset of the local file header, relative to the start of - * the current file. - */ - void setLFHOffset(off_t offset) { - mCDE.mLocalHeaderRelOffset = (long) offset; - } - - /* mark for deletion; used by ZipFile::remove() */ - void setDeleted(void) { mDeleted = true; } - -private: - /* these are private and not defined */ - ZipEntry(const ZipEntry& src); - ZipEntry& operator=(const ZipEntry& src); - - /* returns "true" if the CDE and the LFH agree */ - bool compareHeaders(void) const; - void copyCDEtoLFH(void); - - bool mDeleted; // set if entry is pending deletion - bool mMarked; // app-defined marker - - /* - * Every entry in the Zip archive starts off with one of these. - */ - class LocalFileHeader { - public: - LocalFileHeader(void) : - mVersionToExtract(0), - mGPBitFlag(0), - mCompressionMethod(0), - mLastModFileTime(0), - mLastModFileDate(0), - mCRC32(0), - mCompressedSize(0), - mUncompressedSize(0), - mFileNameLength(0), - mExtraFieldLength(0), - mFileName(NULL), - mExtraField(NULL) - {} - virtual ~LocalFileHeader(void) { - delete[] mFileName; - delete[] mExtraField; - } - - status_t read(FILE* fp); - status_t write(FILE* fp); - - // unsigned long mSignature; - unsigned short mVersionToExtract; - unsigned short mGPBitFlag; - unsigned short mCompressionMethod; - unsigned short mLastModFileTime; - unsigned short mLastModFileDate; - unsigned long mCRC32; - unsigned long mCompressedSize; - unsigned long mUncompressedSize; - unsigned short mFileNameLength; - unsigned short mExtraFieldLength; - unsigned char* mFileName; - unsigned char* mExtraField; - - enum { - kSignature = 0x04034b50, - kLFHLen = 30, // LocalFileHdr len, excl. var fields - }; - - void dump(void) const; - }; - - /* - * Every entry in the Zip archive has one of these in the "central - * directory" at the end of the file. - */ - class CentralDirEntry { - public: - CentralDirEntry(void) : - mVersionMadeBy(0), - mVersionToExtract(0), - mGPBitFlag(0), - mCompressionMethod(0), - mLastModFileTime(0), - mLastModFileDate(0), - mCRC32(0), - mCompressedSize(0), - mUncompressedSize(0), - mFileNameLength(0), - mExtraFieldLength(0), - mFileCommentLength(0), - mDiskNumberStart(0), - mInternalAttrs(0), - mExternalAttrs(0), - mLocalHeaderRelOffset(0), - mFileName(NULL), - mExtraField(NULL), - mFileComment(NULL) - {} - virtual ~CentralDirEntry(void) { - delete[] mFileName; - delete[] mExtraField; - delete[] mFileComment; - } - - status_t read(FILE* fp); - status_t write(FILE* fp); - - // unsigned long mSignature; - unsigned short mVersionMadeBy; - unsigned short mVersionToExtract; - unsigned short mGPBitFlag; - unsigned short mCompressionMethod; - unsigned short mLastModFileTime; - unsigned short mLastModFileDate; - unsigned long mCRC32; - unsigned long mCompressedSize; - unsigned long mUncompressedSize; - unsigned short mFileNameLength; - unsigned short mExtraFieldLength; - unsigned short mFileCommentLength; - unsigned short mDiskNumberStart; - unsigned short mInternalAttrs; - unsigned long mExternalAttrs; - unsigned long mLocalHeaderRelOffset; - unsigned char* mFileName; - unsigned char* mExtraField; - unsigned char* mFileComment; - - void dump(void) const; - - enum { - kSignature = 0x02014b50, - kCDELen = 46, // CentralDirEnt len, excl. var fields - }; - }; - - enum { - //kDataDescriptorSignature = 0x08074b50, // currently unused - kDataDescriptorLen = 16, // four 32-bit fields - - kDefaultVersion = 20, // need deflate, nothing much else - kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 - kUsesDataDescr = 0x0008, // GPBitFlag bit 3 - }; - - LocalFileHeader mLFH; - CentralDirEntry mCDE; -}; - -}; // namespace android - -#endif // __LIBS_ZIPENTRY_H diff --git a/include/utils/ZipFile.h b/include/utils/ZipFile.h deleted file mode 100644 index 44df5bbaa405..000000000000 --- a/include/utils/ZipFile.h +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -// -// General-purpose Zip archive access. This class allows both reading and -// writing to Zip archives, including deletion of existing entries. -// -#ifndef __LIBS_ZIPFILE_H -#define __LIBS_ZIPFILE_H - -#include "ZipEntry.h" -#include "Vector.h" -#include "Errors.h" -#include - -namespace android { - -/* - * Manipulate a Zip archive. - * - * Some changes will not be visible in the until until "flush" is called. - * - * The correct way to update a file archive is to make all changes to a - * copy of the archive in a temporary file, and then unlink/rename over - * the original after everything completes. Because we're only interested - * in using this for packaging, we don't worry about such things. Crashing - * after making changes and before flush() completes could leave us with - * an unusable Zip archive. - */ -class ZipFile { -public: - ZipFile(void) - : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) - {} - ~ZipFile(void) { - if (!mReadOnly) - flush(); - if (mZipFp != NULL) - fclose(mZipFp); - discardEntries(); - } - - /* - * Open a new or existing archive. - */ - typedef enum { - kOpenReadOnly = 0x01, - kOpenReadWrite = 0x02, - kOpenCreate = 0x04, // create if it doesn't exist - kOpenTruncate = 0x08, // if it exists, empty it - }; - status_t open(const char* zipFileName, int flags); - - /* - * Add a file to the end of the archive. Specify whether you want the - * library to try to store it compressed. - * - * If "storageName" is specified, the archive will use that instead - * of "fileName". - * - * If there is already an entry with the same name, the call fails. - * Existing entries with the same name must be removed first. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const char* fileName, int compressionMethod, - ZipEntry** ppEntry) - { - return add(fileName, fileName, compressionMethod, ppEntry); - } - status_t add(const char* fileName, const char* storageName, - int compressionMethod, ZipEntry** ppEntry) - { - return addCommon(fileName, NULL, 0, storageName, - ZipEntry::kCompressStored, - compressionMethod, ppEntry); - } - - /* - * Add a file that is already compressed with gzip. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t addGzip(const char* fileName, const char* storageName, - ZipEntry** ppEntry) - { - return addCommon(fileName, NULL, 0, storageName, - ZipEntry::kCompressDeflated, - ZipEntry::kCompressDeflated, ppEntry); - } - - /* - * Add a file from an in-memory data buffer. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const void* data, size_t size, const char* storageName, - int compressionMethod, ZipEntry** ppEntry) - { - return addCommon(NULL, data, size, storageName, - ZipEntry::kCompressStored, - compressionMethod, ppEntry); - } - - /* - * Add an entry by copying it from another zip file. If "padding" is - * nonzero, the specified number of bytes will be added to the "extra" - * field in the header. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, - int padding, ZipEntry** ppEntry); - - /* - * Mark an entry as having been removed. It is not actually deleted - * from the archive or our internal data structures until flush() is - * called. - */ - status_t remove(ZipEntry* pEntry); - - /* - * Flush changes. If mNeedCDRewrite is set, this writes the central dir. - */ - status_t flush(void); - - /* - * Expand the data into the buffer provided. The buffer must hold - * at least bytes. Variation expands directly - * to a file. - * - * Returns "false" if an error was encountered in the compressed data. - */ - //bool uncompress(const ZipEntry* pEntry, void* buf) const; - //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; - void* uncompress(const ZipEntry* pEntry); - - /* - * Get an entry, by name. Returns NULL if not found. - * - * Does not return entries pending deletion. - */ - ZipEntry* getEntryByName(const char* fileName) const; - - /* - * Get the Nth entry in the archive. - * - * This will return an entry that is pending deletion. - */ - int getNumEntries(void) const { return mEntries.size(); } - ZipEntry* getEntryByIndex(int idx) const; - -private: - /* these are private and not defined */ - ZipFile(const ZipFile& src); - ZipFile& operator=(const ZipFile& src); - - class EndOfCentralDir { - public: - EndOfCentralDir(void) : - mDiskNumber(0), - mDiskWithCentralDir(0), - mNumEntries(0), - mTotalNumEntries(0), - mCentralDirSize(0), - mCentralDirOffset(0), - mCommentLen(0), - mComment(NULL) - {} - virtual ~EndOfCentralDir(void) { - delete[] mComment; - } - - status_t readBuf(const unsigned char* buf, int len); - status_t write(FILE* fp); - - //unsigned long mSignature; - unsigned short mDiskNumber; - unsigned short mDiskWithCentralDir; - unsigned short mNumEntries; - unsigned short mTotalNumEntries; - unsigned long mCentralDirSize; - unsigned long mCentralDirOffset; // offset from first disk - unsigned short mCommentLen; - unsigned char* mComment; - - enum { - kSignature = 0x06054b50, - kEOCDLen = 22, // EndOfCentralDir len, excl. comment - - kMaxCommentLen = 65535, // longest possible in ushort - kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, - - }; - - void dump(void) const; - }; - - - /* read all entries in the central dir */ - status_t readCentralDir(void); - - /* crunch deleted entries out */ - status_t crunchArchive(void); - - /* clean up mEntries */ - void discardEntries(void); - - /* common handler for all "add" functions */ - status_t addCommon(const char* fileName, const void* data, size_t size, - const char* storageName, int sourceType, int compressionMethod, - ZipEntry** ppEntry); - - /* copy all of "srcFp" into "dstFp" */ - status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); - /* copy all of "data" into "dstFp" */ - status_t copyDataToFp(FILE* dstFp, - const void* data, size_t size, unsigned long* pCRC32); - /* copy some of "srcFp" into "dstFp" */ - status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, - unsigned long* pCRC32); - /* like memmove(), but on parts of a single file */ - status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); - /* compress all of "srcFp" into "dstFp", using Deflate */ - status_t compressFpToFp(FILE* dstFp, FILE* srcFp, - const void* data, size_t size, unsigned long* pCRC32); - - /* get modification date from a file descriptor */ - time_t getModTime(int fd); - - /* - * We use stdio FILE*, which gives us buffering but makes dealing - * with files >2GB awkward. Until we support Zip64, we're fine. - */ - FILE* mZipFp; // Zip file pointer - - /* one of these per file */ - EndOfCentralDir mEOCD; - - /* did we open this read-only? */ - bool mReadOnly; - - /* set this when we trash the central dir */ - bool mNeedCDRewrite; - - /* - * One ZipEntry per entry in the zip file. I'm using pointers instead - * of objects because it's easier than making operator= work for the - * classes and sub-classes. - */ - Vector mEntries; -}; - -}; // namespace android - -#endif // __LIBS_ZIPFILE_H diff --git a/include/utils/inet_address.h b/include/utils/inet_address.h deleted file mode 100644 index dbd8672e0b0b..000000000000 --- a/include/utils/inet_address.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2005 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. - */ - -// -// Internet address classes. Modeled after Java classes. -// -#ifndef _RUNTIME_INET_ADDRESS_H -#define _RUNTIME_INET_ADDRESS_H - -#ifdef HAVE_ANDROID_OS -#error DO NOT USE THIS FILE IN THE DEVICE BUILD -#endif - - -namespace android { - -/* - * This class holds Internet addresses. Perhaps more useful is its - * ability to look up addresses by name. - * - * Invoke one of the static factory methods to create a new object. - */ -class InetAddress { -public: - virtual ~InetAddress(void); - - // create from w.x.y.z or foo.bar.com notation - static InetAddress* getByName(const char* host); - - // copy-construction - InetAddress(const InetAddress& orig); - - const void* getAddress(void) const { return mAddress; } - int getAddressLength(void) const { return mLength; } - const char* getHostName(void) const { return mName; } - -private: - InetAddress(void); - // assignment (private) - InetAddress& operator=(const InetAddress& addr); - - // use a void* here so we don't have to expose actual socket headers - void* mAddress; // this is really a ptr to sockaddr_in - int mLength; - char* mName; -}; - - -/* - * Base class for socket addresses. - */ -class SocketAddress { -public: - SocketAddress() {} - virtual ~SocketAddress() {} -}; - - -/* - * Internet address class. This combines an InetAddress with a port. - */ -class InetSocketAddress : public SocketAddress { -public: - InetSocketAddress() : - mAddress(0), mPort(-1) - {} - ~InetSocketAddress(void) { - delete mAddress; - } - - // Create an address with a host wildcard (useful for servers). - bool create(int port); - // Create an address with the specified host and port. - bool create(const InetAddress* addr, int port); - // Create an address with the specified host and port. Does the - // hostname lookup. - bool create(const char* host, int port); - - const InetAddress* getAddress(void) const { return mAddress; } - const int getPort(void) const { return mPort; } - const char* getHostName(void) const { return mAddress->getHostName(); } - -private: - InetAddress* mAddress; - int mPort; -}; - -}; // namespace android - -#endif // _RUNTIME_INET_ADDRESS_H diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index 6605c9fde0ea..70d440797305 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -44,26 +44,13 @@ commonSources:= \ misc.cpp \ LogSocket.cpp -# -# The cpp files listed here do not belong in the device -# build. Consult with the swetland before even thinking about -# putting them in commonSources. -# -# They're used by the simulator runtime and by host-side tools like -# aapt and the simulator front-end. -# -hostSources:= \ - InetAddress.cpp \ - Socket.cpp \ - ZipEntry.cpp \ - ZipFile.cpp # For the host # ===================================================== include $(CLEAR_VARS) -LOCAL_SRC_FILES:= $(commonSources) $(hostSources) +LOCAL_SRC_FILES:= $(commonSources) ifeq ($(HOST_OS),linux) # Use the futex based mutex and condition variable @@ -100,10 +87,6 @@ LOCAL_SRC_FILES:= \ BackupData.cpp \ BackupHelpers.cpp -ifeq ($(TARGET_SIMULATOR),true) -LOCAL_SRC_FILES += $(hostSources) -endif - ifeq ($(TARGET_OS),linux) # Use the futex based mutex and condition variable # implementation from android-arm because it's shared mem safe @@ -130,9 +113,5 @@ endif # linux-x86 endif # sim LOCAL_MODULE:= libutils - -#LOCAL_CFLAGS+= -#LOCAL_LDFLAGS:= - include $(BUILD_SHARED_LIBRARY) diff --git a/libs/utils/InetAddress.cpp b/libs/utils/InetAddress.cpp deleted file mode 100644 index 39a0a6839a53..000000000000 --- a/libs/utils/InetAddress.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2005 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. - */ - -// -// Internet address class. -// -#ifdef HAVE_WINSOCK -# include -#else -# include -# include -# include -//# include -# include -#endif - -#include -#include -#include - -#include -#include -#include -#include -#include - -using namespace android; - - -/* - * =========================================================================== - * InetAddress - * =========================================================================== - */ - -// lock for the next couple of functions; could tuck into InetAddress -static Mutex* gGHBNLock; - -/* - * Lock/unlock access to the hostent struct returned by gethostbyname(). - */ -static inline void lock_gethostbyname(void) -{ - if (gGHBNLock == NULL) - gGHBNLock = new Mutex; - gGHBNLock->lock(); -} -static inline void unlock_gethostbyname(void) -{ - assert(gGHBNLock != NULL); - gGHBNLock->unlock(); -} - - -/* - * Constructor -- just init members. This is private so that callers - * are required to use getByName(). - */ -InetAddress::InetAddress(void) - : mAddress(NULL), mLength(-1), mName(NULL) -{ -} - -/* - * Destructor -- free address storage. - */ -InetAddress::~InetAddress(void) -{ - delete[] (char*) mAddress; - delete[] mName; -} - -/* - * Copy constructor. - */ -InetAddress::InetAddress(const InetAddress& orig) -{ - *this = orig; // use assignment code -} - -/* - * Assignment operator. - */ -InetAddress& InetAddress::operator=(const InetAddress& addr) -{ - // handle self-assignment - if (this == &addr) - return *this; - // copy mLength and mAddress - mLength = addr.mLength; - if (mLength > 0) { - mAddress = new char[mLength]; - memcpy(mAddress, addr.mAddress, mLength); - LOG(LOG_DEBUG, "socket", - "HEY: copied %d bytes in assignment operator\n", mLength); - } else { - mAddress = NULL; - } - // copy mName - mName = new char[strlen(addr.mName)+1]; - strcpy(mName, addr.mName); - - return *this; -} - -/* - * Create a new object from a name or a dotted-number IP notation. - * - * Returns NULL on failure. - */ -InetAddress* -InetAddress::getByName(const char* host) -{ - InetAddress* newAddr = NULL; - struct sockaddr_in addr; - struct hostent* he; - DurationTimer hostTimer, lockTimer; - - // gethostbyname() isn't reentrant, so we need to lock things until - // we can copy the data out. - lockTimer.start(); - lock_gethostbyname(); - hostTimer.start(); - - he = gethostbyname(host); - if (he == NULL) { - LOG(LOG_WARN, "socket", "WARNING: cannot resolve host %s\n", host); - unlock_gethostbyname(); - return NULL; - } - - memcpy(&addr.sin_addr, he->h_addr, he->h_length); - addr.sin_family = he->h_addrtype; - addr.sin_port = 0; - - // got it, unlock us - hostTimer.stop(); - he = NULL; - unlock_gethostbyname(); - - lockTimer.stop(); - if ((long) lockTimer.durationUsecs() > 100000) { - long lockTime = (long) lockTimer.durationUsecs(); - long hostTime = (long) hostTimer.durationUsecs(); - LOG(LOG_DEBUG, "socket", - "Lookup of %s took %.3fs (gethostbyname=%.3fs lock=%.3fs)\n", - host, lockTime / 1000000.0, hostTime / 1000000.0, - (lockTime - hostTime) / 1000000.0); - } - - // Alloc storage and copy it over. - newAddr = new InetAddress(); - if (newAddr == NULL) - return NULL; - - newAddr->mLength = sizeof(struct sockaddr_in); - newAddr->mAddress = new char[sizeof(struct sockaddr_in)]; - if (newAddr->mAddress == NULL) { - delete newAddr; - return NULL; - } - memcpy(newAddr->mAddress, &addr, newAddr->mLength); - - // Keep this for debug messages. - newAddr->mName = new char[strlen(host)+1]; - if (newAddr->mName == NULL) { - delete newAddr; - return NULL; - } - strcpy(newAddr->mName, host); - - return newAddr; -} - - -/* - * =========================================================================== - * InetSocketAddress - * =========================================================================== - */ - -/* - * Create an address with the host wildcard (INADDR_ANY). - */ -bool InetSocketAddress::create(int port) -{ - assert(mAddress == NULL); - - mAddress = InetAddress::getByName("0.0.0.0"); - if (mAddress == NULL) - return false; - mPort = port; - return true; -} - -/* - * Create address with host and port specified. - */ -bool InetSocketAddress::create(const InetAddress* addr, int port) -{ - assert(mAddress == NULL); - - mAddress = new InetAddress(*addr); // make a copy - if (mAddress == NULL) - return false; - mPort = port; - return true; -} - -/* - * Create address with host and port specified. - */ -bool InetSocketAddress::create(const char* host, int port) -{ - assert(mAddress == NULL); - - mAddress = InetAddress::getByName(host); - if (mAddress == NULL) - return false; - mPort = port; - return true; -} - diff --git a/libs/utils/Socket.cpp b/libs/utils/Socket.cpp deleted file mode 100644 index 51509a30480d..000000000000 --- a/libs/utils/Socket.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) 2005 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. - */ - -// -// Internet address class. -// - -#ifdef HAVE_WINSOCK -// This needs to come first, or Cygwin gets concerned about a potential -// clash between WinSock and . -# include -#endif - -#include -#include -#include -#include - -#ifndef HAVE_WINSOCK -# include -# include -# include -# include -#endif - -#include -#include -#include -#include -#include -#include - -using namespace android; - - -/* - * =========================================================================== - * Socket - * =========================================================================== - */ - -#ifndef INVALID_SOCKET -# define INVALID_SOCKET (-1) -#endif -#define UNDEF_SOCKET ((unsigned long) INVALID_SOCKET) - -/*static*/ bool Socket::mBootInitialized = false; - -/* - * Extract system-dependent error code. - */ -static inline int getSocketError(void) { -#ifdef HAVE_WINSOCK - return WSAGetLastError(); -#else - return errno; -#endif -} - -/* - * One-time initialization for socket code. - */ -/*static*/ bool Socket::bootInit(void) -{ -#ifdef HAVE_WINSOCK - WSADATA wsaData; - int err; - - err = WSAStartup(MAKEWORD(2, 0), &wsaData); - if (err != 0) { - LOG(LOG_ERROR, "socket", "Unable to start WinSock\n"); - return false; - } - - LOG(LOG_INFO, "socket", "Using WinSock v%d.%d\n", - LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion)); -#endif - - mBootInitialized = true; - return true; -} - -/* - * One-time shutdown for socket code. - */ -/*static*/ void Socket::finalShutdown(void) -{ -#ifdef HAVE_WINSOCK - WSACleanup(); -#endif - mBootInitialized = false; -} - - -/* - * Simple constructor. Allow the application to create us and then make - * bind/connect calls. - */ -Socket::Socket(void) - : mSock(UNDEF_SOCKET) -{ - if (!mBootInitialized) - LOG(LOG_WARN, "socket", "WARNING: sockets not initialized\n"); -} - -/* - * Destructor. Closes the socket and resets our storage. - */ -Socket::~Socket(void) -{ - close(); -} - - -/* - * Create a socket and connect to the specified host and port. - */ -int Socket::connect(const char* host, int port) -{ - if (mSock != UNDEF_SOCKET) { - LOG(LOG_WARN, "socket", "Socket already connected\n"); - return -1; - } - - InetSocketAddress sockAddr; - if (!sockAddr.create(host, port)) - return -1; - - //return doConnect(sockAddr); - int foo; - foo = doConnect(sockAddr); - return foo; -} - -/* - * Create a socket and connect to the specified host and port. - */ -int Socket::connect(const InetAddress* addr, int port) -{ - if (mSock != UNDEF_SOCKET) { - LOG(LOG_WARN, "socket", "Socket already connected\n"); - return -1; - } - - InetSocketAddress sockAddr; - if (!sockAddr.create(addr, port)) - return -1; - - return doConnect(sockAddr); -} - -/* - * Finish creating a socket by connecting to the remote host. - * - * Returns 0 on success. - */ -int Socket::doConnect(const InetSocketAddress& sockAddr) -{ -#ifdef HAVE_WINSOCK - SOCKET sock; -#else - int sock; -#endif - const InetAddress* addr = sockAddr.getAddress(); - int port = sockAddr.getPort(); - struct sockaddr_in inaddr; - DurationTimer connectTimer; - - assert(sizeof(struct sockaddr_in) == addr->getAddressLength()); - memcpy(&inaddr, addr->getAddress(), addr->getAddressLength()); - inaddr.sin_port = htons(port); - - //fprintf(stderr, "--- connecting to %s:%d\n", - // sockAddr.getHostName(), port); - - sock = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sock == INVALID_SOCKET) { - int err = getSocketError(); - LOG(LOG_ERROR, "socket", "Unable to create socket (err=%d)\n", err); - return (err != 0) ? err : -1; - } - - connectTimer.start(); - - if (::connect(sock, (struct sockaddr*) &inaddr, sizeof(inaddr)) != 0) { - int err = getSocketError(); - LOG(LOG_WARN, "socket", "Connect to %s:%d failed: %d\n", - sockAddr.getHostName(), port, err); - return (err != 0) ? err : -1; - } - - connectTimer.stop(); - if ((long) connectTimer.durationUsecs() > 100000) { - LOG(LOG_INFO, "socket", - "Connect to %s:%d took %.3fs\n", sockAddr.getHostName(), - port, ((long) connectTimer.durationUsecs()) / 1000000.0); - } - - mSock = (unsigned long) sock; - LOG(LOG_VERBOSE, "socket", - "--- connected to %s:%d\n", sockAddr.getHostName(), port); - return 0; -} - - -/* - * Close the socket if it needs closing. - */ -bool Socket::close(void) -{ - if (mSock != UNDEF_SOCKET) { - //fprintf(stderr, "--- closing socket %lu\n", mSock); -#ifdef HAVE_WINSOCK - if (::closesocket((SOCKET) mSock) != 0) - return false; -#else - if (::close((int) mSock) != 0) - return false; -#endif - } - - mSock = UNDEF_SOCKET; - - return true; -} - -/* - * Read data from socket. - * - * Standard semantics: read up to "len" bytes into "buf". Returns the - * number of bytes read, or less than zero on error. - */ -int Socket::read(void* buf, ssize_t len) const -{ - if (mSock == UNDEF_SOCKET) { - LOG(LOG_ERROR, "socket", "ERROR: read on invalid socket\n"); - return -500; - } - -#ifdef HAVE_WINSOCK - SOCKET sock = (SOCKET) mSock; -#else - int sock = (int) mSock; -#endif - int cc; - - cc = recv(sock, (char*)buf, len, 0); - if (cc < 0) { - int err = getSocketError(); - return (err > 0) ? -err : -1; - } - - return cc; -} - -/* - * Write data to a socket. - * - * Standard semantics: write up to "len" bytes into "buf". Returns the - * number of bytes written, or less than zero on error. - */ -int Socket::write(const void* buf, ssize_t len) const -{ - if (mSock == UNDEF_SOCKET) { - LOG(LOG_ERROR, "socket", "ERROR: write on invalid socket\n"); - return -500; - } - -#ifdef HAVE_WINSOCK - SOCKET sock = (SOCKET) mSock; -#else - int sock = (int) mSock; -#endif - int cc; - - cc = send(sock, (const char*)buf, len, 0); - if (cc < 0) { - int err = getSocketError(); - return (err > 0) ? -err : -1; - } - - return cc; -} - - -/* - * =========================================================================== - * Socket tests - * =========================================================================== - */ - -/* - * Read all data from the socket. The data is read into a buffer that - * expands as needed. - * - * On exit, the buffer is returned, and the length of the data is stored - * in "*sz". A null byte is added to the end, but is not included in - * the length. - */ -static char* socketReadAll(const Socket& s, int *sz) -{ - int max, r; - char *data, *ptr, *tmp; - - data = (char*) malloc(max = 32768); - if (data == NULL) - return NULL; - - ptr = data; - - for (;;) { - if ((ptr - data) == max) { - tmp = (char*) realloc(data, max *= 2); - if(tmp == 0) { - free(data); - return 0; - } - } - r = s.read(ptr, max - (ptr - data)); - if (r == 0) - break; - if (r < 0) { - LOG(LOG_WARN, "socket", "WARNING: socket read failed (res=%d)\n",r); - break; - } - ptr += r; - } - - if ((ptr - data) == max) { - tmp = (char*) realloc(data, max + 1); - if (tmp == NULL) { - free(data); - return NULL; - } - } - *ptr = '\0'; - *sz = (ptr - data); - return data; -} - -/* - * Exercise the Socket class. - */ -void android::TestSockets(void) -{ - printf("----- SOCKET TEST ------\n"); - Socket::bootInit(); - - char* buf = NULL; - int len, cc; - const char* kTestStr = - "GET / HTTP/1.0\n" - "Connection: close\n" - "\n"; - - Socket sock; - if (sock.connect("www.google.com", 80) != 0) { - fprintf(stderr, "socket connected failed\n"); - goto bail; - } - - cc = sock.write(kTestStr, strlen(kTestStr)); - if (cc != (int) strlen(kTestStr)) { - fprintf(stderr, "write failed, res=%d\n", cc); - goto bail; - } - buf = socketReadAll(sock, &len); - - printf("GOT '%s'\n", buf); - -bail: - sock.close(); - free(buf); -} - diff --git a/libs/utils/ZipEntry.cpp b/libs/utils/ZipEntry.cpp deleted file mode 100644 index 96f9fc4d692f..000000000000 --- a/libs/utils/ZipEntry.cpp +++ /dev/null @@ -1,696 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -// -// Access to entries in a Zip archive. -// - -#define LOG_TAG "zip" - -#include -#include - -#include -#include -#include - -using namespace android; - -/* - * Initialize a new ZipEntry structure from a FILE* positioned at a - * CentralDirectoryEntry. - * - * On exit, the file pointer will be at the start of the next CDE or - * at the EOCD. - */ -status_t ZipEntry::initFromCDE(FILE* fp) -{ - status_t result; - long posn; - bool hasDD; - - //LOGV("initFromCDE ---\n"); - - /* read the CDE */ - result = mCDE.read(fp); - if (result != NO_ERROR) { - LOGD("mCDE.read failed\n"); - return result; - } - - //mCDE.dump(); - - /* using the info in the CDE, go load up the LFH */ - posn = ftell(fp); - if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { - LOGD("local header seek failed (%ld)\n", - mCDE.mLocalHeaderRelOffset); - return UNKNOWN_ERROR; - } - - result = mLFH.read(fp); - if (result != NO_ERROR) { - LOGD("mLFH.read failed\n"); - return result; - } - - if (fseek(fp, posn, SEEK_SET) != 0) - return UNKNOWN_ERROR; - - //mLFH.dump(); - - /* - * We *might* need to read the Data Descriptor at this point and - * integrate it into the LFH. If this bit is set, the CRC-32, - * compressed size, and uncompressed size will be zero. In practice - * these seem to be rare. - */ - hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; - if (hasDD) { - // do something clever - //LOGD("+++ has data descriptor\n"); - } - - /* - * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" - * flag is set, because the LFH is incomplete. (Not a problem, since we - * prefer the CDE values.) - */ - if (!hasDD && !compareHeaders()) { - LOGW("WARNING: header mismatch\n"); - // keep going? - } - - /* - * If the mVersionToExtract is greater than 20, we may have an - * issue unpacking the record -- could be encrypted, compressed - * with something we don't support, or use Zip64 extensions. We - * can defer worrying about that to when we're extracting data. - */ - - return NO_ERROR; -} - -/* - * Initialize a new entry. Pass in the file name and an optional comment. - * - * Initializes the CDE and the LFH. - */ -void ZipEntry::initNew(const char* fileName, const char* comment) -{ - assert(fileName != NULL && *fileName != '\0'); // name required - - /* most fields are properly initialized by constructor */ - mCDE.mVersionMadeBy = kDefaultMadeBy; - mCDE.mVersionToExtract = kDefaultVersion; - mCDE.mCompressionMethod = kCompressStored; - mCDE.mFileNameLength = strlen(fileName); - if (comment != NULL) - mCDE.mFileCommentLength = strlen(comment); - mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does - - if (mCDE.mFileNameLength > 0) { - mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; - strcpy((char*) mCDE.mFileName, fileName); - } - if (mCDE.mFileCommentLength > 0) { - /* TODO: stop assuming null-terminated ASCII here? */ - mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; - strcpy((char*) mCDE.mFileComment, comment); - } - - copyCDEtoLFH(); -} - -/* - * Initialize a new entry, starting with the ZipEntry from a different - * archive. - * - * Initializes the CDE and the LFH. - */ -status_t ZipEntry::initFromExternal(const ZipFile* pZipFile, - const ZipEntry* pEntry) -{ - /* - * Copy everything in the CDE over, then fix up the hairy bits. - */ - memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE)); - - if (mCDE.mFileNameLength > 0) { - mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; - if (mCDE.mFileName == NULL) - return NO_MEMORY; - strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName); - } - if (mCDE.mFileCommentLength > 0) { - mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; - if (mCDE.mFileComment == NULL) - return NO_MEMORY; - strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment); - } - if (mCDE.mExtraFieldLength > 0) { - /* we null-terminate this, though it may not be a string */ - mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1]; - if (mCDE.mExtraField == NULL) - return NO_MEMORY; - memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField, - mCDE.mExtraFieldLength+1); - } - - /* construct the LFH from the CDE */ - copyCDEtoLFH(); - - /* - * The LFH "extra" field is independent of the CDE "extra", so we - * handle it here. - */ - assert(mLFH.mExtraField == NULL); - mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; - if (mLFH.mExtraFieldLength > 0) { - mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; - if (mLFH.mExtraField == NULL) - return NO_MEMORY; - memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, - mLFH.mExtraFieldLength+1); - } - - return NO_ERROR; -} - -/* - * Insert pad bytes in the LFH by tweaking the "extra" field. This will - * potentially confuse something that put "extra" data in here earlier, - * but I can't find an actual problem. - */ -status_t ZipEntry::addPadding(int padding) -{ - if (padding <= 0) - return INVALID_OPERATION; - - //LOGI("HEY: adding %d pad bytes to existing %d in %s\n", - // padding, mLFH.mExtraFieldLength, mCDE.mFileName); - - if (mLFH.mExtraFieldLength > 0) { - /* extend existing field */ - unsigned char* newExtra; - - newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; - if (newExtra == NULL) - return NO_MEMORY; - memset(newExtra + mLFH.mExtraFieldLength, 0, padding); - memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); - - delete[] mLFH.mExtraField; - mLFH.mExtraField = newExtra; - mLFH.mExtraFieldLength += padding; - } else { - /* create new field */ - mLFH.mExtraField = new unsigned char[padding]; - memset(mLFH.mExtraField, 0, padding); - mLFH.mExtraFieldLength = padding; - } - - return NO_ERROR; -} - -/* - * Set the fields in the LFH equal to the corresponding fields in the CDE. - * - * This does not touch the LFH "extra" field. - */ -void ZipEntry::copyCDEtoLFH(void) -{ - mLFH.mVersionToExtract = mCDE.mVersionToExtract; - mLFH.mGPBitFlag = mCDE.mGPBitFlag; - mLFH.mCompressionMethod = mCDE.mCompressionMethod; - mLFH.mLastModFileTime = mCDE.mLastModFileTime; - mLFH.mLastModFileDate = mCDE.mLastModFileDate; - mLFH.mCRC32 = mCDE.mCRC32; - mLFH.mCompressedSize = mCDE.mCompressedSize; - mLFH.mUncompressedSize = mCDE.mUncompressedSize; - mLFH.mFileNameLength = mCDE.mFileNameLength; - // the "extra field" is independent - - delete[] mLFH.mFileName; - if (mLFH.mFileNameLength > 0) { - mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; - strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); - } else { - mLFH.mFileName = NULL; - } -} - -/* - * Set some information about a file after we add it. - */ -void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, - int compressionMethod) -{ - mCDE.mCompressionMethod = compressionMethod; - mCDE.mCRC32 = crc32; - mCDE.mCompressedSize = compLen; - mCDE.mUncompressedSize = uncompLen; - mCDE.mCompressionMethod = compressionMethod; - if (compressionMethod == kCompressDeflated) { - mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used - } - copyCDEtoLFH(); -} - -/* - * See if the data in mCDE and mLFH match up. This is mostly useful for - * debugging these classes, but it can be used to identify damaged - * archives. - * - * Returns "false" if they differ. - */ -bool ZipEntry::compareHeaders(void) const -{ - if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { - LOGV("cmp: VersionToExtract\n"); - return false; - } - if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { - LOGV("cmp: GPBitFlag\n"); - return false; - } - if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { - LOGV("cmp: CompressionMethod\n"); - return false; - } - if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { - LOGV("cmp: LastModFileTime\n"); - return false; - } - if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { - LOGV("cmp: LastModFileDate\n"); - return false; - } - if (mCDE.mCRC32 != mLFH.mCRC32) { - LOGV("cmp: CRC32\n"); - return false; - } - if (mCDE.mCompressedSize != mLFH.mCompressedSize) { - LOGV("cmp: CompressedSize\n"); - return false; - } - if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { - LOGV("cmp: UncompressedSize\n"); - return false; - } - if (mCDE.mFileNameLength != mLFH.mFileNameLength) { - LOGV("cmp: FileNameLength\n"); - return false; - } -#if 0 // this seems to be used for padding, not real data - if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { - LOGV("cmp: ExtraFieldLength\n"); - return false; - } -#endif - if (mCDE.mFileName != NULL) { - if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { - LOGV("cmp: FileName\n"); - return false; - } - } - - return true; -} - - -/* - * Convert the DOS date/time stamp into a UNIX time stamp. - */ -time_t ZipEntry::getModWhen(void) const -{ - struct tm parts; - - parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; - parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; - parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; - parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); - parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; - parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; - parts.tm_wday = parts.tm_yday = 0; - parts.tm_isdst = -1; // DST info "not available" - - return mktime(&parts); -} - -/* - * Set the CDE/LFH timestamp from UNIX time. - */ -void ZipEntry::setModWhen(time_t when) -{ -#ifdef HAVE_LOCALTIME_R - struct tm tmResult; -#endif - time_t even; - unsigned short zdate, ztime; - - struct tm* ptm; - - /* round up to an even number of seconds */ - even = (time_t)(((unsigned long)(when) + 1) & (~1)); - - /* expand */ -#ifdef HAVE_LOCALTIME_R - ptm = localtime_r(&even, &tmResult); -#else - ptm = localtime(&even); -#endif - - int year; - year = ptm->tm_year; - if (year < 80) - year = 80; - - zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; - ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; - - mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; - mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; -} - - -/* - * =========================================================================== - * ZipEntry::LocalFileHeader - * =========================================================================== - */ - -/* - * Read a local file header. - * - * On entry, "fp" points to the signature at the start of the header. - * On exit, "fp" points to the start of data. - */ -status_t ZipEntry::LocalFileHeader::read(FILE* fp) -{ - status_t result = NO_ERROR; - unsigned char buf[kLFHLen]; - - assert(mFileName == NULL); - assert(mExtraField == NULL); - - if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { - result = UNKNOWN_ERROR; - goto bail; - } - - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { - LOGD("whoops: didn't find expected signature\n"); - result = UNKNOWN_ERROR; - goto bail; - } - - mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); - mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); - mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); - mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); - mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); - mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); - mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); - mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); - mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); - mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); - - // TODO: validate sizes - - /* grab filename */ - if (mFileNameLength != 0) { - mFileName = new unsigned char[mFileNameLength+1]; - if (mFileName == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mFileName[mFileNameLength] = '\0'; - } - - /* grab extra field */ - if (mExtraFieldLength != 0) { - mExtraField = new unsigned char[mExtraFieldLength+1]; - if (mExtraField == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mExtraField[mExtraFieldLength] = '\0'; - } - -bail: - return result; -} - -/* - * Write a local file header. - */ -status_t ZipEntry::LocalFileHeader::write(FILE* fp) -{ - unsigned char buf[kLFHLen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); - ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); - ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); - ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); - ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); - ZipEntry::putLongLE(&buf[0x0e], mCRC32); - ZipEntry::putLongLE(&buf[0x12], mCompressedSize); - ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); - ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); - ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); - - if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) - return UNKNOWN_ERROR; - - /* write filename */ - if (mFileNameLength != 0) { - if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) - return UNKNOWN_ERROR; - } - - /* write "extra field" */ - if (mExtraFieldLength != 0) { - if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - - -/* - * Dump the contents of a LocalFileHeader object. - */ -void ZipEntry::LocalFileHeader::dump(void) const -{ - LOGD(" LocalFileHeader contents:\n"); - LOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", - mVersionToExtract, mGPBitFlag, mCompressionMethod); - LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", - mLastModFileTime, mLastModFileDate, mCRC32); - LOGD(" compressedSize=%lu uncompressedSize=%lu\n", - mCompressedSize, mUncompressedSize); - LOGD(" filenameLen=%u extraLen=%u\n", - mFileNameLength, mExtraFieldLength); - if (mFileName != NULL) - LOGD(" filename: '%s'\n", mFileName); -} - - -/* - * =========================================================================== - * ZipEntry::CentralDirEntry - * =========================================================================== - */ - -/* - * Read the central dir entry that appears next in the file. - * - * On entry, "fp" should be positioned on the signature bytes for the - * entry. On exit, "fp" will point at the signature word for the next - * entry or for the EOCD. - */ -status_t ZipEntry::CentralDirEntry::read(FILE* fp) -{ - status_t result = NO_ERROR; - unsigned char buf[kCDELen]; - - /* no re-use */ - assert(mFileName == NULL); - assert(mExtraField == NULL); - assert(mFileComment == NULL); - - if (fread(buf, 1, kCDELen, fp) != kCDELen) { - result = UNKNOWN_ERROR; - goto bail; - } - - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { - LOGD("Whoops: didn't find expected signature\n"); - result = UNKNOWN_ERROR; - goto bail; - } - - mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); - mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); - mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); - mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); - mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); - mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); - mCRC32 = ZipEntry::getLongLE(&buf[0x10]); - mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); - mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); - mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); - mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); - mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); - mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); - mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); - mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); - mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); - - // TODO: validate sizes and offsets - - /* grab filename */ - if (mFileNameLength != 0) { - mFileName = new unsigned char[mFileNameLength+1]; - if (mFileName == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mFileName[mFileNameLength] = '\0'; - } - - /* read "extra field" */ - if (mExtraFieldLength != 0) { - mExtraField = new unsigned char[mExtraFieldLength+1]; - if (mExtraField == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mExtraField[mExtraFieldLength] = '\0'; - } - - - /* grab comment, if any */ - if (mFileCommentLength != 0) { - mFileComment = new unsigned char[mFileCommentLength+1]; - if (mFileComment == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) - { - result = UNKNOWN_ERROR; - goto bail; - } - mFileComment[mFileCommentLength] = '\0'; - } - -bail: - return result; -} - -/* - * Write a central dir entry. - */ -status_t ZipEntry::CentralDirEntry::write(FILE* fp) -{ - unsigned char buf[kCDELen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); - ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); - ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); - ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); - ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); - ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); - ZipEntry::putLongLE(&buf[0x10], mCRC32); - ZipEntry::putLongLE(&buf[0x14], mCompressedSize); - ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); - ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); - ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); - ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); - ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); - ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); - ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); - ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); - - if (fwrite(buf, 1, kCDELen, fp) != kCDELen) - return UNKNOWN_ERROR; - - /* write filename */ - if (mFileNameLength != 0) { - if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) - return UNKNOWN_ERROR; - } - - /* write "extra field" */ - if (mExtraFieldLength != 0) { - if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) - return UNKNOWN_ERROR; - } - - /* write comment */ - if (mFileCommentLength != 0) { - if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - -/* - * Dump the contents of a CentralDirEntry object. - */ -void ZipEntry::CentralDirEntry::dump(void) const -{ - LOGD(" CentralDirEntry contents:\n"); - LOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", - mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); - LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", - mLastModFileTime, mLastModFileDate, mCRC32); - LOGD(" compressedSize=%lu uncompressedSize=%lu\n", - mCompressedSize, mUncompressedSize); - LOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", - mFileNameLength, mExtraFieldLength, mFileCommentLength); - LOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", - mDiskNumberStart, mInternalAttrs, mExternalAttrs, - mLocalHeaderRelOffset); - - if (mFileName != NULL) - LOGD(" filename: '%s'\n", mFileName); - if (mFileComment != NULL) - LOGD(" comment: '%s'\n", mFileComment); -} - diff --git a/libs/utils/ZipFile.cpp b/libs/utils/ZipFile.cpp deleted file mode 100644 index eaa0b208cbfd..000000000000 --- a/libs/utils/ZipFile.cpp +++ /dev/null @@ -1,1296 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -// -// Access to Zip archives. -// - -#define LOG_TAG "zip" - -#include -#include -#include - -#include -#define DEF_MEM_LEVEL 8 // normally in zutil.h? - -#include -#include -#include -#include - -using namespace android; - -/* - * Some environments require the "b", some choke on it. - */ -#define FILE_OPEN_RO "rb" -#define FILE_OPEN_RW "r+b" -#define FILE_OPEN_RW_CREATE "w+b" - -/* should live somewhere else? */ -static status_t errnoToStatus(int err) -{ - if (err == ENOENT) - return NAME_NOT_FOUND; - else if (err == EACCES) - return PERMISSION_DENIED; - else - return UNKNOWN_ERROR; -} - -/* - * Open a file and parse its guts. - */ -status_t ZipFile::open(const char* zipFileName, int flags) -{ - bool newArchive = false; - - assert(mZipFp == NULL); // no reopen - - if ((flags & kOpenTruncate)) - flags |= kOpenCreate; // trunc implies create - - if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) - return INVALID_OPERATION; // not both - if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) - return INVALID_OPERATION; // not neither - if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) - return INVALID_OPERATION; // create requires write - - if (flags & kOpenTruncate) { - newArchive = true; - } else { - newArchive = (access(zipFileName, F_OK) != 0); - if (!(flags & kOpenCreate) && newArchive) { - /* not creating, must already exist */ - LOGD("File %s does not exist", zipFileName); - return NAME_NOT_FOUND; - } - } - - /* open the file */ - const char* openflags; - if (flags & kOpenReadWrite) { - if (newArchive) - openflags = FILE_OPEN_RW_CREATE; - else - openflags = FILE_OPEN_RW; - } else { - openflags = FILE_OPEN_RO; - } - mZipFp = fopen(zipFileName, openflags); - if (mZipFp == NULL) { - int err = errno; - LOGD("fopen failed: %d\n", err); - return errnoToStatus(err); - } - - status_t result; - if (!newArchive) { - /* - * Load the central directory. If that fails, then this probably - * isn't a Zip archive. - */ - result = readCentralDir(); - } else { - /* - * Newly-created. The EndOfCentralDir constructor actually - * sets everything to be the way we want it (all zeroes). We - * set mNeedCDRewrite so that we create *something* if the - * caller doesn't add any files. (We could also just unlink - * the file if it's brand new and nothing was added, but that's - * probably doing more than we really should -- the user might - * have a need for empty zip files.) - */ - mNeedCDRewrite = true; - result = NO_ERROR; - } - - if (flags & kOpenReadOnly) - mReadOnly = true; - else - assert(!mReadOnly); - - return result; -} - -/* - * Return the Nth entry in the archive. - */ -ZipEntry* ZipFile::getEntryByIndex(int idx) const -{ - if (idx < 0 || idx >= (int) mEntries.size()) - return NULL; - - return mEntries[idx]; -} - -/* - * Find an entry by name. - */ -ZipEntry* ZipFile::getEntryByName(const char* fileName) const -{ - /* - * Do a stupid linear string-compare search. - * - * There are various ways to speed this up, especially since it's rare - * to intermingle changes to the archive with "get by name" calls. We - * don't want to sort the mEntries vector itself, however, because - * it's used to recreate the Central Directory. - * - * (Hash table works, parallel list of pointers in sorted order is good.) - */ - int idx; - - for (idx = mEntries.size()-1; idx >= 0; idx--) { - ZipEntry* pEntry = mEntries[idx]; - if (!pEntry->getDeleted() && - strcmp(fileName, pEntry->getFileName()) == 0) - { - return pEntry; - } - } - - return NULL; -} - -/* - * Empty the mEntries vector. - */ -void ZipFile::discardEntries(void) -{ - int count = mEntries.size(); - - while (--count >= 0) - delete mEntries[count]; - - mEntries.clear(); -} - - -/* - * Find the central directory and read the contents. - * - * The fun thing about ZIP archives is that they may or may not be - * readable from start to end. In some cases, notably for archives - * that were written to stdout, the only length information is in the - * central directory at the end of the file. - * - * Of course, the central directory can be followed by a variable-length - * comment field, so we have to scan through it backwards. The comment - * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff - * itself, plus apparently sometimes people throw random junk on the end - * just for the fun of it. - * - * This is all a little wobbly. If the wrong value ends up in the EOCD - * area, we're hosed. This appears to be the way that everbody handles - * it though, so we're in pretty good company if this fails. - */ -status_t ZipFile::readCentralDir(void) -{ - status_t result = NO_ERROR; - unsigned char* buf = NULL; - off_t fileLength, seekStart; - long readAmount; - int i; - - fseek(mZipFp, 0, SEEK_END); - fileLength = ftell(mZipFp); - rewind(mZipFp); - - /* too small to be a ZIP archive? */ - if (fileLength < EndOfCentralDir::kEOCDLen) { - LOGD("Length is %ld -- too small\n", (long)fileLength); - result = INVALID_OPERATION; - goto bail; - } - - buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; - if (buf == NULL) { - LOGD("Failure allocating %d bytes for EOCD search", - EndOfCentralDir::kMaxEOCDSearch); - result = NO_MEMORY; - goto bail; - } - - if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { - seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; - readAmount = EndOfCentralDir::kMaxEOCDSearch; - } else { - seekStart = 0; - readAmount = (long) fileLength; - } - if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { - LOGD("Failure seeking to end of zip at %ld", (long) seekStart); - result = UNKNOWN_ERROR; - goto bail; - } - - /* read the last part of the file into the buffer */ - if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { - LOGD("short file? wanted %ld\n", readAmount); - result = UNKNOWN_ERROR; - goto bail; - } - - /* find the end-of-central-dir magic */ - for (i = readAmount - 4; i >= 0; i--) { - if (buf[i] == 0x50 && - ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) - { - LOGV("+++ Found EOCD at buf+%d\n", i); - break; - } - } - if (i < 0) { - LOGD("EOCD not found, not Zip\n"); - result = INVALID_OPERATION; - goto bail; - } - - /* extract eocd values */ - result = mEOCD.readBuf(buf + i, readAmount - i); - if (result != NO_ERROR) { - LOGD("Failure reading %ld bytes of EOCD values", readAmount - i); - goto bail; - } - //mEOCD.dump(); - - if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || - mEOCD.mNumEntries != mEOCD.mTotalNumEntries) - { - LOGD("Archive spanning not supported\n"); - result = INVALID_OPERATION; - goto bail; - } - - /* - * So far so good. "mCentralDirSize" is the size in bytes of the - * central directory, so we can just seek back that far to find it. - * We can also seek forward mCentralDirOffset bytes from the - * start of the file. - * - * We're not guaranteed to have the rest of the central dir in the - * buffer, nor are we guaranteed that the central dir will have any - * sort of convenient size. We need to skip to the start of it and - * read the header, then the other goodies. - * - * The only thing we really need right now is the file comment, which - * we're hoping to preserve. - */ - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - LOGD("Failure seeking to central dir offset %ld\n", - mEOCD.mCentralDirOffset); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * Loop through and read the central dir entries. - */ - LOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); - int entry; - for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { - ZipEntry* pEntry = new ZipEntry; - - result = pEntry->initFromCDE(mZipFp); - if (result != NO_ERROR) { - LOGD("initFromCDE failed\n"); - delete pEntry; - goto bail; - } - - mEntries.add(pEntry); - } - - - /* - * If all went well, we should now be back at the EOCD. - */ - { - unsigned char checkBuf[4]; - if (fread(checkBuf, 1, 4, mZipFp) != 4) { - LOGD("EOCD check read failed\n"); - result = INVALID_OPERATION; - goto bail; - } - if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { - LOGD("EOCD read check failed\n"); - result = UNKNOWN_ERROR; - goto bail; - } - LOGV("+++ EOCD read check passed\n"); - } - -bail: - delete[] buf; - return result; -} - - -/* - * Add a new file to the archive. - * - * This requires creating and populating a ZipEntry structure, and copying - * the data into the file at the appropriate position. The "appropriate - * position" is the current location of the central directory, which we - * casually overwrite (we can put it back later). - * - * If we were concerned about safety, we would want to make all changes - * in a temp file and then overwrite the original after everything was - * safely written. Not really a concern for us. - */ -status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, - const char* storageName, int sourceType, int compressionMethod, - ZipEntry** ppEntry) -{ - ZipEntry* pEntry = NULL; - status_t result = NO_ERROR; - long lfhPosn, startPosn, endPosn, uncompressedLen; - FILE* inputFp = NULL; - unsigned long crc; - time_t modWhen; - - if (mReadOnly) - return INVALID_OPERATION; - - assert(compressionMethod == ZipEntry::kCompressDeflated || - compressionMethod == ZipEntry::kCompressStored); - - /* make sure we're in a reasonable state */ - assert(mZipFp != NULL); - assert(mEntries.size() == mEOCD.mTotalNumEntries); - - /* make sure it doesn't already exist */ - if (getEntryByName(storageName) != NULL) - return ALREADY_EXISTS; - - if (!data) { - inputFp = fopen(fileName, FILE_OPEN_RO); - if (inputFp == NULL) - return errnoToStatus(errno); - } - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - - pEntry = new ZipEntry; - pEntry->initNew(storageName, NULL); - - /* - * From here on out, failures are more interesting. - */ - mNeedCDRewrite = true; - - /* - * Write the LFH, even though it's still mostly blank. We need it - * as a place-holder. In theory the LFH isn't necessary, but in - * practice some utilities demand it. - */ - lfhPosn = ftell(mZipFp); - pEntry->mLFH.write(mZipFp); - startPosn = ftell(mZipFp); - - /* - * Copy the data in, possibly compressing it as we go. - */ - if (sourceType == ZipEntry::kCompressStored) { - if (compressionMethod == ZipEntry::kCompressDeflated) { - bool failed = false; - result = compressFpToFp(mZipFp, inputFp, data, size, &crc); - if (result != NO_ERROR) { - LOGD("compression failed, storing\n"); - failed = true; - } else { - /* - * Make sure it has compressed "enough". This probably ought - * to be set through an API call, but I don't expect our - * criteria to change over time. - */ - long src = inputFp ? ftell(inputFp) : size; - long dst = ftell(mZipFp) - startPosn; - if (dst + (dst / 10) > src) { - LOGD("insufficient compression (src=%ld dst=%ld), storing\n", - src, dst); - failed = true; - } - } - - if (failed) { - compressionMethod = ZipEntry::kCompressStored; - if (inputFp) rewind(inputFp); - fseek(mZipFp, startPosn, SEEK_SET); - /* fall through to kCompressStored case */ - } - } - /* handle "no compression" request, or failed compression from above */ - if (compressionMethod == ZipEntry::kCompressStored) { - if (inputFp) { - result = copyFpToFp(mZipFp, inputFp, &crc); - } else { - result = copyDataToFp(mZipFp, data, size, &crc); - } - if (result != NO_ERROR) { - // don't need to truncate; happens in CDE rewrite - LOGD("failed copying data in\n"); - goto bail; - } - } - - // currently seeked to end of file - uncompressedLen = inputFp ? ftell(inputFp) : size; - } else if (sourceType == ZipEntry::kCompressDeflated) { - /* we should support uncompressed-from-compressed, but it's not - * important right now */ - assert(compressionMethod == ZipEntry::kCompressDeflated); - - bool scanResult; - int method; - long compressedLen; - - scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, - &compressedLen, &crc); - if (!scanResult || method != ZipEntry::kCompressDeflated) { - LOGD("this isn't a deflated gzip file?"); - result = UNKNOWN_ERROR; - goto bail; - } - - result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); - if (result != NO_ERROR) { - LOGD("failed copying gzip data in\n"); - goto bail; - } - } else { - assert(false); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * We could write the "Data Descriptor", but there doesn't seem to - * be any point since we're going to go back and write the LFH. - * - * Update file offsets. - */ - endPosn = ftell(mZipFp); // seeked to end of compressed data - - /* - * Success! Fill out new values. - */ - pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, - compressionMethod); - modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); - pEntry->setModWhen(modWhen); - pEntry->setLFHOffset(lfhPosn); - mEOCD.mNumEntries++; - mEOCD.mTotalNumEntries++; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - mEOCD.mCentralDirOffset = endPosn; - - /* - * Go back and write the LFH. - */ - if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - pEntry->mLFH.write(mZipFp); - - /* - * Add pEntry to the list. - */ - mEntries.add(pEntry); - if (ppEntry != NULL) - *ppEntry = pEntry; - pEntry = NULL; - -bail: - if (inputFp != NULL) - fclose(inputFp); - delete pEntry; - return result; -} - -/* - * Add an entry by copying it from another zip file. If "padding" is - * nonzero, the specified number of bytes will be added to the "extra" - * field in the header. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ -status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, - int padding, ZipEntry** ppEntry) -{ - ZipEntry* pEntry = NULL; - status_t result; - long lfhPosn, endPosn; - - if (mReadOnly) - return INVALID_OPERATION; - - /* make sure we're in a reasonable state */ - assert(mZipFp != NULL); - assert(mEntries.size() == mEOCD.mTotalNumEntries); - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - - pEntry = new ZipEntry; - if (pEntry == NULL) { - result = NO_MEMORY; - goto bail; - } - - result = pEntry->initFromExternal(pSourceZip, pSourceEntry); - if (result != NO_ERROR) - goto bail; - if (padding != 0) { - result = pEntry->addPadding(padding); - if (result != NO_ERROR) - goto bail; - } - - /* - * From here on out, failures are more interesting. - */ - mNeedCDRewrite = true; - - /* - * Write the LFH. Since we're not recompressing the data, we already - * have all of the fields filled out. - */ - lfhPosn = ftell(mZipFp); - pEntry->mLFH.write(mZipFp); - - /* - * Copy the data over. - * - * If the "has data descriptor" flag is set, we want to copy the DD - * fields as well. This is a fixed-size area immediately following - * the data. - */ - if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) - { - result = UNKNOWN_ERROR; - goto bail; - } - - off_t copyLen; - copyLen = pSourceEntry->getCompressedLen(); - if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) - copyLen += ZipEntry::kDataDescriptorLen; - - if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) - != NO_ERROR) - { - LOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * Update file offsets. - */ - endPosn = ftell(mZipFp); - - /* - * Success! Fill out new values. - */ - pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset - mEOCD.mNumEntries++; - mEOCD.mTotalNumEntries++; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - mEOCD.mCentralDirOffset = endPosn; - - /* - * Add pEntry to the list. - */ - mEntries.add(pEntry); - if (ppEntry != NULL) - *ppEntry = pEntry; - pEntry = NULL; - - result = NO_ERROR; - -bail: - delete pEntry; - return result; -} - -/* - * Copy all of the bytes in "src" to "dst". - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the data. - */ -status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) -{ - unsigned char tmpBuf[32768]; - size_t count; - - *pCRC32 = crc32(0L, Z_NULL, 0); - - while (1) { - count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); - if (ferror(srcFp) || ferror(dstFp)) - return errnoToStatus(errno); - if (count == 0) - break; - - *pCRC32 = crc32(*pCRC32, tmpBuf, count); - - if (fwrite(tmpBuf, 1, count, dstFp) != count) { - LOGD("fwrite %d bytes failed\n", (int) count); - return UNKNOWN_ERROR; - } - } - - return NO_ERROR; -} - -/* - * Copy all of the bytes in "src" to "dst". - * - * On exit, "dstFp" will be seeked immediately past the data. - */ -status_t ZipFile::copyDataToFp(FILE* dstFp, - const void* data, size_t size, unsigned long* pCRC32) -{ - size_t count; - - *pCRC32 = crc32(0L, Z_NULL, 0); - if (size > 0) { - *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); - if (fwrite(data, 1, size, dstFp) != size) { - LOGD("fwrite %d bytes failed\n", (int) size); - return UNKNOWN_ERROR; - } - } - - return NO_ERROR; -} - -/* - * Copy some of the bytes in "src" to "dst". - * - * If "pCRC32" is NULL, the CRC will not be computed. - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the data just written. - */ -status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, - unsigned long* pCRC32) -{ - unsigned char tmpBuf[32768]; - size_t count; - - if (pCRC32 != NULL) - *pCRC32 = crc32(0L, Z_NULL, 0); - - while (length) { - long readSize; - - readSize = sizeof(tmpBuf); - if (readSize > length) - readSize = length; - - count = fread(tmpBuf, 1, readSize, srcFp); - if ((long) count != readSize) { // error or unexpected EOF - LOGD("fread %d bytes failed\n", (int) readSize); - return UNKNOWN_ERROR; - } - - if (pCRC32 != NULL) - *pCRC32 = crc32(*pCRC32, tmpBuf, count); - - if (fwrite(tmpBuf, 1, count, dstFp) != count) { - LOGD("fwrite %d bytes failed\n", (int) count); - return UNKNOWN_ERROR; - } - - length -= readSize; - } - - return NO_ERROR; -} - -/* - * Compress all of the data in "srcFp" and write it to "dstFp". - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the compressed data. - */ -status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, - const void* data, size_t size, unsigned long* pCRC32) -{ - status_t result = NO_ERROR; - const size_t kBufSize = 32768; - unsigned char* inBuf = NULL; - unsigned char* outBuf = NULL; - z_stream zstream; - bool atEof = false; // no feof() aviailable yet - unsigned long crc; - int zerr; - - /* - * Create an input buffer and an output buffer. - */ - inBuf = new unsigned char[kBufSize]; - outBuf = new unsigned char[kBufSize]; - if (inBuf == NULL || outBuf == NULL) { - result = NO_MEMORY; - goto bail; - } - - /* - * Initialize the zlib stream. - */ - memset(&zstream, 0, sizeof(zstream)); - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.opaque = Z_NULL; - zstream.next_in = NULL; - zstream.avail_in = 0; - zstream.next_out = outBuf; - zstream.avail_out = kBufSize; - zstream.data_type = Z_UNKNOWN; - - zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, - Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); - if (zerr != Z_OK) { - result = UNKNOWN_ERROR; - if (zerr == Z_VERSION_ERROR) { - LOGE("Installed zlib is not compatible with linked version (%s)\n", - ZLIB_VERSION); - } else { - LOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); - } - goto bail; - } - - crc = crc32(0L, Z_NULL, 0); - - /* - * Loop while we have data. - */ - do { - size_t getSize; - int flush; - - /* only read if the input buffer is empty */ - if (zstream.avail_in == 0 && !atEof) { - LOGV("+++ reading %d bytes\n", (int)kBufSize); - if (data) { - getSize = size > kBufSize ? kBufSize : size; - memcpy(inBuf, data, getSize); - data = ((const char*)data) + getSize; - size -= getSize; - } else { - getSize = fread(inBuf, 1, kBufSize, srcFp); - if (ferror(srcFp)) { - LOGD("deflate read failed (errno=%d)\n", errno); - goto z_bail; - } - } - if (getSize < kBufSize) { - LOGV("+++ got %d bytes, EOF reached\n", - (int)getSize); - atEof = true; - } - - crc = crc32(crc, inBuf, getSize); - - zstream.next_in = inBuf; - zstream.avail_in = getSize; - } - - if (atEof) - flush = Z_FINISH; /* tell zlib that we're done */ - else - flush = Z_NO_FLUSH; /* more to come! */ - - zerr = deflate(&zstream, flush); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - LOGD("zlib deflate call failed (zerr=%d)\n", zerr); - result = UNKNOWN_ERROR; - goto z_bail; - } - - /* write when we're full or when we're done */ - if (zstream.avail_out == 0 || - (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) - { - LOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); - if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != - (size_t)(zstream.next_out - outBuf)) - { - LOGD("write %d failed in deflate\n", - (int) (zstream.next_out - outBuf)); - goto z_bail; - } - - zstream.next_out = outBuf; - zstream.avail_out = kBufSize; - } - } while (zerr == Z_OK); - - assert(zerr == Z_STREAM_END); /* other errors should've been caught */ - - *pCRC32 = crc; - -z_bail: - deflateEnd(&zstream); /* free up any allocated structures */ - -bail: - delete[] inBuf; - delete[] outBuf; - - return result; -} - -/* - * Mark an entry as deleted. - * - * We will eventually need to crunch the file down, but if several files - * are being removed (perhaps as part of an "update" process) we can make - * things considerably faster by deferring the removal to "flush" time. - */ -status_t ZipFile::remove(ZipEntry* pEntry) -{ - /* - * Should verify that pEntry is actually part of this archive, and - * not some stray ZipEntry from a different file. - */ - - /* mark entry as deleted, and mark archive as dirty */ - pEntry->setDeleted(); - mNeedCDRewrite = true; - return NO_ERROR; -} - -/* - * Flush any pending writes. - * - * In particular, this will crunch out deleted entries, and write the - * Central Directory and EOCD if we have stomped on them. - */ -status_t ZipFile::flush(void) -{ - status_t result = NO_ERROR; - long eocdPosn; - int i, count; - - if (mReadOnly) - return INVALID_OPERATION; - if (!mNeedCDRewrite) - return NO_ERROR; - - assert(mZipFp != NULL); - - result = crunchArchive(); - if (result != NO_ERROR) - return result; - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) - return UNKNOWN_ERROR; - - count = mEntries.size(); - for (i = 0; i < count; i++) { - ZipEntry* pEntry = mEntries[i]; - pEntry->mCDE.write(mZipFp); - } - - eocdPosn = ftell(mZipFp); - mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; - - mEOCD.write(mZipFp); - - /* - * If we had some stuff bloat up during compression and get replaced - * with plain files, or if we deleted some entries, there's a lot - * of wasted space at the end of the file. Remove it now. - */ - if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { - LOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); - // not fatal - } - - /* should we clear the "newly added" flag in all entries now? */ - - mNeedCDRewrite = false; - return NO_ERROR; -} - -/* - * Crunch deleted files out of an archive by shifting the later files down. - * - * Because we're not using a temp file, we do the operation inside the - * current file. - */ -status_t ZipFile::crunchArchive(void) -{ - status_t result = NO_ERROR; - int i, count; - long delCount, adjust; - -#if 0 - printf("CONTENTS:\n"); - for (i = 0; i < (int) mEntries.size(); i++) { - printf(" %d: lfhOff=%ld del=%d\n", - i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); - } - printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); -#endif - - /* - * Roll through the set of files, shifting them as appropriate. We - * could probably get a slight performance improvement by sliding - * multiple files down at once (because we could use larger reads - * when operating on batches of small files), but it's not that useful. - */ - count = mEntries.size(); - delCount = adjust = 0; - for (i = 0; i < count; i++) { - ZipEntry* pEntry = mEntries[i]; - long span; - - if (pEntry->getLFHOffset() != 0) { - long nextOffset; - - /* Get the length of this entry by finding the offset - * of the next entry. Directory entries don't have - * file offsets, so we need to find the next non-directory - * entry. - */ - nextOffset = 0; - for (int ii = i+1; nextOffset == 0 && ii < count; ii++) - nextOffset = mEntries[ii]->getLFHOffset(); - if (nextOffset == 0) - nextOffset = mEOCD.mCentralDirOffset; - span = nextOffset - pEntry->getLFHOffset(); - - assert(span >= ZipEntry::LocalFileHeader::kLFHLen); - } else { - /* This is a directory entry. It doesn't have - * any actual file contents, so there's no need to - * move anything. - */ - span = 0; - } - - //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", - // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); - - if (pEntry->getDeleted()) { - adjust += span; - delCount++; - - delete pEntry; - mEntries.removeAt(i); - - /* adjust loop control */ - count--; - i--; - } else if (span != 0 && adjust > 0) { - /* shuffle this entry back */ - //printf("+++ Shuffling '%s' back %ld\n", - // pEntry->getFileName(), adjust); - result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, - pEntry->getLFHOffset(), span); - if (result != NO_ERROR) { - /* this is why you use a temp file */ - LOGE("error during crunch - archive is toast\n"); - return result; - } - - pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); - } - } - - /* - * Fix EOCD info. We have to wait until the end to do some of this - * because we use mCentralDirOffset to determine "span" for the - * last entry. - */ - mEOCD.mCentralDirOffset -= adjust; - mEOCD.mNumEntries -= delCount; - mEOCD.mTotalNumEntries -= delCount; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - - assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); - assert(mEOCD.mNumEntries == count); - - return result; -} - -/* - * Works like memmove(), but on pieces of a file. - */ -status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) -{ - if (dst == src || n <= 0) - return NO_ERROR; - - unsigned char readBuf[32768]; - - if (dst < src) { - /* shift stuff toward start of file; must read from start */ - while (n != 0) { - size_t getSize = sizeof(readBuf); - if (getSize > n) - getSize = n; - - if (fseek(fp, (long) src, SEEK_SET) != 0) { - LOGD("filemove src seek %ld failed\n", (long) src); - return UNKNOWN_ERROR; - } - - if (fread(readBuf, 1, getSize, fp) != getSize) { - LOGD("filemove read %ld off=%ld failed\n", - (long) getSize, (long) src); - return UNKNOWN_ERROR; - } - - if (fseek(fp, (long) dst, SEEK_SET) != 0) { - LOGD("filemove dst seek %ld failed\n", (long) dst); - return UNKNOWN_ERROR; - } - - if (fwrite(readBuf, 1, getSize, fp) != getSize) { - LOGD("filemove write %ld off=%ld failed\n", - (long) getSize, (long) dst); - return UNKNOWN_ERROR; - } - - src += getSize; - dst += getSize; - n -= getSize; - } - } else { - /* shift stuff toward end of file; must read from end */ - assert(false); // write this someday, maybe - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - - -/* - * Get the modification time from a file descriptor. - */ -time_t ZipFile::getModTime(int fd) -{ - struct stat sb; - - if (fstat(fd, &sb) < 0) { - LOGD("HEY: fstat on fd %d failed\n", fd); - return (time_t) -1; - } - - return sb.st_mtime; -} - - -#if 0 /* this is a bad idea */ -/* - * Get a copy of the Zip file descriptor. - * - * We don't allow this if the file was opened read-write because we tend - * to leave the file contents in an uncertain state between calls to - * flush(). The duplicated file descriptor should only be valid for reads. - */ -int ZipFile::getZipFd(void) const -{ - if (!mReadOnly) - return INVALID_OPERATION; - assert(mZipFp != NULL); - - int fd; - fd = dup(fileno(mZipFp)); - if (fd < 0) { - LOGD("didn't work, errno=%d\n", errno); - } - - return fd; -} -#endif - - -#if 0 -/* - * Expand data. - */ -bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const -{ - return false; -} -#endif - -// free the memory when you're done -void* ZipFile::uncompress(const ZipEntry* entry) -{ - size_t unlen = entry->getUncompressedLen(); - size_t clen = entry->getCompressedLen(); - - void* buf = malloc(unlen); - if (buf == NULL) { - return NULL; - } - - fseek(mZipFp, 0, SEEK_SET); - - off_t offset = entry->getFileOffset(); - if (fseek(mZipFp, offset, SEEK_SET) != 0) { - goto bail; - } - - switch (entry->getCompressionMethod()) - { - case ZipEntry::kCompressStored: { - ssize_t amt = fread(buf, 1, unlen, mZipFp); - if (amt != (ssize_t)unlen) { - goto bail; - } -#if 0 - printf("data...\n"); - const unsigned char* p = (unsigned char*)buf; - const unsigned char* end = p+unlen; - for (int i=0; i<32 && p < end; i++) { - printf("0x%08x ", (int)(offset+(i*0x10))); - for (int j=0; j<0x10 && p < end; j++) { - printf(" %02x", *p); - p++; - } - printf("\n"); - } -#endif - - } - break; - case ZipEntry::kCompressDeflated: { - if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { - goto bail; - } - } - break; - default: - goto bail; - } - return buf; - -bail: - free(buf); - return NULL; -} - - -/* - * =========================================================================== - * ZipFile::EndOfCentralDir - * =========================================================================== - */ - -/* - * Read the end-of-central-dir fields. - * - * "buf" should be positioned at the EOCD signature, and should contain - * the entire EOCD area including the comment. - */ -status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) -{ - /* don't allow re-use */ - assert(mComment == NULL); - - if (len < kEOCDLen) { - /* looks like ZIP file got truncated */ - LOGD(" Zip EOCD: expected >= %d bytes, found %d\n", - kEOCDLen, len); - return INVALID_OPERATION; - } - - /* this should probably be an assert() */ - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) - return UNKNOWN_ERROR; - - mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); - mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); - mNumEntries = ZipEntry::getShortLE(&buf[0x08]); - mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); - mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); - mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); - mCommentLen = ZipEntry::getShortLE(&buf[0x14]); - - // TODO: validate mCentralDirOffset - - if (mCommentLen > 0) { - if (kEOCDLen + mCommentLen > len) { - LOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", - kEOCDLen, mCommentLen, len); - return UNKNOWN_ERROR; - } - mComment = new unsigned char[mCommentLen]; - memcpy(mComment, buf + kEOCDLen, mCommentLen); - } - - return NO_ERROR; -} - -/* - * Write an end-of-central-directory section. - */ -status_t ZipFile::EndOfCentralDir::write(FILE* fp) -{ - unsigned char buf[kEOCDLen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mDiskNumber); - ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); - ZipEntry::putShortLE(&buf[0x08], mNumEntries); - ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); - ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); - ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); - ZipEntry::putShortLE(&buf[0x14], mCommentLen); - - if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) - return UNKNOWN_ERROR; - if (mCommentLen > 0) { - assert(mComment != NULL); - if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - -/* - * Dump the contents of an EndOfCentralDir object. - */ -void ZipFile::EndOfCentralDir::dump(void) const -{ - LOGD(" EndOfCentralDir contents:\n"); - LOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", - mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); - LOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", - mCentralDirSize, mCentralDirOffset, mCommentLen); -} - diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 2ef0347c2d45..3f370696289a 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include "ZipFile.h" #include "Bundle.h" #include "SourcePos.h" diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index fdc859c01c4d..2d8973d89b72 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -17,7 +17,10 @@ LOCAL_SRC_FILES := \ ResourceTable.cpp \ Images.cpp \ Resource.cpp \ - SourcePos.cpp + SourcePos.cpp \ + ZipEntry.cpp \ + ZipFile.cpp + LOCAL_CFLAGS += -Wno-format-y2k diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 32df6317e288..0e889f5d1a1e 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 6ff82690105d..a33b4d7113dd 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h index b5c334c7700a..34ca5e5e4d09 100644 --- a/tools/aapt/Main.h +++ b/tools/aapt/Main.h @@ -12,7 +12,7 @@ #include #include "Bundle.h" #include "AaptAssets.h" -#include +#include "ZipFile.h" extern int doVersion(Bundle* bundle); extern int doList(Bundle* bundle); diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 890052972d9d..84241699db3f 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp new file mode 100644 index 000000000000..bed033361c58 --- /dev/null +++ b/tools/aapt/ZipEntry.cpp @@ -0,0 +1,696 @@ +/* + * Copyright (C) 2006 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. + */ + +// +// Access to entries in a Zip archive. +// + +#define LOG_TAG "zip" + +#include "ZipEntry.h" +#include + +#include +#include +#include + +using namespace android; + +/* + * Initialize a new ZipEntry structure from a FILE* positioned at a + * CentralDirectoryEntry. + * + * On exit, the file pointer will be at the start of the next CDE or + * at the EOCD. + */ +status_t ZipEntry::initFromCDE(FILE* fp) +{ + status_t result; + long posn; + bool hasDD; + + //LOGV("initFromCDE ---\n"); + + /* read the CDE */ + result = mCDE.read(fp); + if (result != NO_ERROR) { + LOGD("mCDE.read failed\n"); + return result; + } + + //mCDE.dump(); + + /* using the info in the CDE, go load up the LFH */ + posn = ftell(fp); + if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { + LOGD("local header seek failed (%ld)\n", + mCDE.mLocalHeaderRelOffset); + return UNKNOWN_ERROR; + } + + result = mLFH.read(fp); + if (result != NO_ERROR) { + LOGD("mLFH.read failed\n"); + return result; + } + + if (fseek(fp, posn, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + //mLFH.dump(); + + /* + * We *might* need to read the Data Descriptor at this point and + * integrate it into the LFH. If this bit is set, the CRC-32, + * compressed size, and uncompressed size will be zero. In practice + * these seem to be rare. + */ + hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; + if (hasDD) { + // do something clever + //LOGD("+++ has data descriptor\n"); + } + + /* + * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" + * flag is set, because the LFH is incomplete. (Not a problem, since we + * prefer the CDE values.) + */ + if (!hasDD && !compareHeaders()) { + LOGW("WARNING: header mismatch\n"); + // keep going? + } + + /* + * If the mVersionToExtract is greater than 20, we may have an + * issue unpacking the record -- could be encrypted, compressed + * with something we don't support, or use Zip64 extensions. We + * can defer worrying about that to when we're extracting data. + */ + + return NO_ERROR; +} + +/* + * Initialize a new entry. Pass in the file name and an optional comment. + * + * Initializes the CDE and the LFH. + */ +void ZipEntry::initNew(const char* fileName, const char* comment) +{ + assert(fileName != NULL && *fileName != '\0'); // name required + + /* most fields are properly initialized by constructor */ + mCDE.mVersionMadeBy = kDefaultMadeBy; + mCDE.mVersionToExtract = kDefaultVersion; + mCDE.mCompressionMethod = kCompressStored; + mCDE.mFileNameLength = strlen(fileName); + if (comment != NULL) + mCDE.mFileCommentLength = strlen(comment); + mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + strcpy((char*) mCDE.mFileName, fileName); + } + if (mCDE.mFileCommentLength > 0) { + /* TODO: stop assuming null-terminated ASCII here? */ + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + strcpy((char*) mCDE.mFileComment, comment); + } + + copyCDEtoLFH(); +} + +/* + * Initialize a new entry, starting with the ZipEntry from a different + * archive. + * + * Initializes the CDE and the LFH. + */ +status_t ZipEntry::initFromExternal(const ZipFile* pZipFile, + const ZipEntry* pEntry) +{ + /* + * Copy everything in the CDE over, then fix up the hairy bits. + */ + memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE)); + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + if (mCDE.mFileName == NULL) + return NO_MEMORY; + strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName); + } + if (mCDE.mFileCommentLength > 0) { + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + if (mCDE.mFileComment == NULL) + return NO_MEMORY; + strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment); + } + if (mCDE.mExtraFieldLength > 0) { + /* we null-terminate this, though it may not be a string */ + mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1]; + if (mCDE.mExtraField == NULL) + return NO_MEMORY; + memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField, + mCDE.mExtraFieldLength+1); + } + + /* construct the LFH from the CDE */ + copyCDEtoLFH(); + + /* + * The LFH "extra" field is independent of the CDE "extra", so we + * handle it here. + */ + assert(mLFH.mExtraField == NULL); + mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; + if (mLFH.mExtraFieldLength > 0) { + mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; + if (mLFH.mExtraField == NULL) + return NO_MEMORY; + memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, + mLFH.mExtraFieldLength+1); + } + + return NO_ERROR; +} + +/* + * Insert pad bytes in the LFH by tweaking the "extra" field. This will + * potentially confuse something that put "extra" data in here earlier, + * but I can't find an actual problem. + */ +status_t ZipEntry::addPadding(int padding) +{ + if (padding <= 0) + return INVALID_OPERATION; + + //LOGI("HEY: adding %d pad bytes to existing %d in %s\n", + // padding, mLFH.mExtraFieldLength, mCDE.mFileName); + + if (mLFH.mExtraFieldLength > 0) { + /* extend existing field */ + unsigned char* newExtra; + + newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; + if (newExtra == NULL) + return NO_MEMORY; + memset(newExtra + mLFH.mExtraFieldLength, 0, padding); + memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); + + delete[] mLFH.mExtraField; + mLFH.mExtraField = newExtra; + mLFH.mExtraFieldLength += padding; + } else { + /* create new field */ + mLFH.mExtraField = new unsigned char[padding]; + memset(mLFH.mExtraField, 0, padding); + mLFH.mExtraFieldLength = padding; + } + + return NO_ERROR; +} + +/* + * Set the fields in the LFH equal to the corresponding fields in the CDE. + * + * This does not touch the LFH "extra" field. + */ +void ZipEntry::copyCDEtoLFH(void) +{ + mLFH.mVersionToExtract = mCDE.mVersionToExtract; + mLFH.mGPBitFlag = mCDE.mGPBitFlag; + mLFH.mCompressionMethod = mCDE.mCompressionMethod; + mLFH.mLastModFileTime = mCDE.mLastModFileTime; + mLFH.mLastModFileDate = mCDE.mLastModFileDate; + mLFH.mCRC32 = mCDE.mCRC32; + mLFH.mCompressedSize = mCDE.mCompressedSize; + mLFH.mUncompressedSize = mCDE.mUncompressedSize; + mLFH.mFileNameLength = mCDE.mFileNameLength; + // the "extra field" is independent + + delete[] mLFH.mFileName; + if (mLFH.mFileNameLength > 0) { + mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; + strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); + } else { + mLFH.mFileName = NULL; + } +} + +/* + * Set some information about a file after we add it. + */ +void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod) +{ + mCDE.mCompressionMethod = compressionMethod; + mCDE.mCRC32 = crc32; + mCDE.mCompressedSize = compLen; + mCDE.mUncompressedSize = uncompLen; + mCDE.mCompressionMethod = compressionMethod; + if (compressionMethod == kCompressDeflated) { + mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used + } + copyCDEtoLFH(); +} + +/* + * See if the data in mCDE and mLFH match up. This is mostly useful for + * debugging these classes, but it can be used to identify damaged + * archives. + * + * Returns "false" if they differ. + */ +bool ZipEntry::compareHeaders(void) const +{ + if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { + LOGV("cmp: VersionToExtract\n"); + return false; + } + if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { + LOGV("cmp: GPBitFlag\n"); + return false; + } + if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { + LOGV("cmp: CompressionMethod\n"); + return false; + } + if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { + LOGV("cmp: LastModFileTime\n"); + return false; + } + if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { + LOGV("cmp: LastModFileDate\n"); + return false; + } + if (mCDE.mCRC32 != mLFH.mCRC32) { + LOGV("cmp: CRC32\n"); + return false; + } + if (mCDE.mCompressedSize != mLFH.mCompressedSize) { + LOGV("cmp: CompressedSize\n"); + return false; + } + if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { + LOGV("cmp: UncompressedSize\n"); + return false; + } + if (mCDE.mFileNameLength != mLFH.mFileNameLength) { + LOGV("cmp: FileNameLength\n"); + return false; + } +#if 0 // this seems to be used for padding, not real data + if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { + LOGV("cmp: ExtraFieldLength\n"); + return false; + } +#endif + if (mCDE.mFileName != NULL) { + if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { + LOGV("cmp: FileName\n"); + return false; + } + } + + return true; +} + + +/* + * Convert the DOS date/time stamp into a UNIX time stamp. + */ +time_t ZipEntry::getModWhen(void) const +{ + struct tm parts; + + parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; + parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; + parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; + parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); + parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; + parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; + parts.tm_wday = parts.tm_yday = 0; + parts.tm_isdst = -1; // DST info "not available" + + return mktime(&parts); +} + +/* + * Set the CDE/LFH timestamp from UNIX time. + */ +void ZipEntry::setModWhen(time_t when) +{ +#ifdef HAVE_LOCALTIME_R + struct tm tmResult; +#endif + time_t even; + unsigned short zdate, ztime; + + struct tm* ptm; + + /* round up to an even number of seconds */ + even = (time_t)(((unsigned long)(when) + 1) & (~1)); + + /* expand */ +#ifdef HAVE_LOCALTIME_R + ptm = localtime_r(&even, &tmResult); +#else + ptm = localtime(&even); +#endif + + int year; + year = ptm->tm_year; + if (year < 80) + year = 80; + + zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; + ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; + + mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; + mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; +} + + +/* + * =========================================================================== + * ZipEntry::LocalFileHeader + * =========================================================================== + */ + +/* + * Read a local file header. + * + * On entry, "fp" points to the signature at the start of the header. + * On exit, "fp" points to the start of data. + */ +status_t ZipEntry::LocalFileHeader::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kLFHLen]; + + assert(mFileName == NULL); + assert(mExtraField == NULL); + + if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + LOGD("whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); + mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); + + // TODO: validate sizes + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* grab extra field */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a local file header. + */ +status_t ZipEntry::LocalFileHeader::write(FILE* fp) +{ + unsigned char buf[kLFHLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x0e], mCRC32); + ZipEntry::putLongLE(&buf[0x12], mCompressedSize); + ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); + + if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Dump the contents of a LocalFileHeader object. + */ +void ZipEntry::LocalFileHeader::dump(void) const +{ + LOGD(" LocalFileHeader contents:\n"); + LOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionToExtract, mGPBitFlag, mCompressionMethod); + LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + LOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + LOGD(" filenameLen=%u extraLen=%u\n", + mFileNameLength, mExtraFieldLength); + if (mFileName != NULL) + LOGD(" filename: '%s'\n", mFileName); +} + + +/* + * =========================================================================== + * ZipEntry::CentralDirEntry + * =========================================================================== + */ + +/* + * Read the central dir entry that appears next in the file. + * + * On entry, "fp" should be positioned on the signature bytes for the + * entry. On exit, "fp" will point at the signature word for the next + * entry or for the EOCD. + */ +status_t ZipEntry::CentralDirEntry::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kCDELen]; + + /* no re-use */ + assert(mFileName == NULL); + assert(mExtraField == NULL); + assert(mFileComment == NULL); + + if (fread(buf, 1, kCDELen, fp) != kCDELen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + LOGD("Whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); + mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); + mCRC32 = ZipEntry::getLongLE(&buf[0x10]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); + mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); + mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); + mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); + mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); + mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); + + // TODO: validate sizes and offsets + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* read "extra field" */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + + + /* grab comment, if any */ + if (mFileCommentLength != 0) { + mFileComment = new unsigned char[mFileCommentLength+1]; + if (mFileComment == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + { + result = UNKNOWN_ERROR; + goto bail; + } + mFileComment[mFileCommentLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a central dir entry. + */ +status_t ZipEntry::CentralDirEntry::write(FILE* fp) +{ + unsigned char buf[kCDELen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); + ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x10], mCRC32); + ZipEntry::putLongLE(&buf[0x14], mCompressedSize); + ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); + ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); + ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); + ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); + ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); + ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); + + if (fwrite(buf, 1, kCDELen, fp) != kCDELen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + /* write comment */ + if (mFileCommentLength != 0) { + if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of a CentralDirEntry object. + */ +void ZipEntry::CentralDirEntry::dump(void) const +{ + LOGD(" CentralDirEntry contents:\n"); + LOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); + LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + LOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + LOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", + mFileNameLength, mExtraFieldLength, mFileCommentLength); + LOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", + mDiskNumberStart, mInternalAttrs, mExternalAttrs, + mLocalHeaderRelOffset); + + if (mFileName != NULL) + LOGD(" filename: '%s'\n", mFileName); + if (mFileComment != NULL) + LOGD(" comment: '%s'\n", mFileComment); +} + diff --git a/tools/aapt/ZipEntry.h b/tools/aapt/ZipEntry.h new file mode 100644 index 000000000000..7f721b46321a --- /dev/null +++ b/tools/aapt/ZipEntry.h @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2006 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. + */ + +// +// Zip archive entries. +// +// The ZipEntry class is tightly meshed with the ZipFile class. +// +#ifndef __LIBS_ZIPENTRY_H +#define __LIBS_ZIPENTRY_H + +#include + +#include +#include + +namespace android { + +class ZipFile; + +/* + * ZipEntry objects represent a single entry in a Zip archive. + * + * You can use one of these to get or set information about an entry, but + * there are no functions here for accessing the data itself. (We could + * tuck a pointer to the ZipFile in here for convenience, but that raises + * the likelihood of using ZipEntry objects after discarding the ZipFile.) + * + * File information is stored in two places: next to the file data (the Local + * File Header, and possibly a Data Descriptor), and at the end of the file + * (the Central Directory Entry). The two must be kept in sync. + */ +class ZipEntry { +public: + friend class ZipFile; + + ZipEntry(void) + : mDeleted(false), mMarked(false) + {} + ~ZipEntry(void) {} + + /* + * Returns "true" if the data is compressed. + */ + bool isCompressed(void) const { + return mCDE.mCompressionMethod != kCompressStored; + } + int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } + + /* + * Return the uncompressed length. + */ + off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } + + /* + * Return the compressed length. For uncompressed data, this returns + * the same thing as getUncompresesdLen(). + */ + off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } + + /* + * Return the absolute file offset of the start of the compressed or + * uncompressed data. + */ + off_t getFileOffset(void) const { + return mCDE.mLocalHeaderRelOffset + + LocalFileHeader::kLFHLen + + mLFH.mFileNameLength + + mLFH.mExtraFieldLength; + } + + /* + * Return the data CRC. + */ + unsigned long getCRC32(void) const { return mCDE.mCRC32; } + + /* + * Return file modification time in UNIX seconds-since-epoch. + */ + time_t getModWhen(void) const; + + /* + * Return the archived file name. + */ + const char* getFileName(void) const { return (const char*) mCDE.mFileName; } + + /* + * Application-defined "mark". Can be useful when synchronizing the + * contents of an archive with contents on disk. + */ + bool getMarked(void) const { return mMarked; } + void setMarked(bool val) { mMarked = val; } + + /* + * Some basic functions for raw data manipulation. "LE" means + * Little Endian. + */ + static inline unsigned short getShortLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8); + } + static inline unsigned long getLongLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + } + static inline void putShortLE(unsigned char* buf, short val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + } + static inline void putLongLE(unsigned char* buf, long val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + buf[2] = (unsigned char) (val >> 16); + buf[3] = (unsigned char) (val >> 24); + } + + /* defined for Zip archives */ + enum { + kCompressStored = 0, // no compression + // shrunk = 1, + // reduced 1 = 2, + // reduced 2 = 3, + // reduced 3 = 4, + // reduced 4 = 5, + // imploded = 6, + // tokenized = 7, + kCompressDeflated = 8, // standard deflate + // Deflate64 = 9, + // lib imploded = 10, + // reserved = 11, + // bzip2 = 12, + }; + + /* + * Deletion flag. If set, the entry will be removed on the next + * call to "flush". + */ + bool getDeleted(void) const { return mDeleted; } + +protected: + /* + * Initialize the structure from the file, which is pointing at + * our Central Directory entry. + */ + status_t initFromCDE(FILE* fp); + + /* + * Initialize the structure for a new file. We need the filename + * and comment so that we can properly size the LFH area. The + * filename is mandatory, the comment is optional. + */ + void initNew(const char* fileName, const char* comment); + + /* + * Initialize the structure with the contents of a ZipEntry from + * another file. + */ + status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); + + /* + * Add some pad bytes to the LFH. We do this by adding or resizing + * the "extra" field. + */ + status_t addPadding(int padding); + + /* + * Set information about the data for this entry. + */ + void setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod); + + /* + * Set the modification date. + */ + void setModWhen(time_t when); + + /* + * Return the offset of the local file header. + */ + off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } + + /* + * Set the offset of the local file header, relative to the start of + * the current file. + */ + void setLFHOffset(off_t offset) { + mCDE.mLocalHeaderRelOffset = (long) offset; + } + + /* mark for deletion; used by ZipFile::remove() */ + void setDeleted(void) { mDeleted = true; } + +private: + /* these are private and not defined */ + ZipEntry(const ZipEntry& src); + ZipEntry& operator=(const ZipEntry& src); + + /* returns "true" if the CDE and the LFH agree */ + bool compareHeaders(void) const; + void copyCDEtoLFH(void); + + bool mDeleted; // set if entry is pending deletion + bool mMarked; // app-defined marker + + /* + * Every entry in the Zip archive starts off with one of these. + */ + class LocalFileHeader { + public: + LocalFileHeader(void) : + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileName(NULL), + mExtraField(NULL) + {} + virtual ~LocalFileHeader(void) { + delete[] mFileName; + delete[] mExtraField; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned char* mFileName; + unsigned char* mExtraField; + + enum { + kSignature = 0x04034b50, + kLFHLen = 30, // LocalFileHdr len, excl. var fields + }; + + void dump(void) const; + }; + + /* + * Every entry in the Zip archive has one of these in the "central + * directory" at the end of the file. + */ + class CentralDirEntry { + public: + CentralDirEntry(void) : + mVersionMadeBy(0), + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileCommentLength(0), + mDiskNumberStart(0), + mInternalAttrs(0), + mExternalAttrs(0), + mLocalHeaderRelOffset(0), + mFileName(NULL), + mExtraField(NULL), + mFileComment(NULL) + {} + virtual ~CentralDirEntry(void) { + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionMadeBy; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned short mFileCommentLength; + unsigned short mDiskNumberStart; + unsigned short mInternalAttrs; + unsigned long mExternalAttrs; + unsigned long mLocalHeaderRelOffset; + unsigned char* mFileName; + unsigned char* mExtraField; + unsigned char* mFileComment; + + void dump(void) const; + + enum { + kSignature = 0x02014b50, + kCDELen = 46, // CentralDirEnt len, excl. var fields + }; + }; + + enum { + //kDataDescriptorSignature = 0x08074b50, // currently unused + kDataDescriptorLen = 16, // four 32-bit fields + + kDefaultVersion = 20, // need deflate, nothing much else + kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 + kUsesDataDescr = 0x0008, // GPBitFlag bit 3 + }; + + LocalFileHeader mLFH; + CentralDirEntry mCDE; +}; + +}; // namespace android + +#endif // __LIBS_ZIPENTRY_H diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp new file mode 100644 index 000000000000..62c9383fc22a --- /dev/null +++ b/tools/aapt/ZipFile.cpp @@ -0,0 +1,1297 @@ +/* + * Copyright (C) 2006 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. + */ + +// +// Access to Zip archives. +// + +#define LOG_TAG "zip" + +#include +#include + +#include "ZipFile.h" + +#include +#define DEF_MEM_LEVEL 8 // normally in zutil.h? + +#include +#include +#include +#include + +using namespace android; + +/* + * Some environments require the "b", some choke on it. + */ +#define FILE_OPEN_RO "rb" +#define FILE_OPEN_RW "r+b" +#define FILE_OPEN_RW_CREATE "w+b" + +/* should live somewhere else? */ +static status_t errnoToStatus(int err) +{ + if (err == ENOENT) + return NAME_NOT_FOUND; + else if (err == EACCES) + return PERMISSION_DENIED; + else + return UNKNOWN_ERROR; +} + +/* + * Open a file and parse its guts. + */ +status_t ZipFile::open(const char* zipFileName, int flags) +{ + bool newArchive = false; + + assert(mZipFp == NULL); // no reopen + + if ((flags & kOpenTruncate)) + flags |= kOpenCreate; // trunc implies create + + if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) + return INVALID_OPERATION; // not both + if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) + return INVALID_OPERATION; // not neither + if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) + return INVALID_OPERATION; // create requires write + + if (flags & kOpenTruncate) { + newArchive = true; + } else { + newArchive = (access(zipFileName, F_OK) != 0); + if (!(flags & kOpenCreate) && newArchive) { + /* not creating, must already exist */ + LOGD("File %s does not exist", zipFileName); + return NAME_NOT_FOUND; + } + } + + /* open the file */ + const char* openflags; + if (flags & kOpenReadWrite) { + if (newArchive) + openflags = FILE_OPEN_RW_CREATE; + else + openflags = FILE_OPEN_RW; + } else { + openflags = FILE_OPEN_RO; + } + mZipFp = fopen(zipFileName, openflags); + if (mZipFp == NULL) { + int err = errno; + LOGD("fopen failed: %d\n", err); + return errnoToStatus(err); + } + + status_t result; + if (!newArchive) { + /* + * Load the central directory. If that fails, then this probably + * isn't a Zip archive. + */ + result = readCentralDir(); + } else { + /* + * Newly-created. The EndOfCentralDir constructor actually + * sets everything to be the way we want it (all zeroes). We + * set mNeedCDRewrite so that we create *something* if the + * caller doesn't add any files. (We could also just unlink + * the file if it's brand new and nothing was added, but that's + * probably doing more than we really should -- the user might + * have a need for empty zip files.) + */ + mNeedCDRewrite = true; + result = NO_ERROR; + } + + if (flags & kOpenReadOnly) + mReadOnly = true; + else + assert(!mReadOnly); + + return result; +} + +/* + * Return the Nth entry in the archive. + */ +ZipEntry* ZipFile::getEntryByIndex(int idx) const +{ + if (idx < 0 || idx >= (int) mEntries.size()) + return NULL; + + return mEntries[idx]; +} + +/* + * Find an entry by name. + */ +ZipEntry* ZipFile::getEntryByName(const char* fileName) const +{ + /* + * Do a stupid linear string-compare search. + * + * There are various ways to speed this up, especially since it's rare + * to intermingle changes to the archive with "get by name" calls. We + * don't want to sort the mEntries vector itself, however, because + * it's used to recreate the Central Directory. + * + * (Hash table works, parallel list of pointers in sorted order is good.) + */ + int idx; + + for (idx = mEntries.size()-1; idx >= 0; idx--) { + ZipEntry* pEntry = mEntries[idx]; + if (!pEntry->getDeleted() && + strcmp(fileName, pEntry->getFileName()) == 0) + { + return pEntry; + } + } + + return NULL; +} + +/* + * Empty the mEntries vector. + */ +void ZipFile::discardEntries(void) +{ + int count = mEntries.size(); + + while (--count >= 0) + delete mEntries[count]; + + mEntries.clear(); +} + + +/* + * Find the central directory and read the contents. + * + * The fun thing about ZIP archives is that they may or may not be + * readable from start to end. In some cases, notably for archives + * that were written to stdout, the only length information is in the + * central directory at the end of the file. + * + * Of course, the central directory can be followed by a variable-length + * comment field, so we have to scan through it backwards. The comment + * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + * This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that everbody handles + * it though, so we're in pretty good company if this fails. + */ +status_t ZipFile::readCentralDir(void) +{ + status_t result = NO_ERROR; + unsigned char* buf = NULL; + off_t fileLength, seekStart; + long readAmount; + int i; + + fseek(mZipFp, 0, SEEK_END); + fileLength = ftell(mZipFp); + rewind(mZipFp); + + /* too small to be a ZIP archive? */ + if (fileLength < EndOfCentralDir::kEOCDLen) { + LOGD("Length is %ld -- too small\n", (long)fileLength); + result = INVALID_OPERATION; + goto bail; + } + + buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; + if (buf == NULL) { + LOGD("Failure allocating %d bytes for EOCD search", + EndOfCentralDir::kMaxEOCDSearch); + result = NO_MEMORY; + goto bail; + } + + if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { + seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; + readAmount = EndOfCentralDir::kMaxEOCDSearch; + } else { + seekStart = 0; + readAmount = (long) fileLength; + } + if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { + LOGD("Failure seeking to end of zip at %ld", (long) seekStart); + result = UNKNOWN_ERROR; + goto bail; + } + + /* read the last part of the file into the buffer */ + if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { + LOGD("short file? wanted %ld\n", readAmount); + result = UNKNOWN_ERROR; + goto bail; + } + + /* find the end-of-central-dir magic */ + for (i = readAmount - 4; i >= 0; i--) { + if (buf[i] == 0x50 && + ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) + { + LOGV("+++ Found EOCD at buf+%d\n", i); + break; + } + } + if (i < 0) { + LOGD("EOCD not found, not Zip\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* extract eocd values */ + result = mEOCD.readBuf(buf + i, readAmount - i); + if (result != NO_ERROR) { + LOGD("Failure reading %ld bytes of EOCD values", readAmount - i); + goto bail; + } + //mEOCD.dump(); + + if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || + mEOCD.mNumEntries != mEOCD.mTotalNumEntries) + { + LOGD("Archive spanning not supported\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* + * So far so good. "mCentralDirSize" is the size in bytes of the + * central directory, so we can just seek back that far to find it. + * We can also seek forward mCentralDirOffset bytes from the + * start of the file. + * + * We're not guaranteed to have the rest of the central dir in the + * buffer, nor are we guaranteed that the central dir will have any + * sort of convenient size. We need to skip to the start of it and + * read the header, then the other goodies. + * + * The only thing we really need right now is the file comment, which + * we're hoping to preserve. + */ + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + LOGD("Failure seeking to central dir offset %ld\n", + mEOCD.mCentralDirOffset); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Loop through and read the central dir entries. + */ + LOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); + int entry; + for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { + ZipEntry* pEntry = new ZipEntry; + + result = pEntry->initFromCDE(mZipFp); + if (result != NO_ERROR) { + LOGD("initFromCDE failed\n"); + delete pEntry; + goto bail; + } + + mEntries.add(pEntry); + } + + + /* + * If all went well, we should now be back at the EOCD. + */ + { + unsigned char checkBuf[4]; + if (fread(checkBuf, 1, 4, mZipFp) != 4) { + LOGD("EOCD check read failed\n"); + result = INVALID_OPERATION; + goto bail; + } + if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { + LOGD("EOCD read check failed\n"); + result = UNKNOWN_ERROR; + goto bail; + } + LOGV("+++ EOCD read check passed\n"); + } + +bail: + delete[] buf; + return result; +} + + +/* + * Add a new file to the archive. + * + * This requires creating and populating a ZipEntry structure, and copying + * the data into the file at the appropriate position. The "appropriate + * position" is the current location of the central directory, which we + * casually overwrite (we can put it back later). + * + * If we were concerned about safety, we would want to make all changes + * in a temp file and then overwrite the original after everything was + * safely written. Not really a concern for us. + */ +status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result = NO_ERROR; + long lfhPosn, startPosn, endPosn, uncompressedLen; + FILE* inputFp = NULL; + unsigned long crc; + time_t modWhen; + + if (mReadOnly) + return INVALID_OPERATION; + + assert(compressionMethod == ZipEntry::kCompressDeflated || + compressionMethod == ZipEntry::kCompressStored); + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + /* make sure it doesn't already exist */ + if (getEntryByName(storageName) != NULL) + return ALREADY_EXISTS; + + if (!data) { + inputFp = fopen(fileName, FILE_OPEN_RO); + if (inputFp == NULL) + return errnoToStatus(errno); + } + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + pEntry->initNew(storageName, NULL); + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH, even though it's still mostly blank. We need it + * as a place-holder. In theory the LFH isn't necessary, but in + * practice some utilities demand it. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + startPosn = ftell(mZipFp); + + /* + * Copy the data in, possibly compressing it as we go. + */ + if (sourceType == ZipEntry::kCompressStored) { + if (compressionMethod == ZipEntry::kCompressDeflated) { + bool failed = false; + result = compressFpToFp(mZipFp, inputFp, data, size, &crc); + if (result != NO_ERROR) { + LOGD("compression failed, storing\n"); + failed = true; + } else { + /* + * Make sure it has compressed "enough". This probably ought + * to be set through an API call, but I don't expect our + * criteria to change over time. + */ + long src = inputFp ? ftell(inputFp) : size; + long dst = ftell(mZipFp) - startPosn; + if (dst + (dst / 10) > src) { + LOGD("insufficient compression (src=%ld dst=%ld), storing\n", + src, dst); + failed = true; + } + } + + if (failed) { + compressionMethod = ZipEntry::kCompressStored; + if (inputFp) rewind(inputFp); + fseek(mZipFp, startPosn, SEEK_SET); + /* fall through to kCompressStored case */ + } + } + /* handle "no compression" request, or failed compression from above */ + if (compressionMethod == ZipEntry::kCompressStored) { + if (inputFp) { + result = copyFpToFp(mZipFp, inputFp, &crc); + } else { + result = copyDataToFp(mZipFp, data, size, &crc); + } + if (result != NO_ERROR) { + // don't need to truncate; happens in CDE rewrite + LOGD("failed copying data in\n"); + goto bail; + } + } + + // currently seeked to end of file + uncompressedLen = inputFp ? ftell(inputFp) : size; + } else if (sourceType == ZipEntry::kCompressDeflated) { + /* we should support uncompressed-from-compressed, but it's not + * important right now */ + assert(compressionMethod == ZipEntry::kCompressDeflated); + + bool scanResult; + int method; + long compressedLen; + + scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, + &compressedLen, &crc); + if (!scanResult || method != ZipEntry::kCompressDeflated) { + LOGD("this isn't a deflated gzip file?"); + result = UNKNOWN_ERROR; + goto bail; + } + + result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); + if (result != NO_ERROR) { + LOGD("failed copying gzip data in\n"); + goto bail; + } + } else { + assert(false); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * We could write the "Data Descriptor", but there doesn't seem to + * be any point since we're going to go back and write the LFH. + * + * Update file offsets. + */ + endPosn = ftell(mZipFp); // seeked to end of compressed data + + /* + * Success! Fill out new values. + */ + pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, + compressionMethod); + modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); + pEntry->setModWhen(modWhen); + pEntry->setLFHOffset(lfhPosn); + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Go back and write the LFH. + */ + if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + pEntry->mLFH.write(mZipFp); + + /* + * Add pEntry to the list. + */ + mEntries.add(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + +bail: + if (inputFp != NULL) + fclose(inputFp); + delete pEntry; + return result; +} + +/* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ +status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result; + long lfhPosn, endPosn; + + if (mReadOnly) + return INVALID_OPERATION; + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + if (pEntry == NULL) { + result = NO_MEMORY; + goto bail; + } + + result = pEntry->initFromExternal(pSourceZip, pSourceEntry); + if (result != NO_ERROR) + goto bail; + if (padding != 0) { + result = pEntry->addPadding(padding); + if (result != NO_ERROR) + goto bail; + } + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH. Since we're not recompressing the data, we already + * have all of the fields filled out. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + + /* + * Copy the data over. + * + * If the "has data descriptor" flag is set, we want to copy the DD + * fields as well. This is a fixed-size area immediately following + * the data. + */ + if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) + { + result = UNKNOWN_ERROR; + goto bail; + } + + off_t copyLen; + copyLen = pSourceEntry->getCompressedLen(); + if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) + copyLen += ZipEntry::kDataDescriptorLen; + + if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) + != NO_ERROR) + { + LOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Update file offsets. + */ + endPosn = ftell(mZipFp); + + /* + * Success! Fill out new values. + */ + pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Add pEntry to the list. + */ + mEntries.add(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + + result = NO_ERROR; + +bail: + delete pEntry; + return result; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data. + */ +status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (1) { + count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); + if (ferror(srcFp) || ferror(dstFp)) + return errnoToStatus(errno); + if (count == 0) + break; + + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + LOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "dstFp" will be seeked immediately past the data. + */ +status_t ZipFile::copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + if (size > 0) { + *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); + if (fwrite(data, 1, size, dstFp) != size) { + LOGD("fwrite %d bytes failed\n", (int) size); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy some of the bytes in "src" to "dst". + * + * If "pCRC32" is NULL, the CRC will not be computed. + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data just written. + */ +status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + if (pCRC32 != NULL) + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (length) { + long readSize; + + readSize = sizeof(tmpBuf); + if (readSize > length) + readSize = length; + + count = fread(tmpBuf, 1, readSize, srcFp); + if ((long) count != readSize) { // error or unexpected EOF + LOGD("fread %d bytes failed\n", (int) readSize); + return UNKNOWN_ERROR; + } + + if (pCRC32 != NULL) + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + LOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + + length -= readSize; + } + + return NO_ERROR; +} + +/* + * Compress all of the data in "srcFp" and write it to "dstFp". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the compressed data. + */ +status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + status_t result = NO_ERROR; + const size_t kBufSize = 32768; + unsigned char* inBuf = NULL; + unsigned char* outBuf = NULL; + z_stream zstream; + bool atEof = false; // no feof() aviailable yet + unsigned long crc; + int zerr; + + /* + * Create an input buffer and an output buffer. + */ + inBuf = new unsigned char[kBufSize]; + outBuf = new unsigned char[kBufSize]; + if (inBuf == NULL || outBuf == NULL) { + result = NO_MEMORY; + goto bail; + } + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (zerr != Z_OK) { + result = UNKNOWN_ERROR; + if (zerr == Z_VERSION_ERROR) { + LOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + LOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + crc = crc32(0L, Z_NULL, 0); + + /* + * Loop while we have data. + */ + do { + size_t getSize; + int flush; + + /* only read if the input buffer is empty */ + if (zstream.avail_in == 0 && !atEof) { + LOGV("+++ reading %d bytes\n", (int)kBufSize); + if (data) { + getSize = size > kBufSize ? kBufSize : size; + memcpy(inBuf, data, getSize); + data = ((const char*)data) + getSize; + size -= getSize; + } else { + getSize = fread(inBuf, 1, kBufSize, srcFp); + if (ferror(srcFp)) { + LOGD("deflate read failed (errno=%d)\n", errno); + goto z_bail; + } + } + if (getSize < kBufSize) { + LOGV("+++ got %d bytes, EOF reached\n", + (int)getSize); + atEof = true; + } + + crc = crc32(crc, inBuf, getSize); + + zstream.next_in = inBuf; + zstream.avail_in = getSize; + } + + if (atEof) + flush = Z_FINISH; /* tell zlib that we're done */ + else + flush = Z_NO_FLUSH; /* more to come! */ + + zerr = deflate(&zstream, flush); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + LOGD("zlib deflate call failed (zerr=%d)\n", zerr); + result = UNKNOWN_ERROR; + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) + { + LOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); + if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != + (size_t)(zstream.next_out - outBuf)) + { + LOGD("write %d failed in deflate\n", + (int) (zstream.next_out - outBuf)); + goto z_bail; + } + + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + *pCRC32 = crc; + +z_bail: + deflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] inBuf; + delete[] outBuf; + + return result; +} + +/* + * Mark an entry as deleted. + * + * We will eventually need to crunch the file down, but if several files + * are being removed (perhaps as part of an "update" process) we can make + * things considerably faster by deferring the removal to "flush" time. + */ +status_t ZipFile::remove(ZipEntry* pEntry) +{ + /* + * Should verify that pEntry is actually part of this archive, and + * not some stray ZipEntry from a different file. + */ + + /* mark entry as deleted, and mark archive as dirty */ + pEntry->setDeleted(); + mNeedCDRewrite = true; + return NO_ERROR; +} + +/* + * Flush any pending writes. + * + * In particular, this will crunch out deleted entries, and write the + * Central Directory and EOCD if we have stomped on them. + */ +status_t ZipFile::flush(void) +{ + status_t result = NO_ERROR; + long eocdPosn; + int i, count; + + if (mReadOnly) + return INVALID_OPERATION; + if (!mNeedCDRewrite) + return NO_ERROR; + + assert(mZipFp != NULL); + + result = crunchArchive(); + if (result != NO_ERROR) + return result; + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + count = mEntries.size(); + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + pEntry->mCDE.write(mZipFp); + } + + eocdPosn = ftell(mZipFp); + mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; + + mEOCD.write(mZipFp); + + /* + * If we had some stuff bloat up during compression and get replaced + * with plain files, or if we deleted some entries, there's a lot + * of wasted space at the end of the file. Remove it now. + */ + if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { + LOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); + // not fatal + } + + /* should we clear the "newly added" flag in all entries now? */ + + mNeedCDRewrite = false; + return NO_ERROR; +} + +/* + * Crunch deleted files out of an archive by shifting the later files down. + * + * Because we're not using a temp file, we do the operation inside the + * current file. + */ +status_t ZipFile::crunchArchive(void) +{ + status_t result = NO_ERROR; + int i, count; + long delCount, adjust; + +#if 0 + printf("CONTENTS:\n"); + for (i = 0; i < (int) mEntries.size(); i++) { + printf(" %d: lfhOff=%ld del=%d\n", + i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); + } + printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); +#endif + + /* + * Roll through the set of files, shifting them as appropriate. We + * could probably get a slight performance improvement by sliding + * multiple files down at once (because we could use larger reads + * when operating on batches of small files), but it's not that useful. + */ + count = mEntries.size(); + delCount = adjust = 0; + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + long span; + + if (pEntry->getLFHOffset() != 0) { + long nextOffset; + + /* Get the length of this entry by finding the offset + * of the next entry. Directory entries don't have + * file offsets, so we need to find the next non-directory + * entry. + */ + nextOffset = 0; + for (int ii = i+1; nextOffset == 0 && ii < count; ii++) + nextOffset = mEntries[ii]->getLFHOffset(); + if (nextOffset == 0) + nextOffset = mEOCD.mCentralDirOffset; + span = nextOffset - pEntry->getLFHOffset(); + + assert(span >= ZipEntry::LocalFileHeader::kLFHLen); + } else { + /* This is a directory entry. It doesn't have + * any actual file contents, so there's no need to + * move anything. + */ + span = 0; + } + + //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", + // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); + + if (pEntry->getDeleted()) { + adjust += span; + delCount++; + + delete pEntry; + mEntries.removeAt(i); + + /* adjust loop control */ + count--; + i--; + } else if (span != 0 && adjust > 0) { + /* shuffle this entry back */ + //printf("+++ Shuffling '%s' back %ld\n", + // pEntry->getFileName(), adjust); + result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, + pEntry->getLFHOffset(), span); + if (result != NO_ERROR) { + /* this is why you use a temp file */ + LOGE("error during crunch - archive is toast\n"); + return result; + } + + pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); + } + } + + /* + * Fix EOCD info. We have to wait until the end to do some of this + * because we use mCentralDirOffset to determine "span" for the + * last entry. + */ + mEOCD.mCentralDirOffset -= adjust; + mEOCD.mNumEntries -= delCount; + mEOCD.mTotalNumEntries -= delCount; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + + assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); + assert(mEOCD.mNumEntries == count); + + return result; +} + +/* + * Works like memmove(), but on pieces of a file. + */ +status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) +{ + if (dst == src || n <= 0) + return NO_ERROR; + + unsigned char readBuf[32768]; + + if (dst < src) { + /* shift stuff toward start of file; must read from start */ + while (n != 0) { + size_t getSize = sizeof(readBuf); + if (getSize > n) + getSize = n; + + if (fseek(fp, (long) src, SEEK_SET) != 0) { + LOGD("filemove src seek %ld failed\n", (long) src); + return UNKNOWN_ERROR; + } + + if (fread(readBuf, 1, getSize, fp) != getSize) { + LOGD("filemove read %ld off=%ld failed\n", + (long) getSize, (long) src); + return UNKNOWN_ERROR; + } + + if (fseek(fp, (long) dst, SEEK_SET) != 0) { + LOGD("filemove dst seek %ld failed\n", (long) dst); + return UNKNOWN_ERROR; + } + + if (fwrite(readBuf, 1, getSize, fp) != getSize) { + LOGD("filemove write %ld off=%ld failed\n", + (long) getSize, (long) dst); + return UNKNOWN_ERROR; + } + + src += getSize; + dst += getSize; + n -= getSize; + } + } else { + /* shift stuff toward end of file; must read from end */ + assert(false); // write this someday, maybe + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Get the modification time from a file descriptor. + */ +time_t ZipFile::getModTime(int fd) +{ + struct stat sb; + + if (fstat(fd, &sb) < 0) { + LOGD("HEY: fstat on fd %d failed\n", fd); + return (time_t) -1; + } + + return sb.st_mtime; +} + + +#if 0 /* this is a bad idea */ +/* + * Get a copy of the Zip file descriptor. + * + * We don't allow this if the file was opened read-write because we tend + * to leave the file contents in an uncertain state between calls to + * flush(). The duplicated file descriptor should only be valid for reads. + */ +int ZipFile::getZipFd(void) const +{ + if (!mReadOnly) + return INVALID_OPERATION; + assert(mZipFp != NULL); + + int fd; + fd = dup(fileno(mZipFp)); + if (fd < 0) { + LOGD("didn't work, errno=%d\n", errno); + } + + return fd; +} +#endif + + +#if 0 +/* + * Expand data. + */ +bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const +{ + return false; +} +#endif + +// free the memory when you're done +void* ZipFile::uncompress(const ZipEntry* entry) +{ + size_t unlen = entry->getUncompressedLen(); + size_t clen = entry->getCompressedLen(); + + void* buf = malloc(unlen); + if (buf == NULL) { + return NULL; + } + + fseek(mZipFp, 0, SEEK_SET); + + off_t offset = entry->getFileOffset(); + if (fseek(mZipFp, offset, SEEK_SET) != 0) { + goto bail; + } + + switch (entry->getCompressionMethod()) + { + case ZipEntry::kCompressStored: { + ssize_t amt = fread(buf, 1, unlen, mZipFp); + if (amt != (ssize_t)unlen) { + goto bail; + } +#if 0 + printf("data...\n"); + const unsigned char* p = (unsigned char*)buf; + const unsigned char* end = p+unlen; + for (int i=0; i<32 && p < end; i++) { + printf("0x%08x ", (int)(offset+(i*0x10))); + for (int j=0; j<0x10 && p < end; j++) { + printf(" %02x", *p); + p++; + } + printf("\n"); + } +#endif + + } + break; + case ZipEntry::kCompressDeflated: { + if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { + goto bail; + } + } + break; + default: + goto bail; + } + return buf; + +bail: + free(buf); + return NULL; +} + + +/* + * =========================================================================== + * ZipFile::EndOfCentralDir + * =========================================================================== + */ + +/* + * Read the end-of-central-dir fields. + * + * "buf" should be positioned at the EOCD signature, and should contain + * the entire EOCD area including the comment. + */ +status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) +{ + /* don't allow re-use */ + assert(mComment == NULL); + + if (len < kEOCDLen) { + /* looks like ZIP file got truncated */ + LOGD(" Zip EOCD: expected >= %d bytes, found %d\n", + kEOCDLen, len); + return INVALID_OPERATION; + } + + /* this should probably be an assert() */ + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) + return UNKNOWN_ERROR; + + mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); + mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); + mNumEntries = ZipEntry::getShortLE(&buf[0x08]); + mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); + mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); + mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); + mCommentLen = ZipEntry::getShortLE(&buf[0x14]); + + // TODO: validate mCentralDirOffset + + if (mCommentLen > 0) { + if (kEOCDLen + mCommentLen > len) { + LOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", + kEOCDLen, mCommentLen, len); + return UNKNOWN_ERROR; + } + mComment = new unsigned char[mCommentLen]; + memcpy(mComment, buf + kEOCDLen, mCommentLen); + } + + return NO_ERROR; +} + +/* + * Write an end-of-central-directory section. + */ +status_t ZipFile::EndOfCentralDir::write(FILE* fp) +{ + unsigned char buf[kEOCDLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mDiskNumber); + ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); + ZipEntry::putShortLE(&buf[0x08], mNumEntries); + ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); + ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); + ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); + ZipEntry::putShortLE(&buf[0x14], mCommentLen); + + if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) + return UNKNOWN_ERROR; + if (mCommentLen > 0) { + assert(mComment != NULL); + if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of an EndOfCentralDir object. + */ +void ZipFile::EndOfCentralDir::dump(void) const +{ + LOGD(" EndOfCentralDir contents:\n"); + LOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", + mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); + LOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", + mCentralDirSize, mCentralDirOffset, mCommentLen); +} + diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h new file mode 100644 index 000000000000..dbbd072d1692 --- /dev/null +++ b/tools/aapt/ZipFile.h @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2006 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. + */ + +// +// General-purpose Zip archive access. This class allows both reading and +// writing to Zip archives, including deletion of existing entries. +// +#ifndef __LIBS_ZIPFILE_H +#define __LIBS_ZIPFILE_H + +#include +#include +#include + +#include "ZipEntry.h" + +namespace android { + +/* + * Manipulate a Zip archive. + * + * Some changes will not be visible in the until until "flush" is called. + * + * The correct way to update a file archive is to make all changes to a + * copy of the archive in a temporary file, and then unlink/rename over + * the original after everything completes. Because we're only interested + * in using this for packaging, we don't worry about such things. Crashing + * after making changes and before flush() completes could leave us with + * an unusable Zip archive. + */ +class ZipFile { +public: + ZipFile(void) + : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) + {} + ~ZipFile(void) { + if (!mReadOnly) + flush(); + if (mZipFp != NULL) + fclose(mZipFp); + discardEntries(); + } + + /* + * Open a new or existing archive. + */ + typedef enum { + kOpenReadOnly = 0x01, + kOpenReadWrite = 0x02, + kOpenCreate = 0x04, // create if it doesn't exist + kOpenTruncate = 0x08, // if it exists, empty it + }; + status_t open(const char* zipFileName, int flags); + + /* + * Add a file to the end of the archive. Specify whether you want the + * library to try to store it compressed. + * + * If "storageName" is specified, the archive will use that instead + * of "fileName". + * + * If there is already an entry with the same name, the call fails. + * Existing entries with the same name must be removed first. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const char* fileName, int compressionMethod, + ZipEntry** ppEntry) + { + return add(fileName, fileName, compressionMethod, ppEntry); + } + status_t add(const char* fileName, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add a file that is already compressed with gzip. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t addGzip(const char* fileName, const char* storageName, + ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressDeflated, + ZipEntry::kCompressDeflated, ppEntry); + } + + /* + * Add a file from an in-memory data buffer. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const void* data, size_t size, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(NULL, data, size, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry); + + /* + * Mark an entry as having been removed. It is not actually deleted + * from the archive or our internal data structures until flush() is + * called. + */ + status_t remove(ZipEntry* pEntry); + + /* + * Flush changes. If mNeedCDRewrite is set, this writes the central dir. + */ + status_t flush(void); + + /* + * Expand the data into the buffer provided. The buffer must hold + * at least bytes. Variation expands directly + * to a file. + * + * Returns "false" if an error was encountered in the compressed data. + */ + //bool uncompress(const ZipEntry* pEntry, void* buf) const; + //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; + void* uncompress(const ZipEntry* pEntry); + + /* + * Get an entry, by name. Returns NULL if not found. + * + * Does not return entries pending deletion. + */ + ZipEntry* getEntryByName(const char* fileName) const; + + /* + * Get the Nth entry in the archive. + * + * This will return an entry that is pending deletion. + */ + int getNumEntries(void) const { return mEntries.size(); } + ZipEntry* getEntryByIndex(int idx) const; + +private: + /* these are private and not defined */ + ZipFile(const ZipFile& src); + ZipFile& operator=(const ZipFile& src); + + class EndOfCentralDir { + public: + EndOfCentralDir(void) : + mDiskNumber(0), + mDiskWithCentralDir(0), + mNumEntries(0), + mTotalNumEntries(0), + mCentralDirSize(0), + mCentralDirOffset(0), + mCommentLen(0), + mComment(NULL) + {} + virtual ~EndOfCentralDir(void) { + delete[] mComment; + } + + status_t readBuf(const unsigned char* buf, int len); + status_t write(FILE* fp); + + //unsigned long mSignature; + unsigned short mDiskNumber; + unsigned short mDiskWithCentralDir; + unsigned short mNumEntries; + unsigned short mTotalNumEntries; + unsigned long mCentralDirSize; + unsigned long mCentralDirOffset; // offset from first disk + unsigned short mCommentLen; + unsigned char* mComment; + + enum { + kSignature = 0x06054b50, + kEOCDLen = 22, // EndOfCentralDir len, excl. comment + + kMaxCommentLen = 65535, // longest possible in ushort + kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, + + }; + + void dump(void) const; + }; + + + /* read all entries in the central dir */ + status_t readCentralDir(void); + + /* crunch deleted entries out */ + status_t crunchArchive(void); + + /* clean up mEntries */ + void discardEntries(void); + + /* common handler for all "add" functions */ + status_t addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry); + + /* copy all of "srcFp" into "dstFp" */ + status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); + /* copy all of "data" into "dstFp" */ + status_t copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32); + /* copy some of "srcFp" into "dstFp" */ + status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32); + /* like memmove(), but on parts of a single file */ + status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); + /* compress all of "srcFp" into "dstFp", using Deflate */ + status_t compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32); + + /* get modification date from a file descriptor */ + time_t getModTime(int fd); + + /* + * We use stdio FILE*, which gives us buffering but makes dealing + * with files >2GB awkward. Until we support Zip64, we're fine. + */ + FILE* mZipFp; // Zip file pointer + + /* one of these per file */ + EndOfCentralDir mEOCD; + + /* did we open this read-only? */ + bool mReadOnly; + + /* set this when we trash the central dir */ + bool mNeedCDRewrite; + + /* + * One ZipEntry per entry in the zip file. I'm using pointers instead + * of objects because it's easier than making operator= work for the + * classes and sub-classes. + */ + Vector mEntries; +}; + +}; // namespace android + +#endif // __LIBS_ZIPFILE_H -- cgit v1.2.3-59-g8ed1b From e583a4ea8c90105eee9b408d39bca3a4af6a2569 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 5 Jun 2009 15:11:23 -0700 Subject: get rid of LogSocket which wasn't even implemented (enabled) --- include/utils/LogSocket.h | 20 ------- libs/utils/Android.mk | 3 +- libs/utils/LogSocket.cpp | 129 ---------------------------------------------- 3 files changed, 1 insertion(+), 151 deletions(-) delete mode 100644 include/utils/LogSocket.h delete mode 100644 libs/utils/LogSocket.cpp diff --git a/include/utils/LogSocket.h b/include/utils/LogSocket.h deleted file mode 100644 index 01fbfb50e33a..000000000000 --- a/include/utils/LogSocket.h +++ /dev/null @@ -1,20 +0,0 @@ -/* utils/LogSocket.h -** -** Copyright 2008, The Android Open Source Project -** -** This file is dual licensed. It may be redistributed and/or modified -** under the terms of the Apache 2.0 License OR version 2 of the GNU -** General Public License. -*/ - -#ifndef _UTILS_LOGSOCKET_H -#define _UTILS_LOGSOCKET_H - -#define SOCKET_CLOSE_LOCAL 0 - -void add_send_stats(int fd, int send); -void add_recv_stats(int fd, int recv); -void log_socket_close(int fd, short reason); -void log_socket_connect(int fd, unsigned int ip, unsigned short port); - -#endif /* _UTILS_LOGSOCKET_H */ diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index 70d440797305..3f5cb85ac841 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -41,8 +41,7 @@ commonSources:= \ ZipFileCRO.cpp \ ZipFileRO.cpp \ ZipUtils.cpp \ - misc.cpp \ - LogSocket.cpp + misc.cpp # For the host diff --git a/libs/utils/LogSocket.cpp b/libs/utils/LogSocket.cpp deleted file mode 100644 index 55c1b99aff96..000000000000 --- a/libs/utils/LogSocket.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef HAVE_WINSOCK -//#define SOCKETLOG -#endif - -#ifdef SOCKETLOG - -#define LOG_TAG "SOCKETLOG" - -#include -#include -#include "utils/LogSocket.h" -#include "utils/logger.h" -#include "cutils/hashmap.h" - -// defined in //device/data/etc/event-log-tags -#define SOCKET_CLOSE_LOG 51000 - -static Hashmap* statsMap = NULL; - -#define LOG_LIST_NUMBER 5 - -typedef struct SocketStats { - int fd; - unsigned int send; - unsigned int recv; - unsigned int ip; - unsigned short port; - short reason; -}SocketStats; - -SocketStats *get_socket_stats(int fd) { - if (statsMap == NULL) { - statsMap = hashmapCreate(8, &hashmapIntHash, &hashmapIntEquals); - } - - SocketStats *s = (SocketStats*) hashmapGet(statsMap, &fd); - if (s == NULL) { - // LOGD("create SocketStats for fd %d", fd); - s = (SocketStats*) malloc(sizeof(SocketStats)); - memset(s, 0, sizeof(SocketStats)); - s->fd = fd; - hashmapPut(statsMap, &s->fd, s); - } - return s; -} - -void log_socket_connect(int fd, unsigned int ip, unsigned short port) { - // LOGD("log_socket_connect for fd %d ip %d port%d", fd, ip, port); - SocketStats *s = get_socket_stats(fd); - s->ip = ip; - s->port = port; -} - -void add_send_stats(int fd, int send) { - if (send <=0) { - LOGE("add_send_stats send %d", send); - return; - } - SocketStats *s = get_socket_stats(fd); - s->send += send; - // LOGD("add_send_stats for fd %d ip %d port%d", fd, s->ip, s->port); -} - -void add_recv_stats(int fd, int recv) { - if (recv <=0) { - LOGE("add_recv_stats recv %d", recv); - return; - } - SocketStats *s = get_socket_stats(fd); - s->recv += recv; - // LOGD("add_recv_stats for fd %d ip %d port%d", fd, s->ip, s->port); -} - -char* put_int(char* buf, int value) { - *buf = EVENT_TYPE_INT; - buf++; - memcpy(buf, &value, sizeof(int)); - return buf + sizeof(int); -} - -void log_socket_close(int fd, short reason) { - if (statsMap) { - SocketStats *s = (SocketStats*) hashmapGet(statsMap, &fd); - if (s != NULL) { - if (s->send != 0 || s->recv != 0) { - s->reason = reason; - // 5 int + list type need 2 bytes - char buf[LOG_LIST_NUMBER * 5 + 2]; - buf[0] = EVENT_TYPE_LIST; - buf[1] = LOG_LIST_NUMBER; - char* writePos = buf + 2; - writePos = put_int(writePos, s->send); - writePos = put_int(writePos, s->recv); - writePos = put_int(writePos, s->ip); - writePos = put_int(writePos, s->port); - writePos = put_int(writePos, s->reason); - - android_bWriteLog(SOCKET_CLOSE_LOG, buf, sizeof(buf)); - // LOGD("send %d recv %d reason %d", s->send, s->recv, s->reason); - } - hashmapRemove(statsMap, &s->fd); - free(s); - } - } -} - -#else -void add_send_stats(int fd, int send) {} -void add_recv_stats(int fd, int recv) {} -void log_socket_close(int fd, short reason) {} -void log_socket_connect(int fd, unsigned int ip, unsigned short port) {} -#endif -- cgit v1.2.3-59-g8ed1b