diff options
235 files changed, 7447 insertions, 4960 deletions
@@ -190,7 +190,6 @@ LOCAL_SRC_FILES += \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ telephony/java/com/android/internal/telephony/IExtendedNetworkService.aidl \ - vpn/java/android/net/vpn/IVpnService.aidl \ voip/java/android/net/sip/ISipSession.aidl \ voip/java/android/net/sip/ISipSessionListener.aidl \ voip/java/android/net/sip/ISipService.aidl @@ -280,7 +279,6 @@ aidl_files := \ frameworks/base/telephony/java/android/telephony/ServiceState.aidl \ frameworks/base/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl \ - frameworks/base/vpn/java/android/net/vpn/IVpnService.aidl \ gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl $(gen): PRIVATE_SRC_FILES := $(aidl_files) diff --git a/CleanSpec.mk b/CleanSpec.mk index 8d34636..f3eaeeb 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -100,6 +100,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libstagefright_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os) $(call add-clean-step, rm -rf $(OUT_DIR)target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/keystore/java/android/security/IKeyChainAliasResponse.java) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/vpn) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ diff --git a/api/current.txt b/api/current.txt index f21a4f3..d009a64 100644 --- a/api/current.txt +++ b/api/current.txt @@ -208,6 +208,7 @@ package android { field public static final int actionModePasteDrawable = 16843539; // 0x1010313 field public static final int actionModeSelectAllDrawable = 16843648; // 0x1010380 field public static final int actionOverflowButtonStyle = 16843510; // 0x10102f6 + field public static final int actionProviderClass = 16843678; // 0x101039e field public static final int actionViewClass = 16843516; // 0x10102fc field public static final int activatedBackgroundIndicator = 16843517; // 0x10102fd field public static final int activityCloseEnterAnimation = 16842938; // 0x10100ba @@ -596,10 +597,10 @@ package android { field public static final int layout_centerInParent = 16843151; // 0x101018f field public static final int layout_centerVertical = 16843153; // 0x1010191 field public static final int layout_column = 16843084; // 0x101014c - field public static final int layout_columnSpan = 16843646; // 0x101037e - field public static final int layout_columnWeight = 16843647; // 0x101037f + field public static final int layout_columnSpan = 16843645; // 0x101037d field public static final int layout_gravity = 16842931; // 0x10100b3 field public static final int layout_height = 16842997; // 0x10100f5 + field public static final int layout_heightSpec = 16843647; // 0x101037f field public static final int layout_margin = 16842998; // 0x10100f6 field public static final int layout_marginBottom = 16843002; // 0x10100fa field public static final int layout_marginEnd = 16843675; // 0x101039b @@ -609,13 +610,13 @@ package android { field public static final int layout_marginTop = 16843000; // 0x10100f8 field public static final int layout_row = 16843643; // 0x101037b field public static final int layout_rowSpan = 16843644; // 0x101037c - field public static final int layout_rowWeight = 16843645; // 0x101037d field public static final int layout_scale = 16843155; // 0x1010193 field public static final int layout_span = 16843085; // 0x101014d field public static final int layout_toLeftOf = 16843138; // 0x1010182 field public static final int layout_toRightOf = 16843139; // 0x1010183 field public static final int layout_weight = 16843137; // 0x1010181 field public static final int layout_width = 16842996; // 0x10100f4 + field public static final int layout_widthSpec = 16843646; // 0x101037e field public static final int layout_x = 16843135; // 0x101017f field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad @@ -3371,6 +3372,7 @@ package android.app { method public void send(android.content.Context, int, android.content.Intent) throws android.app.PendingIntent.CanceledException; method public void send(int, android.app.PendingIntent.OnFinished, android.os.Handler) throws android.app.PendingIntent.CanceledException; method public void send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler) throws android.app.PendingIntent.CanceledException; + method public void send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler, java.lang.String) throws android.app.PendingIntent.CanceledException; method public static void writePendingIntentOrNullToParcel(android.app.PendingIntent, android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -3452,6 +3454,7 @@ package android.app { field public static final java.lang.String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data"; field public static final java.lang.String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id"; field public static final java.lang.String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data"; + field public static final java.lang.String SUGGEST_COLUMN_LAST_ACCESS_HINT = "suggest_last_access_hint"; field public static final java.lang.String SUGGEST_COLUMN_QUERY = "suggest_intent_query"; field public static final java.lang.String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id"; field public static final java.lang.String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING = "suggest_spinner_while_refreshing"; @@ -5297,6 +5300,7 @@ package android.content { method public java.lang.String getTargetPackage(); method public static android.content.IntentSender readIntentSenderOrNullFromParcel(android.os.Parcel); method public void sendIntent(android.content.Context, int, android.content.Intent, android.content.IntentSender.OnFinished, android.os.Handler) throws android.content.IntentSender.SendIntentException; + method public void sendIntent(android.content.Context, int, android.content.Intent, android.content.IntentSender.OnFinished, android.os.Handler, java.lang.String) throws android.content.IntentSender.SendIntentException; method public static void writeIntentSenderOrNullToParcel(android.content.IntentSender, android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -20565,6 +20569,12 @@ package android.view { method public abstract boolean onPrepareActionMode(android.view.ActionMode, android.view.Menu); } + public abstract class ActionProvider { + ctor public ActionProvider(android.content.Context); + method public abstract android.view.View onCreateActionView(); + method public void onPerformDefaultAction(android.view.View); + } + public abstract interface ContextMenu implements android.view.Menu { method public abstract void clearHeader(); method public abstract android.view.ContextMenu setHeaderIcon(int); @@ -21233,6 +21243,7 @@ package android.view { public abstract interface MenuItem { method public abstract boolean collapseActionView(); method public abstract boolean expandActionView(); + method public abstract android.view.ActionProvider getActionProvider(); method public abstract android.view.View getActionView(); method public abstract char getAlphabeticShortcut(); method public abstract int getGroupId(); @@ -21251,6 +21262,7 @@ package android.view { method public abstract boolean isChecked(); method public abstract boolean isEnabled(); method public abstract boolean isVisible(); + method public abstract android.view.MenuItem setActionProvider(android.view.ActionProvider); method public abstract android.view.MenuItem setActionView(android.view.View); method public abstract android.view.MenuItem setActionView(int); method public abstract android.view.MenuItem setAlphabeticShortcut(char); @@ -22072,6 +22084,7 @@ package android.view { method public boolean willNotDraw(); field public static android.util.Property ALPHA; field protected static int DEFAULT_TEXT_DIRECTION; + field protected static float DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD; field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0 field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000 field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000 @@ -22261,6 +22274,7 @@ package android.view { method public static deprecated int getTouchSlop(); method public static deprecated int getWindowTouchSlop(); method public static long getZoomControlsTimeout(); + method public boolean hasPermanentMenuKey(); } public class ViewDebug { @@ -22395,6 +22409,7 @@ package android.view { method public void requestDisallowInterceptTouchEvent(boolean); method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent); method public void requestTransparentRegion(android.view.View); + method protected void resetLayoutDirectionResolution(); method public void scheduleLayoutAnimation(); method public void setAddStatesFromChildren(boolean); method public void setAlwaysDrawnWithCacheEnabled(boolean); @@ -24887,9 +24902,9 @@ package android.widget { } public class GridLayout extends android.view.ViewGroup { - ctor public GridLayout(android.content.Context); ctor public GridLayout(android.content.Context, android.util.AttributeSet, int); ctor public GridLayout(android.content.Context, android.util.AttributeSet); + ctor public GridLayout(android.content.Context); method public int getAlignmentMode(); method public int getColumnCount(); method public int getOrientation(); @@ -24909,8 +24924,11 @@ package android.widget { field public static final int ALIGN_MARGINS = 1; // 0x1 field public static final android.widget.GridLayout.Alignment BASELINE; field public static final android.widget.GridLayout.Alignment BOTTOM; + field public static final android.widget.GridLayout.Spec CAN_SHRINK; + field public static final android.widget.GridLayout.Spec CAN_STRETCH; field public static final android.widget.GridLayout.Alignment CENTER; field public static final android.widget.GridLayout.Alignment FILL; + field public static final android.widget.GridLayout.Spec FIXED; field public static final int HORIZONTAL = 0; // 0x0 field public static final android.widget.GridLayout.Alignment LEFT; field public static final android.widget.GridLayout.Alignment RIGHT; @@ -24937,9 +24955,13 @@ package android.widget { ctor public GridLayout.LayoutParams(android.content.Context, android.util.AttributeSet); method public void setGravity(int); field public android.widget.GridLayout.Group columnGroup; - field public float columnWeight; + field public android.widget.GridLayout.Spec heightSpec; field public android.widget.GridLayout.Group rowGroup; - field public float rowWeight; + field public android.widget.GridLayout.Spec widthSpec; + } + + public static abstract class GridLayout.Spec { + ctor public GridLayout.Spec(); } public class GridView extends android.widget.AbsListView { @@ -25726,6 +25748,14 @@ package android.widget { method public abstract void onStopTrackingTouch(android.widget.SeekBar); } + public class ShareActionProvider extends android.view.ActionProvider { + ctor public ShareActionProvider(android.content.Context); + method public android.view.View onCreateActionView(); + method public void setShareHistoryFileName(java.lang.String); + method public void setShareIntent(android.view.View, android.content.Intent); + field public static final java.lang.String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml"; + } + public class SimpleAdapter extends android.widget.BaseAdapter implements android.widget.Filterable { ctor public SimpleAdapter(android.content.Context, java.util.List<? extends java.util.Map<java.lang.String, ?>>, int, java.lang.String[], int[]); method public int getCount(); @@ -26063,6 +26093,7 @@ package android.widget { method protected void onTextChanged(java.lang.CharSequence, int, int, int); method public boolean onTextContextMenuItem(int); method public void removeTextChangedListener(android.text.TextWatcher); + method protected void resetLayoutDirectionResolution(); method public final void setAutoLinkMask(int); method public void setCompoundDrawablePadding(int); method public void setCompoundDrawables(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); diff --git a/cmds/ip-up-vpn/Android.mk b/cmds/ip-up-vpn/Android.mk new file mode 100644 index 0000000..de81889 --- /dev/null +++ b/cmds/ip-up-vpn/Android.mk @@ -0,0 +1,26 @@ +# +# Copyright (C) 2011 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := ip-up-vpn.c +LOCAL_SHARED_LIBRARIES := libcutils +LOCAL_MODULE := ip-up-vpn +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/ppp +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/cmds/ip-up-vpn/ip-up-vpn.c b/cmds/ip-up-vpn/ip-up-vpn.c new file mode 100644 index 0000000..bbf6b14 --- /dev/null +++ b/cmds/ip-up-vpn/ip-up-vpn.c @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <cutils/properties.h> + +int main(int argc, char **argv) +{ + if (argc > 1 && strlen(argv[1]) > 0) { + char dns[PROPERTY_VALUE_MAX]; + char *dns1 = getenv("DNS1"); + char *dns2 = getenv("DNS2"); + + snprintf(dns, sizeof(dns), "%s %s", dns1 ? dns1 : "", dns2 ? dns2 : ""); + property_set("vpn.dns", dns); + property_set("vpn.via", argv[1]); + } + return 0; +} diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp index 1c1f37a..4b4b9b9 100644 --- a/cmds/keystore/keystore.cpp +++ b/cmds/keystore/keystore.cpp @@ -712,7 +712,6 @@ static struct user { {AID_VPN, AID_SYSTEM, GET}, {AID_WIFI, AID_SYSTEM, GET}, {AID_ROOT, AID_SYSTEM, GET}, - {AID_KEYCHAIN, AID_SYSTEM, TEST | GET | SAW}, {~0, ~0, TEST | GET | INSERT | DELETE | EXIST | SAW}, }; diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 164acbc..68c9926 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -31,82 +31,151 @@ import android.view.accessibility.AccessibilityNodeInfo; * An accessibility service runs in the background and receives callbacks by the system * when {@link AccessibilityEvent}s are fired. Such events denote some state transition * in the user interface, for example, the focus has changed, a button has been clicked, - * etc. + * etc. Such a service can optionally request the capability for querying the content + * of the active window. Development of an accessibility service requires extends this + * class and implements its abstract methods. * <p> - * An accessibility service extends this class and implements its abstract methods. Such - * a service is declared as any other service in an AndroidManifest.xml but it must also - * specify that it handles the "android.accessibilityservice.AccessibilityService" - * {@link android.content.Intent}. Following is an example of such a declaration: - * <p> - * <code> - * <service android:name=".MyAccessibilityService"><br> - * <intent-filter><br> - * <action android:name="android.accessibilityservice.AccessibilityService" /><br> - * </intent-filter><br> - * </service><br> - * </code> + * <strong>Lifecycle</strong> * </p> * <p> - * The lifecycle of an accessibility service is managed exclusively by the system. Starting - * or stopping an accessibility service is triggered by an explicit user action through + * The lifecycle of an accessibility service is managed exclusively by the system and + * follows the established service life cycle. Additionally, starting or stopping an + * accessibility service is triggered exclusively by an explicit user action through * enabling or disabling it in the device settings. After the system binds to a service it * calls {@link AccessibilityService#onServiceConnected()}. This method can be * overriden by clients that want to perform post binding setup. * </p> * <p> + * <strong>Declaration</strong> + * </p> + * <p> + * An accessibility is declared as any other service in an AndroidManifest.xml but it + * must also specify that it handles the "android.accessibilityservice.AccessibilityService" + * {@link android.content.Intent}. Failure to declare this intent will cause the system to + * ignore the accessibility service. Following is an example declaration: + * </p> + * <p> + * <code> + * <pre> + * <service android:name=".MyAccessibilityService"> + * <intent-filter> + * <action android:name="android.accessibilityservice.AccessibilityService" /> + * </intent-filter> + * . . . + * </service> + * </pre> + * </code> + * </p> + * <p> + * <strong>Configuration</strong> + * </p> + * <p> * An accessibility service can be configured to receive specific types of accessibility events, * listen only to specific packages, get events from each type only once in a given time frame, * retrieve window content, specify a settings activity, etc. * </p> + * <p> * There are two approaches for configuring an accessibility service: + * </p> * <ul> - * <li> - * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring - * the service. A service declaration with a meta-data tag is presented below: - * <p> - * <code> - * <service android:name=".MyAccessibilityService"><br> - * <intent-filter><br> - * <action android:name="android.accessibilityservice.AccessibilityService" /><br> - * </intent-filter><br> - * <meta-data android:name="android.accessibilityservice.as" android:resource="@xml/accessibilityservice" /><br> - * </service><br> - * </code> - * </p> - * <p> - * <strong> - * This approach enables setting all accessibility service properties. - * </strong> - * </p> - * <p> - * For more details refer to {@link #SERVICE_META_DATA}. - * </p> - * </li> - * <li> - * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note - * that this method can be called any time to change the service configuration.<br> - * <p> - * <strong> - * This approach enables setting only dynamically configurable accessibility - * service properties: - * {@link AccessibilityServiceInfo#eventTypes}, - * {@link AccessibilityServiceInfo#feedbackType}, - * {@link AccessibilityServiceInfo#flags}, - * {@link AccessibilityServiceInfo#notificationTimeout}, - * {@link AccessibilityServiceInfo#packageNames} - * </strong> - * </p> - * <p> - * For more details refer to {@link AccessibilityServiceInfo}. - * </p> - * </li> + * <li> + * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring + * the service. A service declaration with a meta-data tag is presented below: + * <p> + * <code> + * <pre> + * <service android:name=".MyAccessibilityService"> + * <intent-filter> + * <action android:name="android.accessibilityservice.AccessibilityService" /> + * </intent-filter> + * <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" /> + * </service> + * </pre> + * </code> + * </p> + * <p> + * <strong>Note:</strong>This approach enables setting all properties. + * </p> + * <p> + * For more details refer to {@link #SERVICE_META_DATA} and + * <code><{@link android.R.styleable#AccessibilityService accessibility-service}></code>.. + * </p> + * </li> + * <li> + * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note + * that this method can be called any time to dynamically change the service configuration. + * <p> + * <strong>Note:</strong> This approach enables setting only dynamically configurable properties: + * {@link AccessibilityServiceInfo#eventTypes}, + * {@link AccessibilityServiceInfo#feedbackType}, + * {@link AccessibilityServiceInfo#flags}, + * {@link AccessibilityServiceInfo#notificationTimeout}, + * {@link AccessibilityServiceInfo#packageNames} + * </p> + * <p> + * For more details refer to {@link AccessibilityServiceInfo}. + * </p> + * </li> * </ul> * <p> - * An accessibility service can be registered for events in specific packages to provide a - * specific type of feedback and is notified with a certain timeout after the last event - * of interest has been fired. + * <strong>Retrieving window content</strong> + * </p> + * <p> + * An service can specify in its declaration that it can retrieve the active window + * content which is represented as a tree of {@link AccessibilityNodeInfo}. Note that + * declaring this capability requires that the service declares its configuration via + * an XML resource referenced by {@link #SERVICE_META_DATA}. + * </p> + * <p> + * For security purposes an accessibility service can retrieve only the content of the + * currently active window. The currently active window is defined as the window from + * which was fired the last event of the following types: + * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}, + * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}, + * {@link AccessibilityEvent#TYPE_VIEW_CLICKED}, + * {@link AccessibilityEvent#TYPE_VIEW_FOCUSED}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}, + * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}, + * {@link AccessibilityEvent#TYPE_VIEW_SELECTED}, + * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}, + * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}, + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED}, + * {@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}, + * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}. + * In other words, the active window is the one where the user interaction is taking place. + * </p> + * <p> + * The entry point for retrieving window content is through calling + * {@link AccessibilityEvent#getSource() AccessibilityEvent.getSource()} of the last received + * event of the above types or a previous event from the same window + * (see {@link AccessibilityEvent#getWindowId() AccessibilityEvent.getWindowId()}). Invoking + * this method will return an {@link AccessibilityNodeInfo} that can be used to traverse the + * window content which represented as a tree of such objects. + * </p> + * <p> + * <strong>Note</strong>An accessibility service may have requested to be notified for + * a subset of the event types, thus be unaware that the active window has changed. Therefore + * accessibility service that would like to retrieve window content should: + * <ul> + * <li> + * Register for all event types with no notification timeout and keep track for the active + * window by calling {@link AccessibilityEvent#getWindowId()} of the last received event and + * compare this with the {@link AccessibilityNodeInfo#getWindowId()} before calling retrieval + * methods on the latter. + * </li> + * <li> + * Prepare that a retrieval method on {@link AccessibilityNodeInfo} may fail since the + * active window has changed and the service did not get the accessibility event yet. Note + * that it is possible to have a retrieval method failing event adopting the strategy + * specified in the previous bullet because the accessibility event dispatch is asynchronous + * and crosses process boundaries. + * </li> + * </ul> + * </p> * <p> * <b>Notification strategy</b> + * </p> * <p> * For each feedback type only one accessibility service is notified. Services are notified * in the order of registration. Hence, if two services are registered for the same @@ -117,9 +186,10 @@ import android.view.accessibility.AccessibilityNodeInfo; * registration order. This enables "generic" accessibility services that work reasonably * well with most applications to coexist with "polished" ones that are targeted for * specific applications. + * </p> * <p> * <b>Event types</b> - * <p> + * </p> * {@link AccessibilityEvent#TYPE_VIEW_CLICKED} * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED} * {@link AccessibilityEvent#TYPE_VIEW_FOCUSED} @@ -127,9 +197,16 @@ import android.view.accessibility.AccessibilityNodeInfo; * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED} * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} * {@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED} - * <p> - * <b>Feedback types</b> - * <p> + * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START} + * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END} + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER} + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT} + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} + * {@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED} + * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} + * <p> + * <b>Feedback types</b> + * <p> * {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE} * {@link AccessibilityServiceInfo#FEEDBACK_HAPTIC} * {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE} @@ -140,10 +217,10 @@ import android.view.accessibility.AccessibilityNodeInfo; * @see AccessibilityServiceInfo * @see android.view.accessibility.AccessibilityManager * - * Note: The event notification timeout is useful to avoid propagating events to the client - * too frequently since this is accomplished via an expensive interprocess call. - * One can think of the timeout as a criteria to determine when event generation has - * settled down. + * <strong>Note:</strong> The event notification timeout is useful to avoid propagating + * events to the client too frequently since this is accomplished via an expensive + * interprocess call. One can think of the timeout as a criteria to determine when + * event generation has settled down. */ public abstract class AccessibilityService extends Service { /** @@ -154,57 +231,25 @@ public abstract class AccessibilityService extends Service { /** * Name under which an AccessibilityService component publishes information - * about itself. This meta-data must reference an XML resource containing - * an + * about itself. This meta-data must reference an XML resource containing an * <code><{@link android.R.styleable#AccessibilityService accessibility-service}></code> * tag. This is a a sample XML file configuring an accessibility service: * <p> * <code> - * <?xml version="1.0" encoding="utf-8"?><br> - * <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"<br> - * android:accessibilityEventTypes="typeViewClicked|typeViewFocused"<br> - * android:packageNames="foo.bar, foo.baz"<br> - * android:accessibilityFeedbackType="feedbackSpoken"<br> - * android:notificationTimeout="100"<br> - * android:accessibilityFlags="flagDefault"<br> - * android:settingsActivity="foo.bar.TestBackActivity"<br> - * . . .<br> + * <pre> + * <accessibility-service + * android:accessibilityEventTypes="typeViewClicked|typeViewFocused" + * android:packageNames="foo.bar, foo.baz" + * android:accessibilityFeedbackType="feedbackSpoken" + * android:notificationTimeout="100" + * android:accessibilityFlags="flagDefault" + * android:settingsActivity="foo.bar.TestBackActivity" + * android:canRetrieveWindowContent="true" + * . . . * /> + * </pre> * </code> * </p> - * <p> - * <strong>Note:</strong> A service can retrieve only the content of the active window. - * An active window is the source of the most recent event of type - * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}, - * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}, - * {@link AccessibilityEvent#TYPE_VIEW_CLICKED}, - * {@link AccessibilityEvent#TYPE_VIEW_FOCUSED}, - * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}, - * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}, - * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}, - * {@link AccessibilityEvent#TYPE_VIEW_SELECTED}, - * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}, - * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}. - * Therefore the service should: - * <ul> - * <li> - * Register for all event types with no notification timeout and keep track - * for the active window by calling - * {@link AccessibilityEvent#getWindowId()} of the last received - * event and compare this with the - * {@link AccessibilityNodeInfo#getWindowId()} before calling - * retrieval methods on the latter. - * </li> - * <li> - * Prepare that a retrieval method on {@link AccessibilityNodeInfo} may fail - * since the active window has changed and the service did not get the - * accessibility event. Note that it is possible to have a retrieval method - * failing event adopting the strategy specified in the previous bullet - * because the accessibility event dispatch is asynchronous and crosses - * process boundaries. - * </li> - * <ul> - * </p> */ public static final String SERVICE_META_DATA = "android.accessibilityservice"; diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index b9878cd..ef4adca 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -37,13 +37,13 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; /** - * This class describes an {@link AccessibilityService}. The system - * notifies an {@link AccessibilityService} for - * {@link android.view.accessibility.AccessibilityEvent}s + * This class describes an {@link AccessibilityService}. The system notifies an + * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s * according to the information encapsulated in this class. * * @see AccessibilityService * @see android.view.accessibility.AccessibilityEvent + * @see android.view.accessibility.AccessibilityManager */ public class AccessibilityServiceInfo implements Parcelable { @@ -93,12 +93,19 @@ public class AccessibilityServiceInfo implements Parcelable { * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED + * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START + * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END + * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER + * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT + * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED + * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED + * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED */ public int eventTypes; /** * The package names an {@link AccessibilityService} is interested in. Setting - * to null is equivalent to all packages. + * to <code>null</code> is equivalent to all packages. * <p> * <strong>Can be dynamically set at runtime.</strong> * </p> @@ -125,10 +132,10 @@ public class AccessibilityServiceInfo implements Parcelable { * <strong>Can be dynamically set at runtime.</strong>. * </p> * <p> - * Note: The event notification timeout is useful to avoid propagating events to the client - * too frequently since this is accomplished via an expensive interprocess call. - * One can think of the timeout as a criteria to determine when event generation has - * settled down + * <strong>Note:</strong> The event notification timeout is useful to avoid propagating + * events to the client too frequently since this is accomplished via an expensive + * interprocess call. One can think of the timeout as a criteria to determine when + * event generation has settled down. */ public long notificationTimeout; @@ -159,7 +166,7 @@ public class AccessibilityServiceInfo implements Parcelable { private String mSettingsActivityName; /** - * Flag whether this accessibility service can retrieve screen content. + * Flag whether this accessibility service can retrieve window content. */ private boolean mCanRetrieveWindowContent; @@ -296,12 +303,12 @@ public class AccessibilityServiceInfo implements Parcelable { } /** - * Whether this service can retrieve the currently focused window content. + * Whether this service can retrieve the current window's content. * <p> * <strong>Statically set from * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> * </p> - * @return True screen content is retrieved. + * @return True window content can be retrieved. */ public boolean getCanRetrieveWindowContent() { return mCanRetrieveWindowContent; diff --git a/core/java/android/accessibilityservice/package.html b/core/java/android/accessibilityservice/package.html new file mode 100644 index 0000000..0c640d1 --- /dev/null +++ b/core/java/android/accessibilityservice/package.html @@ -0,0 +1,22 @@ +<html> +<body> +<p> + The classes in this package are used for development of accessibility service that + provide alternative or augmented feedback to the user. +</p> +<p> + An {@link android.accessibilityservice.AccessibilityService} runs in the background and + receives callbacks by the system when {@link android.view.accessibility.AccessibilityEvent}s + are fired. Such events denote some state transition in the user interface, for example, the + focus has changed, a button has been clicked, etc. Such a service can optionally request the + capability for querying the content of the active window. Development of an accessibility + service requires extends this class and implements its abstract methods. +</p> +<p> + An {@link android.accessibilityservice.AccessibilityServiceInfo} describes an + {@link android.accessibilityservice.AccessibilityService}. The system notifies an + AccessibilityService for {@link android.view.accessibility.AccessibilityEvent}s + according to the information encapsulated in this class. +</p> +</body> +</html> diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 85f40c9..fdf4a3a 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -251,12 +251,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IBinder b = data.readStrongBinder(); IApplicationThread app = b != null ? ApplicationThreadNative.asInterface(b) : null; + String packageName = data.readString(); b = data.readStrongBinder(); IIntentReceiver rec = b != null ? IIntentReceiver.Stub.asInterface(b) : null; IntentFilter filter = IntentFilter.CREATOR.createFromParcel(data); String perm = data.readString(); - Intent intent = registerReceiver(app, rec, filter, perm); + Intent intent = registerReceiver(app, packageName, rec, filter, perm); reply.writeNoException(); if (intent != null) { reply.writeInt(1); @@ -1503,6 +1504,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case IS_INTENT_SENDER_TARGETED_TO_PACKAGE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IIntentSender r = IIntentSender.Stub.asInterface( + data.readStrongBinder()); + boolean res = isIntentSenderTargetedToPackage(r); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + } return super.onTransact(code, data, reply, flags); @@ -1702,7 +1713,7 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } - public Intent registerReceiver(IApplicationThread caller, + public Intent registerReceiver(IApplicationThread caller, String packageName, IIntentReceiver receiver, IntentFilter filter, String perm) throws RemoteException { @@ -1710,6 +1721,7 @@ class ActivityManagerProxy implements IActivityManager Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeString(packageName); data.writeStrongBinder(receiver != null ? receiver.asBinder() : null); filter.writeToParcel(data, 0); data.writeString(perm); @@ -3385,5 +3397,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public boolean isIntentSenderTargetedToPackage(IIntentSender sender) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(sender.asBinder()); + mRemote.transact(IS_INTENT_SENDER_TARGETED_TO_PACKAGE_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 94a4afa..8749d3e 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -61,7 +61,6 @@ import android.net.wifi.IWifiManager; import android.net.wifi.WifiManager; import android.nfc.NfcManager; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.DropBoxManager; import android.os.Environment; @@ -81,7 +80,6 @@ import android.content.ClipboardManager; import android.util.AndroidRuntimeException; import android.util.Log; import android.view.ContextThemeWrapper; -import android.view.Display; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; @@ -142,6 +140,7 @@ class ContextImpl extends Context { new HashMap<String, SharedPreferencesImpl>(); /*package*/ LoadedApk mPackageInfo; + private String mBasePackageName; private Resources mResources; /*package*/ ActivityThread mMainThread; private Context mOuterContext; @@ -1030,7 +1029,7 @@ class ContextImpl extends Context { } try { return ActivityManagerNative.getDefault().registerReceiver( - mMainThread.getApplicationThread(), + mMainThread.getApplicationThread(), mBasePackageName, rd, filter, broadcastPermission); } catch (RemoteException e) { return null; @@ -1397,7 +1396,7 @@ class ContextImpl extends Context { if (pi != null) { ContextImpl c = new ContextImpl(); c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED; - c.init(pi, null, mMainThread, mResources); + c.init(pi, null, mMainThread, mResources, mBasePackageName); if (c.mResources != null) { return c; } @@ -1450,6 +1449,7 @@ class ContextImpl extends Context { */ public ContextImpl(ContextImpl context) { mPackageInfo = context.mPackageInfo; + mBasePackageName = context.mBasePackageName; mResources = context.mResources; mMainThread = context.mMainThread; mContentResolver = context.mContentResolver; @@ -1458,13 +1458,14 @@ class ContextImpl extends Context { final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) { - init(packageInfo, activityToken, mainThread, null); + init(packageInfo, activityToken, mainThread, null, null); } final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread, - Resources container) { + Resources container, String basePackageName) { mPackageInfo = packageInfo; + mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName; mResources = mPackageInfo.getResources(mainThread); if (mResources != null && container != null @@ -1485,6 +1486,7 @@ class ContextImpl extends Context { final void init(Resources resources, ActivityThread mainThread) { mPackageInfo = null; + mBasePackageName = null; mResources = resources; mMainThread = mainThread; mContentResolver = new ApplicationContentResolver(this, mainThread); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index e2588cf..9e20764 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -103,7 +103,7 @@ public interface IActivityManager extends IInterface { throws RemoteException; public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException; public boolean willActivityBeVisible(IBinder token) throws RemoteException; - public Intent registerReceiver(IApplicationThread caller, + public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String requiredPermission) throws RemoteException; public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException; @@ -361,6 +361,8 @@ public interface IActivityManager extends IInterface { public void registerProcessObserver(IProcessObserver observer) throws RemoteException; public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException; + public boolean isIntentSenderTargetedToPackage(IIntentSender sender) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -587,4 +589,5 @@ public interface IActivityManager extends IInterface { int REMOVE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+131; int REGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+132; int UNREGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+133; + int IS_INTENT_SENDER_TARGETED_TO_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+134; } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 5b43b65..b4827cb 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -365,7 +365,7 @@ public final class PendingIntent implements Parcelable { * is no longer allowing more intents to be sent through it. */ public void send() throws CanceledException { - send(null, 0, null, null, null); + send(null, 0, null, null, null, null); } /** @@ -379,7 +379,7 @@ public final class PendingIntent implements Parcelable { * is no longer allowing more intents to be sent through it. */ public void send(int code) throws CanceledException { - send(null, code, null, null, null); + send(null, code, null, null, null, null); } /** @@ -399,7 +399,7 @@ public final class PendingIntent implements Parcelable { */ public void send(Context context, int code, Intent intent) throws CanceledException { - send(context, code, intent, null, null); + send(context, code, intent, null, null, null); } /** @@ -420,7 +420,7 @@ public final class PendingIntent implements Parcelable { */ public void send(int code, OnFinished onFinished, Handler handler) throws CanceledException { - send(null, code, null, onFinished, handler); + send(null, code, null, onFinished, handler, null); } /** @@ -449,20 +449,64 @@ public final class PendingIntent implements Parcelable { * @see #send(int) * @see #send(Context, int, Intent) * @see #send(int, android.app.PendingIntent.OnFinished, Handler) + * @see #send(Context, int, Intent, OnFinished, Handler, String) * * @throws CanceledException Throws CanceledException if the PendingIntent * is no longer allowing more intents to be sent through it. */ public void send(Context context, int code, Intent intent, OnFinished onFinished, Handler handler) throws CanceledException { + send(context, code, intent, onFinished, handler, null); + } + + /** + * Perform the operation associated with this PendingIntent, allowing the + * caller to specify information about the Intent to use and be notified + * when the send has completed. + * + * <p>For the intent parameter, a PendingIntent + * often has restrictions on which fields can be supplied here, based on + * how the PendingIntent was retrieved in {@link #getActivity}, + * {@link #getBroadcast}, or {@link #getService}. + * + * @param context The Context of the caller. This may be null if + * <var>intent</var> is also null. + * @param code Result code to supply back to the PendingIntent's target. + * @param intent Additional Intent data. See {@link Intent#fillIn + * Intent.fillIn()} for information on how this is applied to the + * original Intent. Use null to not modify the original Intent. + * @param onFinished The object to call back on when the send has + * completed, or null for no callback. + * @param handler Handler identifying the thread on which the callback + * should happen. If null, the callback will happen from the thread + * pool of the process. + * @param requiredPermission Name of permission that a recipient of the PendingIntent + * is required to hold. This is only valid for broadcast intents, and + * corresponds to the permission argument in + * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}. + * If null, no permission is required. + * + * @see #send() + * @see #send(int) + * @see #send(Context, int, Intent) + * @see #send(int, android.app.PendingIntent.OnFinished, Handler) + * @see #send(Context, int, Intent, OnFinished, Handler) + * + * @throws CanceledException Throws CanceledException if the PendingIntent + * is no longer allowing more intents to be sent through it. + */ + public void send(Context context, int code, Intent intent, + OnFinished onFinished, Handler handler, String requiredPermission) + throws CanceledException { try { String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) : null; int res = mTarget.send(code, intent, resolvedType, onFinished != null - ? new FinishedDispatcher(this, onFinished, handler) - : null); + ? new FinishedDispatcher(this, onFinished, handler) + : null, + requiredPermission); if (res < 0) { throw new CanceledException(); } @@ -491,6 +535,20 @@ public final class PendingIntent implements Parcelable { } /** + * @hide + * Check to verify that this PendingIntent targets a specific package. + */ + public boolean isTargetedToPackage() { + try { + return ActivityManagerNative.getDefault() + .isIntentSenderTargetedToPackage(mTarget); + } catch (RemoteException e) { + // Should never happen. + return false; + } + } + + /** * Comparison operator on two PendingIntent objects, such that true * is returned then they both represent the same operation from the * same package. This allows you to use {@link #getActivity}, diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 41eea2e..42eda02 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -188,8 +188,9 @@ public class SearchDialog extends Dialog { mSearchPlate = mSearchView.findViewById(com.android.internal.R.id.search_plate); mWorkingSpinner = getContext().getResources(). getDrawable(com.android.internal.R.drawable.search_spinner); - mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( - null, null, mWorkingSpinner, null); + // TODO: Restore the spinner for slow suggestion lookups + // mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( + // null, null, mWorkingSpinner, null); setWorking(false); // pre-hide all the extraneous elements diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 85a2fa8..7274362 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -329,6 +329,15 @@ public class SearchManager public final static String SUGGEST_COLUMN_FLAGS = "suggest_flags"; /** + * Column name for suggestions cursor. <i>Optional.</i> This column may be + * used to specify the time in (@link System#currentTimeMillis + * System.currentTImeMillis()} (wall time in UTC) when an item was last + * accessed within the results-providing application. If set, this may be + * used to show more-recently-used items first. + */ + public final static String SUGGEST_COLUMN_LAST_ACCESS_HINT = "suggest_last_access_hint"; + + /** * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion * should not be stored as a shortcut in global search. */ diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index aecec66..fed6d81 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1029,6 +1029,12 @@ public abstract class Context { * * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. * + * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers + * registered with this method will correctly respect the + * {@link Intent#setPackage(String)} specified for an Intent being broadcast. + * Prior to that, it would be ignored and delivered to all matching registered + * receivers. Be careful if using this for security.</p> + * * <p class="note">Note: this method <em>cannot be called from a * {@link BroadcastReceiver} component;</em> that is, from a BroadcastReceiver * that is declared in an application's manifest. It is okay, however, to call @@ -1059,6 +1065,12 @@ public abstract class Context { * * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. * + * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers + * registered with this method will correctly respect the + * {@link Intent#setPackage(String)} specified for an Intent being broadcast. + * Prior to that, it would be ignored and delivered to all matching registered + * receivers. Be careful if using this for security.</p> + * * @param receiver The BroadcastReceiver to handle the broadcast. * @param filter Selects the Intent broadcasts to be received. * @param broadcastPermission String naming a permissions that a diff --git a/core/java/android/content/IIntentSender.aidl b/core/java/android/content/IIntentSender.aidl index b7da472..7dbd6f2 100644 --- a/core/java/android/content/IIntentSender.aidl +++ b/core/java/android/content/IIntentSender.aidl @@ -22,5 +22,5 @@ import android.content.Intent; /** @hide */ interface IIntentSender { int send(int code, in Intent intent, String resolvedType, - IIntentReceiver finishedReceiver); + IIntentReceiver finishedReceiver, String requiredPermission); } diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index 007a715..4db4bdc 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -154,14 +154,47 @@ public class IntentSender implements Parcelable { */ public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler) throws SendIntentException { + sendIntent(context, code, intent, onFinished, handler, null); + } + + /** + * Perform the operation associated with this IntentSender, allowing the + * caller to specify information about the Intent to use and be notified + * when the send has completed. + * + * @param context The Context of the caller. This may be null if + * <var>intent</var> is also null. + * @param code Result code to supply back to the IntentSender's target. + * @param intent Additional Intent data. See {@link Intent#fillIn + * Intent.fillIn()} for information on how this is applied to the + * original Intent. Use null to not modify the original Intent. + * @param onFinished The object to call back on when the send has + * completed, or null for no callback. + * @param handler Handler identifying the thread on which the callback + * should happen. If null, the callback will happen from the thread + * pool of the process. + * @param requiredPermission Name of permission that a recipient of the PendingIntent + * is required to hold. This is only valid for broadcast intents, and + * corresponds to the permission argument in + * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}. + * If null, no permission is required. + * + * + * @throws SendIntentException Throws CanceledIntentException if the IntentSender + * is no longer allowing more intents to be sent through it. + */ + public void sendIntent(Context context, int code, Intent intent, + OnFinished onFinished, Handler handler, String requiredPermission) + throws SendIntentException { try { String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) : null; int res = mTarget.send(code, intent, resolvedType, onFinished != null - ? new FinishedDispatcher(this, onFinished, handler) - : null); + ? new FinishedDispatcher(this, onFinished, handler) + : null, + requiredPermission); if (res < 0) { throw new SendIntentException(); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index fba16e1..57f5967 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -101,7 +101,9 @@ interface IConnectivityManager void protectVpn(in ParcelFileDescriptor socket); - String prepareVpn(String packageName); + boolean prepareVpn(String oldPackage, String newPackage); ParcelFileDescriptor establishVpn(in VpnConfig config); + + void doLegacyVpn(in VpnConfig config, in String[] racoon, in String[] mtpd); } diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index ae9aa05..0548250 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -33,4 +33,7 @@ interface INetworkStatsService { /** Return usage summary per UID for traffic that matches template. */ NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags); + /** Force update of statistics. */ + void forceUpdate(); + } diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index ff6e220..dd2945c 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -279,10 +279,17 @@ public class NetworkStatsHistory implements Parcelable { return (long) (start + (r.nextFloat() * (end - start))); } - public void dump(String prefix, PrintWriter pw) { + public void dump(String prefix, PrintWriter pw, boolean fullHistory) { pw.print(prefix); pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); - for (int i = 0; i < bucketCount; i++) { + + final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); + if (start > 0) { + pw.print(prefix); + pw.print(" (omitting "); pw.print(start); pw.println(" buckets)"); + } + + for (int i = start; i < bucketCount; i++) { pw.print(prefix); pw.print(" bucketStart="); pw.print(bucketStart[i]); pw.print(" rx="); pw.print(rx[i]); @@ -293,7 +300,7 @@ public class NetworkStatsHistory implements Parcelable { @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); - dump("", new PrintWriter(writer)); + dump("", new PrintWriter(writer), false); return writer.toString(); } diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java index 5ade9eb..0eb8cd8 100644 --- a/core/java/android/nfc/NdefRecord.java +++ b/core/java/android/nfc/NdefRecord.java @@ -338,7 +338,15 @@ public final class NdefRecord implements Parcelable { * @hide */ public static NdefRecord createUri(Uri uri) { - String uriString = uri.toString(); + return createUri(uri.toString()); + } + + /** + * Creates an NDEF record of well known type URI. + * TODO: Make a public API + * @hide + */ + public static NdefRecord createUri(String uriString) { byte prefix = 0x0; for (int i = 1; i < URI_PREFIX_MAP.length; i++) { if (uriString.startsWith(URI_PREFIX_MAP[i])) { diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java index 54583d6..a73067a 100644 --- a/core/java/android/nfc/Tag.java +++ b/core/java/android/nfc/Tag.java @@ -313,14 +313,16 @@ public final class Tag implements Parcelable { */ @Override public String toString() { - StringBuilder sb = new StringBuilder("TAG ") - .append("uid = ") - .append(mId) - .append(" Tech ["); - for (int i : mTechList) { - sb.append(i) - .append(", "); + StringBuilder sb = new StringBuilder("TAG: Tech ["); + String[] techList = getTechList(); + int length = techList.length; + for (int i = 0; i < length; i++) { + sb.append(techList[i]); + if (i < length - 1) { + sb.append(", "); + } } + sb.append("]"); return sb.toString(); } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index c9b6121..fcf4796 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -241,6 +241,4 @@ interface INetworkManagementService */ int getInterfaceTxThrottle(String iface); - void setBandwidthControlEnabled(boolean enabled); - } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index d475f36..05e39ac 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -92,12 +92,6 @@ public class Process { public static final int SDCARD_RW_GID = 1015; /** - * Defines the UID for the KeyChain service. - * @hide - */ - public static final int KEYCHAIN_UID = 1020; - - /** * Defines the UID/GID for the NFC service process. * @hide */ diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index 3971045..b492615 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -17,9 +17,6 @@ package android.provider; -import com.android.internal.util.ArrayUtils; - -import android.accounts.Account; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.ContentProviderClient; @@ -35,13 +32,10 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; import android.os.RemoteException; -import android.text.TextUtils; import android.text.format.DateUtils; import android.text.format.Time; import android.util.Log; -import java.util.Arrays; - /** * <p> * The contract between the calendar provider and applications. Contains @@ -97,6 +91,8 @@ public final class CalendarContract { /** * Broadcast Action: This is the intent that gets fired when an alarm * notification needs to be posted for a reminder. + * + * @SdkConstant */ public static final String ACTION_EVENT_REMINDER = "android.intent.action.EVENT_REMINDER"; @@ -122,8 +118,7 @@ public final class CalendarContract { /** * The content:// style URL for the top-level calendar authority */ - public static final Uri CONTENT_URI = - Uri.parse("content://" + AUTHORITY); + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); /** * An optional insert, update or delete URI parameter that allows the caller @@ -572,29 +567,6 @@ public final class CalendarContract { * </ul> */ public static class Calendars implements BaseColumns, SyncColumns, CalendarColumns { - private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars.ACCOUNT_NAME + "=?" - + " AND " - + Calendars.ACCOUNT_TYPE + "=?"; - - /** - * Helper function for generating a calendars query. This is blocking - * and should not be used on the UI thread. See - * {@link ContentResolver#query(Uri, String[], String, String[], String)} - * for more details about using the parameters. - * - * @param cr The ContentResolver to query with - * @param projection A list of columns to return - * @param selection A formatted selection string - * @param selectionArgs arguments to the selection string - * @param orderBy How to order the returned rows - * @return - */ - public static final Cursor query(ContentResolver cr, String[] projection, String selection, - String[] selectionArgs, String orderBy) { - return cr.query(CONTENT_URI, projection, selection, selectionArgs, - orderBy == null ? DEFAULT_SORT_ORDER : orderBy); - } - /** * The content:// style URL for accessing Calendars */ @@ -622,7 +594,9 @@ public final class CalendarContract { * These fields are only writable by a sync adapter. To modify them the * caller must include {@link #CALLER_IS_SYNCADAPTER}, * {@link #ACCOUNT_NAME}, and {@link #ACCOUNT_TYPE} in the Uri's query - * parameters. + * parameters. TODO move to provider + * + * @hide */ public static final String[] SYNC_WRITABLE_COLUMNS = new String[] { ACCOUNT_NAME, @@ -737,7 +711,7 @@ public final class CalendarContract { /** * the projection used by the attendees query */ - private static final String[] PROJECTION = new String[] { + public static final String[] PROJECTION = new String[] { _ID, ATTENDEE_NAME, ATTENDEE_EMAIL, ATTENDEE_RELATIONSHIP, ATTENDEE_STATUS,}; private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?"; @@ -1420,37 +1394,6 @@ public final class CalendarContract { CalendarColumns { /** - * Queries all events with the given projection. This is a blocking call - * and should not be done on the UI thread. - * - * @param cr The content resolver to use for the query - * @param projection The columns to return - * @return A Cursor containing all events in the db - */ - public static final Cursor query(ContentResolver cr, String[] projection) { - return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER); - } - - /** - * Queries events using the given projection, selection filter, and - * ordering. This is a blocking call and should not be done on the UI - * thread. For selection and selectionArgs usage see - * {@link ContentResolver#query(Uri, String[], String, String[], String)} - * - * @param cr The content resolver to use for the query - * @param projection The columns to return - * @param selection Filter on the query as an SQL WHERE statement - * @param selectionArgs Args to replace any '?'s in the selection - * @param orderBy How to order the rows as an SQL ORDER BY statement - * @return A Cursor containing the matching events - */ - public static final Cursor query(ContentResolver cr, String[] projection, String selection, - String[] selectionArgs, String orderBy) { - return cr.query(CONTENT_URI, projection, selection, null, - orderBy == null ? DEFAULT_SORT_ORDER : orderBy); - } - - /** * The content:// style URL for interacting with events. Appending an * event id using {@link ContentUris#withAppendedId(Uri, long)} will * specify a single event. @@ -1464,7 +1407,7 @@ public final class CalendarContract { * appended event ID. Deletion of exceptions requires both the original event ID and * the exception event ID (see {@link Uri.Builder#appendPath}). */ - public static final Uri EXCEPTION_CONTENT_URI = + public static final Uri CONTENT_EXCEPTION_URI = Uri.parse("content://" + AUTHORITY + "/exception"); /** @@ -1475,7 +1418,9 @@ public final class CalendarContract { /** * These are columns that should only ever be updated by the provider, * either because they are views mapped to another table or because they - * are used for provider only functionality. + * are used for provider only functionality. TODO move to provider + * + * @hide */ public static String[] PROVIDER_WRITABLE_COLUMNS = new String[] { ACCOUNT_NAME, @@ -1505,7 +1450,9 @@ public final class CalendarContract { /** * These fields are only writable by a sync adapter. To modify them the * caller must include CALLER_IS_SYNCADAPTER, _SYNC_ACCOUNT, and - * _SYNC_ACCOUNT_TYPE in the query parameters. + * _SYNC_ACCOUNT_TYPE in the query parameters. TODO move to provider. + * + * @hide */ public static final String[] SYNC_WRITABLE_COLUMNS = new String[] { _SYNC_ID, @@ -1672,11 +1619,6 @@ public final class CalendarContract { public static final String END_MINUTE = "endMinute"; } - /** - * CalendarCache stores some settings for calendar including the current - * time zone for the instaces. These settings are stored using a key/value - * scheme. - */ protected interface CalendarCacheColumns { /** * The key for the setting. Keys are defined in {@link CalendarCache}. @@ -1689,6 +1631,11 @@ public final class CalendarContract { public static final String VALUE = "value"; } + /** + * CalendarCache stores some settings for calendar including the current + * time zone for the instances. These settings are stored using a key/value + * scheme. A {@link #KEY} must be specified when updating these values. + */ public static class CalendarCache implements CalendarCacheColumns { /** * The URI to use for retrieving the properties from the Calendar db. @@ -1697,22 +1644,11 @@ public final class CalendarContract { Uri.parse("content://" + AUTHORITY + "/properties"); /** - * If updating a property, this must be provided as the selection. All - * other selections will fail. For queries this field can be omitted to - * retrieve all properties or used to query a single property. Valid - * keys include {@link #TIMEZONE_KEY_TYPE}, - * {@link #TIMEZONE_KEY_INSTANCES}, and - * {@link #TIMEZONE_KEY_INSTANCES_PREVIOUS}, though the last one can - * only be read, not written. - */ - public static final String WHERE = "key=?"; - - /** * They key for updating the use of auto/home time zones in Calendar. * Valid values are {@link #TIMEZONE_TYPE_AUTO} or * {@link #TIMEZONE_TYPE_HOME}. */ - public static final String TIMEZONE_KEY_TYPE = "timezoneType"; + public static final String KEY_TIMEZONE_TYPE = "timezoneType"; /** * The key for updating the time zone used by the provider when it @@ -1720,24 +1656,24 @@ public final class CalendarContract { * type is set to {@link #TIMEZONE_TYPE_HOME}. A valid time zone id * should be written to this field. */ - public static final String TIMEZONE_KEY_INSTANCES = "timezoneInstances"; + public static final String KEY_TIMEZONE_INSTANCES = "timezoneInstances"; /** * The key for reading the last time zone set by the user. This should * only be read by apps and it will be automatically updated whenever - * {@link #TIMEZONE_KEY_INSTANCES} is updated with + * {@link #KEY_TIMEZONE_INSTANCES} is updated with * {@link #TIMEZONE_TYPE_HOME} set. */ - public static final String TIMEZONE_KEY_INSTANCES_PREVIOUS = "timezoneInstancesPrevious"; + public static final String KEY_TIMEZONE_INSTANCES_PREVIOUS = "timezoneInstancesPrevious"; /** - * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider + * The value to write to {@link #KEY_TIMEZONE_TYPE} if the provider * should stay in sync with the device's time zone. */ public static final String TIMEZONE_TYPE_AUTO = "auto"; /** - * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider + * The value to write to {@link #KEY_TIMEZONE_TYPE} if the provider * should use a fixed time zone set by the user. */ public static final String TIMEZONE_TYPE_HOME = "home"; @@ -1814,7 +1750,7 @@ public final class CalendarContract { /** * The projection used by the EventDays query. */ - private static final String[] PROJECTION = { + public static final String[] PROJECTION = { STARTDAY, ENDDAY }; private static final String SELECTION = "selected=1"; @@ -1900,7 +1836,7 @@ public final class CalendarContract { /** * The projection used by the reminders query. */ - private static final String[] PROJECTION = new String[] { + public static final String[] PROJECTION = new String[] { _ID, MINUTES, METHOD,}; @SuppressWarnings("hiding") public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders"); @@ -1967,17 +1903,28 @@ public final class CalendarContract { public static final String NOTIFY_TIME = "notifyTime"; /** - * The state of this alert. It starts out as {@link #SCHEDULED}, then - * when the alarm goes off, it changes to {@link #FIRED}, and then when - * the user dismisses the alarm it changes to {@link #DISMISSED}. Column + * The state of this alert. It starts out as {@link #STATE_SCHEDULED}, then + * when the alarm goes off, it changes to {@link #STATE_FIRED}, and then when + * the user dismisses the alarm it changes to {@link #STATE_DISMISSED}. Column * name. * <P>Type: INTEGER</P> */ public static final String STATE = "state"; - public static final int SCHEDULED = 0; - public static final int FIRED = 1; - public static final int DISMISSED = 2; + /** + * An alert begins in this state when it is first created. + */ + public static final int STATE_SCHEDULED = 0; + /** + * After a notification for an alert has been created it should be + * updated to fired. + */ + public static final int STATE_FIRED = 1; + /** + * Once the user has dismissed the notification the alert's state should + * be set to dismissed so it is not fired again. + */ + public static final int STATE_DISMISSED = 2; /** * The number of minutes that this alarm precedes the start time. Column @@ -2024,7 +1971,7 @@ public final class CalendarContract { private static final String WHERE_FINDNEXTALARMTIME = ALARM_TIME + ">=?"; private static final String SORT_ORDER_ALARMTIME_ASC = ALARM_TIME + " ASC"; - private static final String WHERE_RESCHEDULE_MISSED_ALARMS = STATE + "=" + SCHEDULED + private static final String WHERE_RESCHEDULE_MISSED_ALARMS = STATE + "=" + STATE_SCHEDULED + " AND " + ALARM_TIME + "<?" + " AND " + ALARM_TIME + ">?" + " AND " + END + ">=?"; @@ -2038,10 +1985,11 @@ public final class CalendarContract { public static final Uri CONTENT_URI_BY_INSTANCE = Uri.parse("content://" + AUTHORITY + "/calendar_alerts/by_instance"); - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; /** - * Helper for inserting an alarm time associated with an event + * Helper for inserting an alarm time associated with an event TODO move + * to Provider * * @hide */ @@ -2056,51 +2004,32 @@ public final class CalendarContract { values.put(CalendarAlerts.CREATION_TIME, currentTime); values.put(CalendarAlerts.RECEIVED_TIME, 0); values.put(CalendarAlerts.NOTIFY_TIME, 0); - values.put(CalendarAlerts.STATE, SCHEDULED); + values.put(CalendarAlerts.STATE, STATE_SCHEDULED); values.put(CalendarAlerts.MINUTES, minutes); return cr.insert(CONTENT_URI, values); } /** - * Queries alerts info using the given projection, selection filter, and - * ordering. This is a blocking call and should not be done on the UI - * thread. For selection and selectionArgs usage see - * {@link ContentResolver#query(Uri, String[], String, String[], String)} - * - * @param cr The content resolver to use for the query - * @param projection The columns to return - * @param selection Filter on the query as an SQL WHERE statement - * @param selectionArgs Args to replace any '?'s in the selection - * @param sortOrder How to order the rows as an SQL ORDER BY statement - * @return A Cursor containing the matching alerts - */ - public static final Cursor query(ContentResolver cr, String[] projection, - String selection, String[] selectionArgs, String sortOrder) { - return cr.query(CONTENT_URI, projection, selection, selectionArgs, - sortOrder); - } - - /** * Finds the next alarm after (or equal to) the given time and returns * the time of that alarm or -1 if no such alarm exists. This is a - * blocking call and should not be done on the UI thread. + * blocking call and should not be done on the UI thread. TODO move to + * provider * * @param cr the ContentResolver * @param millis the time in UTC milliseconds * @return the next alarm time greater than or equal to "millis", or -1 * if no such alarm exists. + * @hide */ public static final long findNextAlarmTime(ContentResolver cr, long millis) { String selection = ALARM_TIME + ">=" + millis; // TODO: construct an explicit SQL query so that we can add // "LIMIT 1" to the end and get just one result. String[] projection = new String[] { ALARM_TIME }; - Cursor cursor = query(cr, projection, - WHERE_FINDNEXTALARMTIME, - new String[] { + Cursor cursor = cr.query(CONTENT_URI, projection, WHERE_FINDNEXTALARMTIME, + (new String[] { Long.toString(millis) - }, - SORT_ORDER_ALARMTIME_ASC); + }), SORT_ORDER_ALARMTIME_ASC); long alarmTime = -1; try { if (cursor != null && cursor.moveToFirst()) { @@ -2116,13 +2045,14 @@ public final class CalendarContract { /** * Searches the CalendarAlerts table for alarms that should have fired - * but have not and then reschedules them. This method can be called - * at boot time to restore alarms that may have been lost due to a - * phone reboot. + * but have not and then reschedules them. This method can be called at + * boot time to restore alarms that may have been lost due to a phone + * reboot. TODO move to provider * * @param cr the ContentResolver * @param context the Context * @param manager the AlarmManager + * @hide */ public static final void rescheduleMissedAlarms(ContentResolver cr, Context context, AlarmManager manager) { @@ -2136,15 +2066,10 @@ public final class CalendarContract { // TODO: construct an explicit SQL query so that we can add // "GROUPBY" instead of doing a sort and de-dup - Cursor cursor = CalendarAlerts.query(cr, - projection, - WHERE_RESCHEDULE_MISSED_ALARMS, - new String[] { - Long.toString(now), - Long.toString(ancient), - Long.toString(now) - }, - SORT_ORDER_ALARMTIME_ASC); + Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection, + WHERE_RESCHEDULE_MISSED_ALARMS, (new String[] { + Long.toString(now), Long.toString(ancient), Long.toString(now) + }), SORT_ORDER_ALARMTIME_ASC); if (cursor == null) { return; } @@ -2177,12 +2102,13 @@ public final class CalendarContract { * keep scheduled reminders up to date but apps may use this to * implement snooze functionality without modifying the reminders table. * Scheduled alarms will generate an intent using - * {@link #ACTION_EVENT_REMINDER}. + * {@link #ACTION_EVENT_REMINDER}. TODO Move to provider * * @param context A context for referencing system resources * @param manager The AlarmManager to use or null * @param alarmTime The time to fire the intent in UTC millis since * epoch + * @hide */ public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) { if (DEBUG) { @@ -2204,31 +2130,28 @@ public final class CalendarContract { } /** - * Searches for an entry in the CalendarAlerts table that matches - * the given event id, begin time and alarm time. If one is found - * then this alarm already exists and this method returns true. - * + * Searches for an entry in the CalendarAlerts table that matches the + * given event id, begin time and alarm time. If one is found then this + * alarm already exists and this method returns true. TODO Move to + * provider + * * @param cr the ContentResolver * @param eventId the event id to match * @param begin the start time of the event in UTC millis * @param alarmTime the alarm time of the event in UTC millis - * @return true if there is already an alarm for the given event - * with the same start time and alarm time. + * @return true if there is already an alarm for the given event with + * the same start time and alarm time. + * @hide */ public static final boolean alarmExists(ContentResolver cr, long eventId, long begin, long alarmTime) { // TODO: construct an explicit SQL query so that we can add // "LIMIT 1" to the end and get just one result. String[] projection = new String[] { ALARM_TIME }; - Cursor cursor = query(cr, - projection, - WHERE_ALARM_EXISTS, - new String[] { - Long.toString(eventId), - Long.toString(begin), - Long.toString(alarmTime) - }, - null); + Cursor cursor = cr.query(CONTENT_URI, projection, WHERE_ALARM_EXISTS, + (new String[] { + Long.toString(eventId), Long.toString(begin), Long.toString(alarmTime) + }), null); boolean found = false; try { if (cursor != null && cursor.getCount() > 0) { diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index ab0cb50..d99c760 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -52,18 +52,27 @@ public class VoicemailContract { /** The authority used by the voicemail provider. */ public static final String AUTHORITY = "com.android.voicemail"; - - /** URI to insert/retrieve all voicemails. */ + /** + * URI to insert/retrieve all voicemails. + * @deprecated + */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/voicemail"); - /** URI to insert/retrieve voicemails by a given voicemail source. */ + /** + * URI to insert/retrieve voicemails by a given voicemail source. + * @deprecated + */ public static final Uri CONTENT_URI_SOURCE = Uri.parse("content://" + AUTHORITY + "/voicemail/source/"); + /** + * Parameter key used in the URI to specify the voicemail source package name. + * <p> This field must be set in all requests that originate from a voicemail source. + */ + public static final String PARAM_KEY_SOURCE_PACKAGE = "source_package"; // TODO: Move ACTION_NEW_VOICEMAIL to the Intent class. /** Broadcast intent when a new voicemail record is inserted. */ public static final String ACTION_NEW_VOICEMAIL = "android.intent.action.NEW_VOICEMAIL"; - /** * Extra included in {@value Intent#ACTION_PROVIDER_CHANGED} and * {@value #ACTION_NEW_VOICEMAIL} broadcast intents to indicate if the receiving @@ -71,15 +80,27 @@ public class VoicemailContract { */ public static final String EXTRA_SELF_CHANGE = "com.android.voicemail.extra.SELF_CHANGE"; - /** The mime type for a collection of voicemails. */ - public static final String DIR_TYPE = - "vnd.android.cursor.dir/voicemails"; + /** + * The mime type for a collection of voicemails. + * @deprecated */ + public static final String DIR_TYPE = "vnd.android.cursor.dir/voicemails"; + /** Defines fields exposed through the /voicemail path of this content provider. */ public static final class Voicemails implements BaseColumns { /** Not instantiable. */ private Voicemails() { } + /** URI to insert/retrieve voicemails by a given voicemail source. */ + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/voicemail"); + /** URI to insert/retrieve voicemails by a given voicemail source. */ + public static final Uri CONTENT_URI_SOURCE = + Uri.parse("content://" + AUTHORITY + "/voicemail/source/"); + + /** The mime type for a collection of voicemails. */ + public static final String DIR_TYPE = "vnd.android.cursor.dir/voicemails"; + /** * Phone number of the voicemail sender. * <P>Type: TEXT</P> @@ -143,5 +164,101 @@ public class VoicemailContract { * @hide */ public static final String _DATA = "_data"; + + /** + * A convenience method to build voicemail URI specific to a source package by appending + * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. + */ + public static Uri buildSourceUri(String packageName) { + return Voicemails.CONTENT_URI.buildUpon() + .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build(); + } + } + + /** Defines fields exposed through the /status path of this content provider. */ + public static final class Status implements BaseColumns { + /** URI to insert/retrieve status of voicemail source. */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/status"); + /** The mime type for a collection of voicemail source statuses. */ + public static final String DIR_TYPE = "vnd.android.cursor.dir/voicemail.source.status"; + /** The mime type for a collection of voicemails. */ + public static final String ITEM_TYPE = "vnd.android.cursor.item/voicemail.source.status"; + + /** Not instantiable. */ + private Status() { + } + /** + * The package name of the voicemail source. There can only be a one entry per source. + * <P>Type: TEXT</P> + */ + public static final String SOURCE_PACKAGE = "source_package"; + /** + * The URI to call to invoke source specific voicemail settings screen. On a user request + * to setup voicemail an intent with action VIEW with this URI will be fired by the system. + * <P>Type: TEXT</P> + */ + public static final String SETTINGS_URI = "settings_uri"; + /** + * The URI to call when the user requests to directly access the voicemail from the remote + * server. In case of an IVR voicemail system this is typically set to the the voicemail + * number specified using a tel:/ URI. + * <P>Type: TEXT</P> + */ + public static final String VOICEMAIL_ACCESS_URI = "voicemail_access_uri"; + /** + * The configuration state of the voicemail source. + * <P> Possible values: + * {@link #CONFIGURATION_STATE_OK}, + * {@link #CONFIGURATION_STATE_NOT_CONFIGURED}, + * {@link #CONFIGURATION_STATE_CAN_BE_CONFIGURED} + * <P>Type: INTEGER</P> + */ + public static final String CONFIGURATION_STATE = "configuration_state"; + public static final int CONFIGURATION_STATE_OK = 0; + public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; + /** + * This state must be used when the source has verified that the current user can be + * upgraded to visual voicemail and would like to show a set up invitation message. + */ + public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; + /** + * The data channel state of the voicemail source. This the channel through which the source + * pulls voicemail data from a remote server. + * <P> Possible values: + * {@link #DATA_CHANNEL_STATE_OK}, + * {@link #DATA_CHANNEL_STATE_NO_CONNECTION} + * </P> + * <P>Type: INTEGER</P> + */ + public static final String DATA_CHANNEL_STATE = "data_channel_state"; + public static final int DATA_CHANNEL_STATE_OK = 0; + public static final int DATA_CHANNEL_STATE_NO_CONNECTION = 1; + /** + * The notification channel state of the voicemail source. This is the channel through which + * the source gets notified of new voicemails on the remote server. + * <P> Possible values: + * {@link #NOTIFICATION_CHANNEL_STATE_OK}, + * {@link #NOTIFICATION_CHANNEL_STATE_NO_CONNECTION}, + * {@link #NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING} + * </P> + * <P>Type: INTEGER</P> + */ + public static final String NOTIFICATION_CHANNEL_STATE = "notification_channel_state"; + public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; + public static final int NOTIFICATION_CHANNEL_STATE_NO_CONNECTION = 1; + /** + * Use this state when the notification can only tell that there are pending messages on + * the server but no details of the sender/time etc are known. + */ + public static final int NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING = 2; + + /** + * A convenience method to build status URI specific to a source package by appending + * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. + */ + public static Uri buildSourceUri(String packageName) { + return Status.CONTENT_URI.buildUpon() + .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build(); + } } } diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index ca2212c..8a6fdb4 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -517,6 +517,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothProfile.EXTRA_STATE, state); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent, BLUETOOTH_PERM); if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); @@ -530,6 +531,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothProfile.EXTRA_STATE, state); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent, BLUETOOTH_PERM); if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state); diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 4107c5a..aae9ccf 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1,4 +1,4 @@ -/* + /* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -266,7 +266,7 @@ public abstract class Layout { } } - Alignment align = mAlignment; + Alignment paraAlign = mAlignment; TabStops tabStops = null; boolean tabStopsIsInitialized = false; @@ -310,10 +310,10 @@ public abstract class Layout { ParagraphStyle.class); spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class); - align = mAlignment; + paraAlign = mAlignment; for (int n = spans.length-1; n >= 0; n--) { if (spans[n] instanceof AlignmentSpan) { - align = ((AlignmentSpan) spans[n]).getAlignment(); + paraAlign = ((AlignmentSpan) spans[n]).getAlignment(); break; } } @@ -360,6 +360,16 @@ public abstract class Layout { tabStopsIsInitialized = true; } + // Determine whether the line aligns to normal, opposite, or center. + Alignment align = paraAlign; + if (align == Alignment.ALIGN_LEFT) { + align = (dir == DIR_LEFT_TO_RIGHT) ? + Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE; + } else if (align == Alignment.ALIGN_RIGHT) { + align = (dir == DIR_LEFT_TO_RIGHT) ? + Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL; + } + int x; if (align == Alignment.ALIGN_NORMAL) { if (dir == DIR_LEFT_TO_RIGHT) { @@ -411,7 +421,9 @@ public abstract class Layout { int dir = getParagraphDirection(line); int x; - if (align == Alignment.ALIGN_NORMAL) { + if (align == Alignment.ALIGN_LEFT) { + x = left; + } else if (align == Alignment.ALIGN_NORMAL) { if (dir == DIR_LEFT_TO_RIGHT) { x = left; } else { @@ -430,7 +442,9 @@ public abstract class Layout { } } int max = (int)getLineExtent(line, tabStops, false); - if (align == Alignment.ALIGN_OPPOSITE) { + if (align == Alignment.ALIGN_RIGHT) { + x = right - max; + } else if (align == Alignment.ALIGN_OPPOSITE) { if (dir == DIR_LEFT_TO_RIGHT) { x = right - max; } else { @@ -738,11 +752,15 @@ public abstract class Layout { int dir = getParagraphDirection(line); Alignment align = getParagraphAlignment(line); - if (align == Alignment.ALIGN_NORMAL) { + if (align == Alignment.ALIGN_LEFT) { + return 0; + } else if (align == Alignment.ALIGN_NORMAL) { if (dir == DIR_RIGHT_TO_LEFT) return getParagraphRight(line) - getLineMax(line); else return 0; + } else if (align == Alignment.ALIGN_RIGHT) { + return mWidth - getLineMax(line); } else if (align == Alignment.ALIGN_OPPOSITE) { if (dir == DIR_RIGHT_TO_LEFT) return 0; @@ -765,11 +783,15 @@ public abstract class Layout { int dir = getParagraphDirection(line); Alignment align = getParagraphAlignment(line); - if (align == Alignment.ALIGN_NORMAL) { + if (align == Alignment.ALIGN_LEFT) { + return getParagraphLeft(line) + getLineMax(line); + } else if (align == Alignment.ALIGN_NORMAL) { if (dir == DIR_RIGHT_TO_LEFT) return mWidth; else return getParagraphLeft(line) + getLineMax(line); + } else if (align == Alignment.ALIGN_RIGHT) { + return mWidth; } else if (align == Alignment.ALIGN_OPPOSITE) { if (dir == DIR_RIGHT_TO_LEFT) return getLineMax(line); @@ -1765,8 +1787,10 @@ public abstract class Layout { ALIGN_NORMAL, ALIGN_OPPOSITE, ALIGN_CENTER, - // XXX ALIGN_LEFT, - // XXX ALIGN_RIGHT, + /** @hide */ + ALIGN_LEFT, + /** @hide */ + ALIGN_RIGHT, } private static final int TAB_INCREMENT = 20; diff --git a/core/java/android/view/ActionProvider.java b/core/java/android/view/ActionProvider.java new file mode 100644 index 0000000..6491da0 --- /dev/null +++ b/core/java/android/view/ActionProvider.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.content.Context; + +/** + * This class is a mediator for accomplishing a given task, for example sharing a file. + * It is responsible for creating a view that performs an action that accomplishes the task. + * This class also implements other functions such a performing a default action. + * <p> + * An ActionProvider can be optionally specified for a {@link MenuItem} and in such a + * case it will be responsible for creating the action view that appears in the + * {@link android.app.ActionBar} as a substitute for the menu item when the item is + * displayed as an action item. Also the provider is responsible for performing a + * default action if a menu item placed on the overflow menu of the ActionBar is + * selected and none of the menu item callbacks has handled the selection. + * </p> + * <p> + * There are two ways for using an action provider for creating and handling of action views: + * <ul> + * <li> + * Setting the action provider on a {@link MenuItem} directly by calling + * {@link MenuItem#setActionProvider(ActionProvider)}. + * </li> + * <li> + * Declaring the action provider in the menu XML resource. For example: + * <pre> + * <code> + * <item android:id="@+id/my_menu_item" + * android:title="Title" + * android:icon="@drawable/my_menu_item_icon" + * android:showAsAction="ifRoom" + * android:actionProviderClass="foo.bar.SomeActionProvider" /> + * </code> + * </pre> + * </li> + * </ul> + * </p> + * + * @see MenuItem#setActionProvider(ActionProvider) + * @see MenuItem#getActionProvider() + */ +public abstract class ActionProvider { + + /** + * Creates a new instance. + * + * @param context Context for accessing resources. + */ + public ActionProvider(Context context) { + } + + /** + * Factory method for creating new action views. + * + * @return A new action view. + */ + public abstract View onCreateActionView(); + + /** + * Performs an optional default action. + * <p> + * For the case of an action provider placed in a menu item not shown as an action this + * method is invoked if none of the callbacks for processing menu selection has handled + * the event. + * </p> + * <p> + * A menu item selection is processed in the following order: + * <ul> + * <li> + * Receiving a call to {@link MenuItem.OnMenuItemClickListener#onMenuItemClick + * MenuItem.OnMenuItemClickListener.onMenuItemClick}. + * </li> + * <li> + * Receiving a call to {@link android.app.Activity#onOptionsItemSelected(MenuItem) + * Activity.onOptionsItemSelected(MenuItem)} + * </li> + * <li> + * Receiving a call to {@link android.app.Fragment#onOptionsItemSelected(MenuItem) + * Fragment.onOptionsItemSelected(MenuItem)} + * </li> + * <li> + * Launching the {@link android.content.Intent} set via + * {@link MenuItem#setIntent(android.content.Intent) MenuItem.setIntent(android.content.Intent)} + * </li> + * <li> + * Invoking this method. + * </li> + * </ul> + * </p> + * <p> + * The default implementation does not perform any action. + * </p> + * + * @param actionView A view created by {@link #onCreateActionView()}. + */ + public void onPerformDefaultAction(View actionView) { + } +} diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index 372ac15..a7f0cba 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.util.AttributeSet; +import android.util.Log; import android.util.Xml; import java.io.IOException; @@ -42,6 +43,8 @@ import java.lang.reflect.Method; * <em>something</em> file.) */ public class MenuInflater { + private static final String LOG_TAG = "MenuInflater"; + /** Menu tag name in XML. */ private static final String XML_MENU = "menu"; @@ -53,10 +56,16 @@ public class MenuInflater { private static final int NO_ID = 0; - private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[]{Context.class}; + private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class}; + + private static final Class<?>[] ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE = ACTION_VIEW_CONSTRUCTOR_SIGNATURE; + + private final Object[] mActionViewConstructorArguments; + + private final Object[] mActionProviderConstructorArguments; private Context mContext; - + /** * Constructs a menu inflater. * @@ -64,6 +73,8 @@ public class MenuInflater { */ public MenuInflater(Context context) { mContext = context; + mActionViewConstructorArguments = new Object[] {context}; + mActionProviderConstructorArguments = mActionViewConstructorArguments; } /** @@ -172,14 +183,14 @@ public class MenuInflater { private static class InflatedOnMenuItemClickListener implements MenuItem.OnMenuItemClickListener { - private static final Class[] PARAM_TYPES = new Class[] { MenuItem.class }; + private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class }; private Context mContext; private Method mMethod; public InflatedOnMenuItemClickListener(Context context, String methodName) { mContext = context; - Class c = context.getClass(); + Class<?> c = context.getClass(); try { mMethod = c.getMethod(methodName, PARAM_TYPES); } catch (Exception e) { @@ -255,7 +266,8 @@ public class MenuInflater { private int itemActionViewLayout; private String itemActionViewClassName; - + private String itemActionProviderClassName; + private String itemListenerMethodName; private static final int defaultGroupId = NO_ID; @@ -333,9 +345,10 @@ public class MenuInflater { itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick); itemActionViewLayout = a.getResourceId(com.android.internal.R.styleable.MenuItem_actionLayout, 0); itemActionViewClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionViewClass); - + itemActionProviderClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionProviderClass); + a.recycle(); - + itemAdded = false; } @@ -377,20 +390,35 @@ public class MenuInflater { } } + boolean actionViewSpecified = false; if (itemActionViewClassName != null) { - try { - final Class<?> clazz = Class.forName(itemActionViewClassName, true, - mContext.getClassLoader()); - Constructor<?> c = clazz.getConstructor(ACTION_VIEW_CONSTRUCTOR_SIGNATURE); - item.setActionView((View) c.newInstance(mContext)); - } catch (Exception e) { - throw new InflateException(e); + View actionView = (View) newInstance(itemActionViewClassName, + ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments); + item.setActionView(actionView); + actionViewSpecified = true; + } + if (itemActionViewLayout > 0) { + if (!actionViewSpecified) { + item.setActionView(itemActionViewLayout); + actionViewSpecified = true; + } else { + Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'." + + " Action view already specified."); + } + } + if (itemActionProviderClassName != null) { + if (!actionViewSpecified) { + ActionProvider actionProvider = newInstance(itemActionProviderClassName, + ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE, + mActionProviderConstructorArguments); + item.setActionProvider(actionProvider); + } else { + Log.w(LOG_TAG, "Ignoring attribute 'itemActionProviderClass'." + + " Action view already specified."); } - } else if (itemActionViewLayout > 0) { - item.setActionView(itemActionViewLayout); } } - + public void addItem() { itemAdded = true; setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle)); @@ -406,6 +434,18 @@ public class MenuInflater { public boolean hasAddedItem() { return itemAdded; } + + @SuppressWarnings("unchecked") + private <T> T newInstance(String className, Class<?>[] constructorSignature, + Object[] arguments) { + try { + Class<?> clazz = mContext.getClassLoader().loadClass(className); + Constructor<?> constructor = clazz.getConstructor(constructorSignature); + return (T) constructor.newInstance(arguments); + } catch (Exception e) { + Log.w(LOG_TAG, "Cannot instantiate class: " + className, e); + } + return null; + } } - } diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java index dc68264..ccd8353 100644 --- a/core/java/android/view/MenuItem.java +++ b/core/java/android/view/MenuItem.java @@ -88,7 +88,6 @@ public interface MenuItem { * @see MenuItem#expandActionView() * @see MenuItem#collapseActionView() * @see MenuItem#setShowAsActionFlags(int) - * @see MenuItem# */ public interface OnActionExpandListener { /** @@ -480,6 +479,10 @@ public interface MenuItem { * Set an action view for this menu item. An action view will be displayed in place * of an automatically generated menu item element in the UI when this item is shown * as an action within a parent. + * <p> + * <strong>Note:</strong> Setting an action view overrides the action provider + * set via {@link #setActionProvider(ActionProvider)}. + * </p> * * @param view View to use for presenting this item to the user. * @return This Item so additional setters can be called. @@ -492,6 +495,10 @@ public interface MenuItem { * Set an action view for this menu item. An action view will be displayed in place * of an automatically generated menu item element in the UI when this item is shown * as an action within a parent. + * <p> + * <strong>Note:</strong> Setting an action view overrides the action provider + * set via {@link #setActionProvider(ActionProvider)}. + * </p> * * @param resId Layout resource to use for presenting this item to the user. * @return This Item so additional setters can be called. @@ -511,6 +518,32 @@ public interface MenuItem { public View getActionView(); /** + * Sets the {@link ActionProvider} responsible for creating an action view if + * the item is placed on the action bar. The provider also provides a default + * action invoked if the item is placed in the overflow menu. + * <p> + * <strong>Note:</strong> Setting an action provider overrides the action view + * set via {@link #setActionView(int)} or {@link #setActionView(View)}. + * </p> + * + * @param actionProvider The action provider. + * @return This Item so additional setters can be called. + * + * @see ActionProvider + */ + public MenuItem setActionProvider(ActionProvider actionProvider); + + /** + * Gets the {@link ActionProvider}. + * + * @return The action provider. + * + * @see ActionProvider + * @see #setActionProvider(ActionProvider) + */ + public ActionProvider getActionProvider(); + + /** * Expand the action view associated with this menu item. * The menu item must have an action view set, as well as * the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index bf7f359..1245898 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2523,18 +2523,26 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit public static final int TEXT_DIRECTION_ANY_RTL = 2; /** + * Text direction is the same as the one held by a 60% majority of the characters. If there is + * no majority then the paragraph direction is the resolved layout direction of the View. + * + * @hide + */ + public static final int TEXT_DIRECTION_CHAR_COUNT = 3; + + /** * Text direction is forced to LTR. * * @hide */ - public static final int TEXT_DIRECTION_LTR = 3; + public static final int TEXT_DIRECTION_LTR = 4; /** * Text direction is forced to RTL. * * @hide */ - public static final int TEXT_DIRECTION_RTL = 4; + public static final int TEXT_DIRECTION_RTL = 5; /** * Default text direction is inherited @@ -2542,6 +2550,11 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT; /** + * Default threshold for "char count" heuristic. + */ + protected static float DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD = 0.6f; + + /** * The text direction that has been defined by {@link #setTextDirection(int)}. * * {@hide} @@ -2551,6 +2564,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"), @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_CHAR_COUNT, to = "CHAR_COUNT"), @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL") }) @@ -9121,9 +9135,15 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } /** - * Reset the resolved layout direction by clearing the corresponding flag + * Reset the resolved layout direction. + * + * Subclasses need to override this method to clear cached information that depends on the + * resolved layout direction, or to inform child views that inherit their layout direction. + * Overrides must also call the superclass implementation at the start of their implementation. + * + * @hide */ - void resetLayoutDirectionResolution() { + protected void resetLayoutDirectionResolution() { // Reset the current View resolution mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED; } @@ -11963,7 +11983,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; - if (mLayoutParams != null) { + if (mLayoutParams != null && mParent != null) { mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); } @@ -12990,6 +13010,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * {@link #TEXT_DIRECTION_INHERIT}, * {@link #TEXT_DIRECTION_FIRST_STRONG} * {@link #TEXT_DIRECTION_ANY_RTL}, + * {@link #TEXT_DIRECTION_CHAR_COUNT}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * @@ -13007,6 +13028,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * {@link #TEXT_DIRECTION_INHERIT}, * {@link #TEXT_DIRECTION_FIRST_STRONG} * {@link #TEXT_DIRECTION_ANY_RTL}, + * {@link #TEXT_DIRECTION_CHAR_COUNT}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java index 2b692f3..d70c798 100644 --- a/core/java/android/view/ViewAncestor.java +++ b/core/java/android/view/ViewAncestor.java @@ -4637,13 +4637,18 @@ public final class ViewAncestor extends Handler implements ViewParent, public void run() { if (mView != null) { - // Send the event directly since we do not want to append the - // source text because this is the text for the entire window - // and we just want to notify that the content has changed. - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - mView.onInitializeAccessibilityEvent(event); - AccessibilityManager.getInstance(mView.mContext).sendAccessibilityEvent(event); + // Check again for accessibility state since this is executed delayed. + AccessibilityManager accessibilityManager = + AccessibilityManager.getInstance(mView.mContext); + if (accessibilityManager.isEnabled()) { + // Send the event directly since we do not want to append the + // source text because this is the text for the entire window + // and we just want to notify that the content has changed. + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + mView.onInitializeAccessibilityEvent(event); + accessibilityManager.sendAccessibilityEvent(event); + } mIsPending = false; } } diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index f3a5050..dbcbd6e 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -19,6 +19,8 @@ package android.view; import android.app.AppGlobals; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.RemoteException; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.SparseArray; @@ -219,6 +221,9 @@ public class ViewConfiguration { private final int mOverscrollDistance; private final int mOverflingDistance; + private boolean sHasPermanentMenuKey; + private boolean sHasPermanentMenuKeySet; + private static final SparseArray<ViewConfiguration> sConfigurations = new SparseArray<ViewConfiguration>(2); @@ -254,11 +259,12 @@ public class ViewConfiguration { * @see android.util.DisplayMetrics */ private ViewConfiguration(Context context) { - final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final Resources res = context.getResources(); + final DisplayMetrics metrics = res.getDisplayMetrics(); + final Configuration config = res.getConfiguration(); final float density = metrics.density; final float sizeAndDensity; - if (context.getResources().getConfiguration().isLayoutSizeAtLeast( - Configuration.SCREENLAYOUT_SIZE_XLARGE)) { + if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_XLARGE)) { sizeAndDensity = density * 1.5f; } else { sizeAndDensity = density; @@ -280,6 +286,17 @@ public class ViewConfiguration { mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f); mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f); + + if (!sHasPermanentMenuKeySet) { + IWindowManager wm = Display.getWindowManager(); + try { + sHasPermanentMenuKey = wm.canStatusBarHide() && !res.getBoolean( + com.android.internal.R.bool.config_showNavigationBar); + sHasPermanentMenuKeySet = true; + } catch (RemoteException ex) { + sHasPermanentMenuKey = false; + } + } } /** @@ -640,4 +657,20 @@ public class ViewConfiguration { public static float getScrollFriction() { return SCROLL_FRICTION; } + + /** + * Report if the device has a permanent menu key available to the user. + * + * <p>As of Android 3.0, devices may not have a permanent menu key available. + * Apps should use the action bar to present menu options to users. + * However, there are some apps where the action bar is inappropriate + * or undesirable. This method may be used to detect if a menu key is present. + * If not, applications should provide another on-screen affordance to access + * functionality. + * + * @return true if a permanent menu key is present, false otherwise. + */ + public boolean hasPermanentMenuKey() { + return sHasPermanentMenuKey; + } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 2a90dde..41412de 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1746,6 +1746,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final long now = SystemClock.uptimeMillis(); event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); + event.setSource(InputDevice.SOURCE_TOUCHSCREEN); syntheticEvent = true; } @@ -4998,12 +4999,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager viewAncestor.requestTransitionStart(transition); } - /** - * This method will be called when we need to reset the layout direction resolution flag - * - */ @Override - void resetLayoutDirectionResolution() { + protected void resetLayoutDirectionResolution() { super.resetLayoutDirectionResolution(); // Take care of resetting the children resolution too @@ -5030,15 +5027,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (mParent != null && mParent instanceof ViewGroup) { resolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection(); } else { - // We reached the top of the View hierarchy, so get the direction from - // the Locale - resolvedTextDirection = isLayoutDirectionRtl(Locale.getDefault()) ? - TEXT_DIRECTION_RTL : TEXT_DIRECTION_LTR; + // We reached the top of the View hierarchy, so set the text direction + // heuristic to "first strong" + resolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG; } break; // Pass down the hierarchy the following text direction values case TEXT_DIRECTION_FIRST_STRONG: case TEXT_DIRECTION_ANY_RTL: + case TEXT_DIRECTION_CHAR_COUNT: case TEXT_DIRECTION_LTR: case TEXT_DIRECTION_RTL: resolvedTextDirection = mTextDirection; diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 25f01a7..ac86769 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -25,40 +25,51 @@ import java.util.ArrayList; import java.util.List; /** + * <p> * This class represents accessibility events that are sent by the system when * something notable happens in the user interface. For example, when a * {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc. + * </p> * <p> * An accessibility event is fired by an individual view which populates the event with - * a record for its state and requests from its parent to send the event to interested - * parties. The parent can optionally add a record for itself before dispatching a similar - * request to its parent. A parent can also choose not to respect the request for sending - * an event. The accessibility event is sent by the topmost view in the view tree. - * Therefore, an {@link android.accessibilityservice.AccessibilityService} can explore - * all records in an accessibility event to obtain more information about the context - * in which the event was fired. + * data for its state and requests from its parent to send the event to interested + * parties. The parent can optionally add an {@link AccessibilityRecord} for itself before + * dispatching a similar request to its parent. A parent can also choose not to respect the + * request for sending an event. The accessibility event is sent by the topmost view in the + * view tree. Therefore, an {@link android.accessibilityservice.AccessibilityService} can + * explore all records in an accessibility event to obtain more information about the + * context in which the event was fired. + * </p> * <p> - * A client can add, remove, and modify records. The getters and setters for individual - * properties operate on the current record which can be explicitly set by the client. By - * default current is the first record. Thus, querying a record would require setting - * it as the current one and interacting with the property getters and setters. + * The main purpose of an accessibility event is to expose enough information for an + * {@link android.accessibilityservice.AccessibilityService} to provide meaningful feedback + * to the user. Sometimes however, an accessibility service may need more contextual + * information then the one in the event pay-load. In such cases the service can obtain + * the event source which is an {@link AccessibilityNodeInfo} (snapshot of a View state) + * which can be used for exploring the window content. Note that the privilege for accessing + * an event's source, thus the window content, has to be explicitly requested. For more + * details refer to {@link android.accessibilityservice.AccessibilityService}. If an + * accessibility service has not requested to retrieve the window content the event will + * not contain reference to its source. Also for events of type + * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available. + * </p> * <p> * This class represents various semantically different accessibility event - * types. Each event type has associated a set of related properties. In other + * types. Each event type has an associated set of related properties. In other * words, each event type is characterized via a subset of the properties exposed * by this class. For each event type there is a corresponding constant defined - * in this class. Since some event types are semantically close there are mask - * constants that group them together. Follows a specification of the event - * types and their associated properties: + * in this class. Follows a specification of the event types and their associated properties: + * </p> * <p> - * <b>VIEW TYPES</b> <br> + * <b>VIEW TYPES</b></br> + * </p> * <p> * <b>View clicked</b> - represents the event of clicking on a {@link android.view.View} - * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br> - * Type:{@link #TYPE_VIEW_CLICKED} <br> - * Properties:</br> + * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.</br> + * <em>Type:</em>{@link #TYPE_VIEW_CLICKED}</br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> @@ -67,13 +78,14 @@ import java.util.List; * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isChecked()} - Whether the source is checked.</li> * </ul> + * </p> * <p> * <b>View long clicked</b> - represents the event of long clicking on a {@link android.view.View} - * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br> - * Type:{@link #TYPE_VIEW_LONG_CLICKED} <br> - * Properties:</br> + * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc </br> + * <em>Type:</em>{@link #TYPE_VIEW_LONG_CLICKED}</br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> @@ -82,13 +94,14 @@ import java.util.List; * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isChecked()} - Whether the source is checked.</li> * </ul> + * </p> * <p> * <b>View selected</b> - represents the event of selecting an item usually in - * the context of an {@link android.widget.AdapterView}. <br> - * Type: {@link #TYPE_VIEW_SELECTED} <br> - * Properties:</br> + * the context of an {@link android.widget.AdapterView}.</br> + * <em>Type:</em> {@link #TYPE_VIEW_SELECTED}</br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> @@ -96,17 +109,17 @@ import java.util.List; * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isChecked()} - Whether the source is checked.</li> - * <li>{@link #getItemCount()} -The number of selectable items of the source.</li> + * <li>{@link #getItemCount()} - The number of selectable items of the source.</li> * <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li> * </ul> - * <p> + * </p> * <p> * <b>View focused</b> - represents the event of focusing a - * {@link android.view.View}. <br> - * Type: {@link #TYPE_VIEW_FOCUSED} <br> - * Properties:</br> + * {@link android.view.View}.</br> + * <em>Type:</em> {@link #TYPE_VIEW_FOCUSED}</br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> @@ -114,16 +127,17 @@ import java.util.List; * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isChecked()} - Whether the source is checked.</li> - * <li>{@link #getItemCount()} -The number of focusable items on the screen.</li> + * <li>{@link #getItemCount()} - The number of focusable items on the screen.</li> * <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li> * </ul> + * </p> * <p> * <b>View text changed</b> - represents the event of changing the text of an - * {@link android.widget.EditText}. <br> - * Type: {@link #TYPE_VIEW_TEXT_CHANGED} <br> - * Properties:</br> + * {@link android.widget.EditText}.</br> + * <em>Type:</em> {@link #TYPE_VIEW_TEXT_CHANGED}</br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> @@ -136,13 +150,14 @@ import java.util.List; * <li>{@link #getRemovedCount()} - The number of removed characters.</li> * <li>{@link #getBeforeText()} - The text of the source before the change.</li> * </ul> + * </p> * <p> * <b>View text selection changed</b> - represents the event of changing the text - * selection of an {@link android.widget.EditText}.<br> - * Type: {@link #TYPE_VIEW_TEXT_SELECTION_CHANGED} <br> - * Properties:</br> + * selection of an {@link android.widget.EditText}.</br> + * <em>Type:</em> {@link #TYPE_VIEW_TEXT_SELECTION_CHANGED} </br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> @@ -152,7 +167,8 @@ import java.util.List; * <li>{@link #getFromIndex()} - The selection start index.</li> * <li>{@link #getToIndex()} - The selection end index.</li> * <li>{@link #getItemCount()} - The length of the source text.</li> - * <ul> + * </ul> + * </p> * <p> * <b>View scrolled</b> - represents the event of scrolling a view. If * the source is a descendant of {@link android.widget.AdapterView} the @@ -161,11 +177,11 @@ import java.util.List; * is unaware if its pixel size since its adapter is responsible for * creating views. In all other cases the scroll is reported as the current * scroll on the X and Y axis respectively plus the height of the source in - * pixels.<br> - * Type: {@link #TYPE_VIEW_SCROLLED} <br> - * Properties:</br> + * pixels.</br> + * <em>Type:</em> {@link #TYPE_VIEW_SCROLLED}</br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> @@ -181,41 +197,49 @@ import java.util.List; * (for descendants of AdapterView).</li> * <li>{@link #getItemCount()} - The total items of the source (for descendants of AdapterView) * or the height of the source in pixels (all other cases).</li> - * <ul> - * <p> - * <b>TRANSITION TYPES</b> <br> + * </ul> + * </p> * <p> + * <b>TRANSITION TYPES</b></br> + * </p> * <b>Window state changed</b> - represents the event of opening a * {@link android.widget.PopupWindow}, {@link android.view.Menu}, - * {@link android.app.Dialog}, etc. <br> - * Type: {@link #TYPE_WINDOW_STATE_CHANGED} <br> - * Properties:</br> + * {@link android.app.Dialog}, etc.</br> + * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> * <li>{@link #getText()} - The text of the source.</li> * </ul> + * </p> * <p> * <b>Window content changed</b> - represents the event of change in the * content of a window. This change can be adding/removing view, changing - * a view size, etc.<br> - * Type: {@link #TYPE_WINDOW_CONTENT_CHANGED} <br> - * Properties:</br> + * a view size, etc.</br> + * <p> + * <strong>Note:</strong> This event is fired only for the window source of the + * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED}) + * and its purpose is to notify clients that the content of the user interaction + * window has changed. + * </p> + * <em>Type:</em> {@link #TYPE_WINDOW_CONTENT_CHANGED}</br> + * <em>Properties:</em></br> * <ul> - * <li>{@link #getSource()} - The source info (for registered clients).</li> * + * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <ul> + * </ul> * <p> - * <b>NOTIFICATION TYPES</b> <br> + * <b>NOTIFICATION TYPES</b></br> * <p> - * <b>Notification state changed</b> - represents the event showing/hiding + * <b>Notification state changed</b> - represents the event showing * {@link android.app.Notification}. - * Type: {@link #TYPE_NOTIFICATION_STATE_CHANGED} <br> - * Properties:</br> + * <em>Type:</em> {@link #TYPE_NOTIFICATION_STATE_CHANGED}</br> + * <em>Properties:</em></br> * <ul> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> @@ -223,15 +247,17 @@ import java.util.List; * <li>{@link #getText()} - The text of the source.</li> * <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}.</li> * </ul> + * </p> * <p> * <b>Security note</b> * <p> - * Since an event contains the text of its source privacy can be compromised by leaking of + * Since an event contains the text of its source privacy can be compromised by leaking * sensitive information such as passwords. To address this issue any event fired in response * to manipulation of a PASSWORD field does NOT CONTAIN the text of the password. * * @see android.view.accessibility.AccessibilityManager * @see android.accessibilityservice.AccessibilityService + * @see AccessibilityNodeInfo */ public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable { private static final boolean DEBUG = false; @@ -285,13 +311,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010; /** - * Represents the event of opening/closing a {@link android.widget.PopupWindow}, + * Represents the event of opening a {@link android.widget.PopupWindow}, * {@link android.view.Menu}, {@link android.app.Dialog}, etc. */ public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020; /** - * Represents the event showing/hiding a {@link android.app.Notification}. + * Represents the event showing a {@link android.app.Notification}. */ public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040; @@ -340,6 +366,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @see #TYPE_VIEW_TEXT_CHANGED * @see #TYPE_WINDOW_STATE_CHANGED * @see #TYPE_NOTIFICATION_STATE_CHANGED + * @see #TYPE_VIEW_HOVER_ENTER + * @see #TYPE_VIEW_HOVER_EXIT + * @see #TYPE_TOUCH_EXPLORATION_GESTURE_START + * @see #TYPE_TOUCH_EXPLORATION_GESTURE_END + * @see #TYPE_WINDOW_CONTENT_CHANGED + * @see #TYPE_VIEW_SCROLLED + * @see #TYPE_VIEW_TEXT_SELECTION_CHANGED */ public static final int TYPES_ALL_MASK = 0xFFFFFFFF; @@ -432,10 +465,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** - * Gets the records at a given index. + * Gets the record at a given index. * * @param index The index. - * @return The records at the specified index. + * @return The record at the specified index. */ public AccessibilityRecord getRecord(int index) { return mRecords.get(index); @@ -506,7 +539,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Returns a cached instance if such is available or a new one is - * instantiated with type property set. + * instantiated with its type property set. * * @param eventType The event type. * @return An instance. @@ -519,7 +552,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Returns a cached instance if such is available or a new one is - * instantiated with type property set. + * initialized with from the given <code>event</code>. * * @param event The other event. * @return An instance. @@ -559,9 +592,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** - * Return an instance back to be reused. + * Recycles an instance back to be reused. * <p> - * <b>Note: You must not touch the object after calling this function.</b> + * <b>Note: You must not touch the object after calling this function.</b> + * </p> * * @throws IllegalStateException If the event is already recycled. */ @@ -714,7 +748,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("; EventType: ").append(eventTypeToString(mEventType)); + builder.append("EventType: ").append(eventTypeToString(mEventType)); builder.append("; EventTime: ").append(mEventTime); builder.append("; PackageName: ").append(mPackageName); builder.append(super.toString()); @@ -758,11 +792,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * Returns the string representation of an event type. For example, * {@link #TYPE_VIEW_CLICKED} is represented by the string TYPE_VIEW_CLICKED. * - * @param feedbackType The event type + * @param eventType The event type * @return The string representation. */ - public static String eventTypeToString(int feedbackType) { - switch (feedbackType) { + public static String eventTypeToString(int eventType) { + switch (eventType) { case TYPE_VIEW_CLICKED: return "TYPE_VIEW_CLICKED"; case TYPE_VIEW_LONG_CLICKED: diff --git a/core/java/android/view/accessibility/AccessibilityEventSource.java b/core/java/android/view/accessibility/AccessibilityEventSource.java index 3d70959..f11880b 100644 --- a/core/java/android/view/accessibility/AccessibilityEventSource.java +++ b/core/java/android/view/accessibility/AccessibilityEventSource.java @@ -24,11 +24,12 @@ public interface AccessibilityEventSource { /** * Handles the request for sending an {@link AccessibilityEvent} given * the event type. The method must first check if accessibility is on - * via calling {@link AccessibilityManager#isEnabled()}, obtain - * an {@link AccessibilityEvent} from the event pool through calling - * {@link AccessibilityEvent#obtain(int)}, populate the event, and - * send it for dispatch via calling - * {@link AccessibilityManager#sendAccessibilityEvent(AccessibilityEvent)}. + * via calling {@link AccessibilityManager#isEnabled() AccessibilityManager.isEnabled()}, + * obtain an {@link AccessibilityEvent} from the event pool through calling + * {@link AccessibilityEvent#obtain(int) AccessibilityEvent.obtain(int)}, populate the + * event, and send it for dispatch via calling + * {@link AccessibilityManager#sendAccessibilityEvent(AccessibilityEvent) + * AccessibilityManager.sendAccessibilityEvent(AccessibilityEvent)}. * * @see AccessibilityEvent * @see AccessibilityManager @@ -41,7 +42,8 @@ public interface AccessibilityEventSource { * Handles the request for sending an {@link AccessibilityEvent}. The * method does not guarantee to check if accessibility is on before * sending the event for dispatch. It is responsibility of the caller - * to do the check via calling {@link AccessibilityManager#isEnabled()}. + * to do the check via calling {@link AccessibilityManager#isEnabled() + * AccessibilityManager.isEnabled()}. * * @see AccessibilityEvent * @see AccessibilityManager diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index eece64a..314b7ca 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -37,16 +37,30 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** - * System level service that serves as an event dispatch for {@link AccessibilityEvent}s. - * Such events are generated when something notable happens in the user interface, + * System level service that serves as an event dispatch for {@link AccessibilityEvent}s, + * and provides facilities for querying the accessibility state of the system. + * Accessibility events are generated when something notable happens in the user interface, * for example an {@link android.app.Activity} starts, the focus or selection of a * {@link android.view.View} changes etc. Parties interested in handling accessibility * events implement and register an accessibility service which extends * {@link android.accessibilityservice.AccessibilityService}. + * <p> + * To obtain a handle to the accessibility manager do the following: + * </p> + * <p> + * <code> + * <pre> + * AccessibilityManager accessibilityManager = + * (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + * </pre> + * </code> + * </p> * * @see AccessibilityEvent + * @see AccessibilityNodeInfo * @see android.accessibilityservice.AccessibilityService - * @see android.content.Context#getSystemService + * @see Context#getSystemService + * @see Context#ACCESSIBILITY_SERVICE */ public final class AccessibilityManager { private static final boolean DEBUG = false; @@ -72,10 +86,11 @@ public final class AccessibilityManager { * Listener for the accessibility state. */ public interface AccessibilityStateChangeListener { + /** * Called back on change in the accessibility state. * - * @param enabled + * @param enabled Whether accessibility is enabled. */ public void onAccessibilityStateChanged(boolean enabled); } @@ -142,9 +157,9 @@ public final class AccessibilityManager { } /** - * Returns if the {@link AccessibilityManager} is enabled. + * Returns if the accessibility in the system is enabled. * - * @return True if this {@link AccessibilityManager} is enabled, false otherwise. + * @return True if accessibility is enabled, false otherwise. */ public boolean isEnabled() { synchronized (mHandler) { @@ -161,17 +176,15 @@ public final class AccessibilityManager { * @hide */ public IAccessibilityManagerClient getClient() { - return (IAccessibilityManagerClient) mClient.asBinder(); + return (IAccessibilityManagerClient) mClient.asBinder(); } /** - * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not - * enabled the call is a NOOP. + * Sends an {@link AccessibilityEvent}. * - * @param event The {@link AccessibilityEvent}. + * @param event The event to send. * - * @throws IllegalStateException if a client tries to send an {@link AccessibilityEvent} - * while accessibility is not enabled. + * @throws IllegalStateException if accessibility is not enabled. */ public void sendAccessibilityEvent(AccessibilityEvent event) { if (!mIsEnabled) { @@ -199,7 +212,7 @@ public final class AccessibilityManager { } /** - * Requests interruption of the accessibility feedback from all accessibility services. + * Requests feedback interruption from all accessibility services. */ public void interrupt() { if (!mIsEnabled) { @@ -256,13 +269,20 @@ public final class AccessibilityManager { * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services * for a given feedback type. * - * @param feedbackType The feedback type (can be bitwise or of multiple types). + * @param feedbackTypeFlags The feedback type flags. * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. + * + * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE + * @see AccessibilityServiceInfo#FEEDBACK_GENERIC + * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC + * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN + * @see AccessibilityServiceInfo#FEEDBACK_VISUAL */ - public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) { + public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( + int feedbackTypeFlags) { List<AccessibilityServiceInfo> services = null; try { - services = mService.getEnabledAccessibilityServiceList(feedbackType); + services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags); if (DEBUG) { Log.i(LOG_TAG, "Installed AccessibilityServices " + services); } @@ -273,7 +293,8 @@ public final class AccessibilityManager { } /** - * Registers an {@link AccessibilityStateChangeListener}. + * Registers an {@link AccessibilityStateChangeListener} for changes in + * the global accessibility state of the system. * * @param listener The listener. * @return True if successfully registered. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index dbbe7be..031c6ae 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -22,7 +22,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.text.TextUtils; -import android.util.SparseArray; import android.util.SparseIntArray; import android.view.View; @@ -30,12 +29,26 @@ import java.util.Collections; import java.util.List; /** - * This class represents a node of the screen content. From the point of - * view of an accessibility service the screen content is presented as tree - * of accessibility nodes. + * This class represents a node of the window content as well as actions that + * can be requested from its source. From the point of view of an + * {@link android.accessibilityservice.AccessibilityService} a window content is + * presented as tree of accessibility node info which may or may not map one-to-one + * to the view hierarchy. In other words, a custom view is free to report itself as + * a tree of accessibility node info. + * </p> + * <p> + * Once an accessibility node info is delivered to an accessibility service it is + * made immutable and calling a state mutation method generates an error. + * </p> + * <p> + * Please refer to {@link android.accessibilityservice.AccessibilityService} for + * details about how to obtain a handle to window content as a tree of accessibility + * node info as well as familiarizing with the security model. + * </p> * - * TODO(svertoslavganov): Update the documentation, add sample, and describe - * the security policy. + * @see android.accessibilityservice.AccessibilityService + * @see AccessibilityEvent + * @see AccessibilityManager */ public class AccessibilityNodeInfo implements Parcelable { @@ -85,9 +98,6 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int PROPERTY_SCROLLABLE = 0x00000200; - // Readable representations - lazily initialized. - private static SparseArray<String> sActionSymbolicNames; - // Housekeeping. private static final int MAX_POOL_SIZE = 50; private static final Object sPoolLock = new Object(); @@ -154,12 +164,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Get the child at given index. * <p> - * <strong> - * It is a client responsibility to recycle the received info by - * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating - * of multiple instances. - * </strong> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling {@link AccessibilityNodeInfo#recycle()} + * to avoid creating of multiple instances. * </p> + * * @param index The child index. * @return The child node. * @@ -184,9 +193,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Adds a child. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param child The child. * * @throws IllegalStateException If called from an AccessibilityService. @@ -215,9 +226,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Adds an action that can be performed on the node. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param action The action. * * @throws IllegalStateException If called from an AccessibilityService. @@ -230,9 +243,10 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Performs an action on the node. * <p> - * Note: An action can be performed only if the request is made + * <strong>Note:</strong> An action can be performed only if the request is made * from an {@link android.accessibilityservice.AccessibilityService}. * </p> + * * @param action The action to perform. * @return True if the action was performed. * @@ -256,6 +270,11 @@ public class AccessibilityNodeInfo implements Parcelable { * Finds {@link AccessibilityNodeInfo}s by text. The match is case * insensitive containment. The search is relative to this info i.e. * this info is the root of the traversed tree. + * <p> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling {@link AccessibilityNodeInfo#recycle()} + * to avoid creating of multiple instances. + * </p> * * @param text The searched text. * @return A list of node info. @@ -277,12 +296,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Gets the unique id identifying this node's parent. * <p> - * <strong> - * It is a client responsibility to recycle the received info by - * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating - * of multiple instances. - * </strong> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling {@link AccessibilityNodeInfo#recycle()} + * to avoid creating of multiple instances. * </p> + * * @return The node's patent id. */ public AccessibilityNodeInfo getParent() { @@ -302,9 +320,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets the parent. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param parent The parent. * * @throws IllegalStateException If called from an AccessibilityService. @@ -327,9 +347,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets the node bounds in parent coordinates. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param bounds The node bounds. * * @throws IllegalStateException If called from an AccessibilityService. @@ -352,9 +374,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets the node bounds in screen coordinates. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param bounds The node bounds. * * @throws IllegalStateException If called from an AccessibilityService. @@ -376,9 +400,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is checkable. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param checkable True if the node is checkable. * * @throws IllegalStateException If called from an AccessibilityService. @@ -399,9 +425,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is checked. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param checked True if the node is checked. * * @throws IllegalStateException If called from an AccessibilityService. @@ -422,9 +450,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is focusable. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param focusable True if the node is focusable. * * @throws IllegalStateException If called from an AccessibilityService. @@ -445,9 +475,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is focused. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param focused True if the node is focused. * * @throws IllegalStateException If called from an AccessibilityService. @@ -468,9 +500,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is selected. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param selected True if the node is selected. * * @throws IllegalStateException If called from an AccessibilityService. @@ -491,9 +525,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is clickable. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param clickable True if the node is clickable. * * @throws IllegalStateException If called from an AccessibilityService. @@ -514,9 +550,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is long clickable. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param longClickable True if the node is long clickable. * * @throws IllegalStateException If called from an AccessibilityService. @@ -537,9 +575,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is enabled. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param enabled True if the node is enabled. * * @throws IllegalStateException If called from an AccessibilityService. @@ -560,9 +600,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets whether this node is a password. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param password True if the node is a password. * * @throws IllegalStateException If called from an AccessibilityService. @@ -582,6 +624,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets if the node is scrollable. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> * * @param scrollable True if the node is scrollable, false otherwise. * @@ -604,9 +651,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets the package this node comes from. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param packageName The package name. * * @throws IllegalStateException If called from an AccessibilityService. @@ -628,9 +677,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets the class this node comes from. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param className The class name. * * @throws IllegalStateException If called from an AccessibilityService. @@ -652,9 +703,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets the text of this node. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param text The text. * * @throws IllegalStateException If called from an AccessibilityService. @@ -676,9 +729,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Sets the content description of this node. * <p> - * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. * </p> + * * @param contentDescription The content description. * * @throws IllegalStateException If called from an AccessibilityService. @@ -820,7 +875,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Return an instance back to be reused. * <p> - * <b>Note: You must not touch the object after calling this function.</b> + * <strong>Note:</strong> You must not touch the object after calling this function. * * @throws IllegalStateException If the info is already recycled. */ @@ -842,8 +897,8 @@ public class AccessibilityNodeInfo implements Parcelable { /** * {@inheritDoc} * <p> - * <b>Note: After the instance is written to a parcel it is recycled. - * You must not touch the object after calling this function.</b> + * <strong>Note:</strong> After the instance is written to a parcel it + * is recycled. You must not touch the object after calling this function. * </p> */ public void writeToParcel(Parcel parcel, int flags) { @@ -885,7 +940,7 @@ public class AccessibilityNodeInfo implements Parcelable { TextUtils.writeToParcel(mContentDescription, parcel, flags); // Since instances of this class are fetched via synchronous i.e. blocking - // calls in IPCs and we always recycle as soon as the instance is marshaled. + // calls in IPCs we always recycle as soon as the instance is marshaled. recycle(); } @@ -957,15 +1012,18 @@ public class AccessibilityNodeInfo implements Parcelable { * @return The symbolic name. */ private static String getActionSymbolicName(int action) { - SparseArray<String> actionSymbolicNames = sActionSymbolicNames; - if (actionSymbolicNames == null) { - actionSymbolicNames = sActionSymbolicNames = new SparseArray<String>(); - actionSymbolicNames.put(ACTION_FOCUS, "ACTION_FOCUS"); - actionSymbolicNames.put(ACTION_CLEAR_FOCUS, "ACTION_UNFOCUS"); - actionSymbolicNames.put(ACTION_SELECT, "ACTION_SELECT"); - actionSymbolicNames.put(ACTION_CLEAR_SELECTION, "ACTION_UNSELECT"); + switch (action) { + case ACTION_FOCUS: + return "ACTION_FOCUS"; + case ACTION_CLEAR_FOCUS: + return "ACTION_CLEAR_FOCUS"; + case ACTION_SELECT: + return "ACTION_SELECT"; + case ACTION_CLEAR_SELECTION: + return "ACTION_CLEAR_SELECTION"; + default: + throw new IllegalArgumentException("Unknown action: " + action); } - return actionSymbolicNames.get(action); } private boolean canPerformRequestOverConnection(int accessibilityViewId) { diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index b9815c5..f4d5e89 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -25,12 +25,28 @@ import java.util.ArrayList; import java.util.List; /** - * Represents a record in an accessibility event. This class encapsulates - * the information for a {@link android.view.View}. Note that not all properties - * are applicable to all view types. For detailed information please refer to - * {@link AccessibilityEvent}. + * Represents a record in an {@link AccessibilityEvent} and contains information + * about state change of its source {@link android.view.View}. When a view fires + * an accessibility event it requests from its parent to dispatch the + * constructed event. The parent may optionally append a record for itself + * for providing more context to + * {@link android.accessibilityservice.AccessibilityService}s. Hence, + * accessibility services can facilitate additional accessibility records + * to enhance feedback. + * </p> + * <p> + * Once the accessibility event containing a record is dispatched the record is + * made immutable and calling a state mutation method generates an error. + * </p> + * <p> + * <strong>Note:</strong> Not all properties are applicable to all accessibility + * event types. For detailed information please refer to {@link AccessibilityEvent}. + * </p> * * @see AccessibilityEvent + * @see AccessibilityManager + * @see android.accessibilityservice.AccessibilityService + * @see AccessibilityNodeInfo */ public class AccessibilityRecord { @@ -79,32 +95,6 @@ public class AccessibilityRecord { } /** - * Initialize this record from another one. - * - * @param record The to initialize from. - */ - void init(AccessibilityRecord record) { - mSealed = record.mSealed; - mBooleanProperties = record.mBooleanProperties; - mCurrentItemIndex = record.mCurrentItemIndex; - mItemCount = record.mItemCount; - mFromIndex = record.mFromIndex; - mToIndex = record.mToIndex; - mScrollX = record.mScrollX; - mScrollY = record.mScrollY; - mAddedCount = record.mAddedCount; - mRemovedCount = record.mRemovedCount; - mClassName = record.mClassName; - mContentDescription = record.mContentDescription; - mBeforeText = record.mBeforeText; - mParcelableData = record.mParcelableData; - mText.addAll(record.mText); - mSourceWindowId = record.mSourceWindowId; - mSourceViewId = record.mSourceViewId; - mConnection = record.mConnection; - } - - /** * Sets the event source. * * @param source The source. @@ -125,13 +115,12 @@ public class AccessibilityRecord { /** * Gets the {@link AccessibilityNodeInfo} of the event source. * <p> - * <strong> - * It is a client responsibility to recycle the received info by - * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating - * of multiple instances. - * </strong> + * <strong>Note:</strong> It is a client responsibility to recycle the received info + * by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()} + * to avoid creating of multiple instances. + * * </p> - * @return The info. + * @return The info of the source. */ public AccessibilityNodeInfo getSource() { enforceSealed(); @@ -641,7 +630,7 @@ public class AccessibilityRecord { /** * Return an instance back to be reused. * <p> - * <b>Note: You must not touch the object after calling this function.</b> + * <strong>Note:</strong> You must not touch the object after calling this function. * * @throws IllegalStateException If the record is already recycled. */ @@ -661,6 +650,32 @@ public class AccessibilityRecord { } /** + * Initialize this record from another one. + * + * @param record The to initialize from. + */ + void init(AccessibilityRecord record) { + mSealed = record.mSealed; + mBooleanProperties = record.mBooleanProperties; + mCurrentItemIndex = record.mCurrentItemIndex; + mItemCount = record.mItemCount; + mFromIndex = record.mFromIndex; + mToIndex = record.mToIndex; + mScrollX = record.mScrollX; + mScrollY = record.mScrollY; + mAddedCount = record.mAddedCount; + mRemovedCount = record.mRemovedCount; + mClassName = record.mClassName; + mContentDescription = record.mContentDescription; + mBeforeText = record.mBeforeText; + mParcelableData = record.mParcelableData; + mText.addAll(record.mText); + mSourceWindowId = record.mSourceWindowId; + mSourceViewId = record.mSourceViewId; + mConnection = record.mConnection; + } + + /** * Clears the state of this instance. */ void clear() { diff --git a/core/java/android/view/accessibility/package.html b/core/java/android/view/accessibility/package.html new file mode 100644 index 0000000..4afafd3 --- /dev/null +++ b/core/java/android/view/accessibility/package.html @@ -0,0 +1,39 @@ +<html> +<body> +<p> + The classes in this package are used to represent screen content and changes to it + as well as APIs for querying the global accessibility state of the system. +</p> +<p> + {@link android.view.accessibility.AccessibilityEvent}s are sent by the system when + something notable happens in the user interface. For example, when a + {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc. +</p> +<p> + {@link android.view.accessibility.AccessibilityRecord} contains information + about state change of its source {@link android.view.View}. When a view fires + an accessibility event it requests from its parent to dispatch the + constructed event. The parent may optionally append a record for itself for + providing more context to {@link android.accessibilityservice.AccessibilityService}s. + Hence, accessibility services can facilitate additional accessibility records + to enhance feedback. +</p> +<p> + {@link android.view.accessibility.AccessibilityNodeInfo} represents a node of the + window content as well as actions that can be requested from its source. From the point + of view of an {@link android.accessibilityservice.AccessibilityService} a window content is + presented as tree of accessibility node info which may or may not map one-to-one + to the view hierarchy. In other words, a custom view is free to report itself as + a tree of accessibility node info. +</p> +<p> + {@link android.view.accessibility.AccessibilityManager} is a system level service that + serves as an event dispatch for {@link android.view.accessibility.AccessibilityEvent}s, + and provides facilities for querying the accessibility state of the system. Accessibility + events are generated when something notable happens in the user interface, for example an + {@link android.app.Activity} starts, the focus or selection of a {@link android.view.View} + changes etc. Parties interested in handling accessibility events implement and register an + accessibility service which extends {@link android.accessibilityservice.AccessibilityService}. +</p> +</body> +</html> diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 11ab0d7..0ea27a0 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -107,6 +107,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView // After we return from this we can't use the surface any more. // The current Video View will be destroy when we play a new video. pauseAndDispatch(mProxy); + mPlayer.release(); mSurfaceHolder = null; if (mMediaController != null) { mMediaController.hide(); @@ -226,6 +227,10 @@ public class HTML5VideoFullScreen extends HTML5VideoView mProxy.getWebView().getViewManager().showAll(); mProxy = null; + + // Don't show the controller after exiting the full screen. + mMediaController = null; + mCurrentState = STATE_RELEASED; } }; diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java index 5983a44..67660b8 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -34,6 +34,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { static final int STATE_NOTPREPARED = 1; static final int STATE_PREPARED = 2; static final int STATE_PLAYING = 3; + static final int STATE_RELEASED = 4; protected int mCurrentState; protected HTML5VideoViewProxy mProxy; @@ -84,7 +85,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } public void pause() { - if (mCurrentState == STATE_PREPARED && mPlayer.isPlaying()) { + if (isPlaying()) { mPlayer.pause(); } else if (mCurrentState == STATE_NOTPREPARED) { mPauseDuringPreparing = true; @@ -120,11 +121,18 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } public boolean isPlaying() { - return mPlayer.isPlaying(); + if (mCurrentState == STATE_PREPARED) { + return mPlayer.isPlaying(); + } else { + return false; + } } public void release() { - mPlayer.release(); + if (mCurrentState != STATE_RELEASED) { + mPlayer.release(); + } + mCurrentState = STATE_RELEASED; } public void stopPlayback() { @@ -228,7 +236,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { public int getCurrentState() { - if (mPlayer.isPlaying()) { + if (isPlaying()) { return STATE_PLAYING; } else { return mCurrentState; diff --git a/core/java/android/webkit/L10nUtils.java b/core/java/android/webkit/L10nUtils.java index 5b4fb1d..4c42cde 100644 --- a/core/java/android/webkit/L10nUtils.java +++ b/core/java/android/webkit/L10nUtils.java @@ -70,7 +70,11 @@ public class L10nUtils { com.android.internal.R.string.autofill_expiration_month_re, // IDS_AUTOFILL_EXPIRATION_MONTH_RE com.android.internal.R.string.autofill_expiration_date_re, // IDS_AUTOFILL_EXPIRATION_DATE_RE com.android.internal.R.string.autofill_card_ignored_re, // IDS_AUTOFILL_CARD_IGNORED_RE - com.android.internal.R.string.autofill_fax_re // IDS_AUTOFILL_FAX_RE + com.android.internal.R.string.autofill_fax_re, // IDS_AUTOFILL_FAX_RE + com.android.internal.R.string.autofill_country_code_re, // IDS_AUTOFILL_COUNTRY_CODE_RE + com.android.internal.R.string.autofill_area_code_notext_re, // IDS_AUTOFILL_AREA_CODE_NOTEXT_RE + com.android.internal.R.string.autofill_phone_prefix_separator_re, // IDS_AUTOFILL_PHONE_PREFIX_SEPARATOR_RE + com.android.internal.R.string.autofill_phone_suffix_separator_re // IDS_AUTOFILL_PHONE_SUFFIX_SEPARATOR_RE }; private static Context mApplicationContext; diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 7e41d36..4f97066 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -1950,6 +1950,7 @@ public final class WebViewCore { // mInitialViewState is set by didFirstLayout() and then reset in the // next webkitDraw after passing the state to the UI thread. private ViewState mInitialViewState = null; + private boolean mFirstLayoutForNonStandardLoad; static class ViewState { float mMinScale; @@ -1977,6 +1978,7 @@ public final class WebViewCore { int mMinPrefWidth; // only non-null if it is for the first picture set after the first layout ViewState mViewState; + boolean mFirstLayoutForNonStandardLoad; boolean mFocusSizeChanged; } @@ -2026,6 +2028,10 @@ public final class WebViewCore { draw.mViewState = mInitialViewState; mInitialViewState = null; } + if (mFirstLayoutForNonStandardLoad) { + draw.mFirstLayoutForNonStandardLoad = true; + mFirstLayoutForNonStandardLoad = false; + } if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID"); Message.obtain(mWebView.mPrivateHandler, WebView.NEW_PICTURE_MSG_ID, draw).sendToTarget(); @@ -2312,6 +2318,8 @@ public final class WebViewCore { // if mViewportWidth is 0, it means device-width, always update. if (mViewportWidth != 0 && !updateViewState) { + // For non standard load, since updateViewState will be false. + mFirstLayoutForNonStandardLoad = true; ViewState viewState = new ViewState(); viewState.mMinScale = mViewportMinimumScale / 100.0f; viewState.mMaxScale = mViewportMaximumScale / 100.0f; @@ -2471,9 +2479,10 @@ public final class WebViewCore { // called by JNI private void restoreScale(float scale, float textWrapScale) { if (mBrowserFrame.firstLayoutDone() == false) { - mRestoredScale = scale; + final float defaultScale = mWebView.getDefaultZoomScale(); + mRestoredScale = (scale <= 0.0) ? defaultScale : scale; if (mSettings.getUseWideViewPort()) { - mRestoredTextWrapScale = textWrapScale; + mRestoredTextWrapScale = (textWrapScale <= 0.0) ? defaultScale : textWrapScale; } } } diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 6c6974b..7d43e94 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -1024,6 +1024,11 @@ class ZoomManager { } else { mInZoomOverview = !scaleHasDiff; } + if (drawData.mFirstLayoutForNonStandardLoad && settings.getLoadWithOverviewMode()) { + // Set mInitialZoomOverview in case this is the first picture for non standard load, + // so next new picture could be forced into overview mode if it's true. + mInitialZoomOverview = mInZoomOverview; + } } /** diff --git a/core/java/android/widget/ActivityChooserModel.java b/core/java/android/widget/ActivityChooserModel.java new file mode 100644 index 0000000..83f80ff --- /dev/null +++ b/core/java/android/widget/ActivityChooserModel.java @@ -0,0 +1,1115 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.database.DataSetObservable; +import android.database.DataSetObserver; +import android.os.AsyncTask; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; +import android.util.Xml; + +import com.android.internal.content.PackageMonitor; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * <p> + * This class represents a data model for choosing a component for handing a + * given {@link Intent}. The model is responsible for querying the system for + * activities that can handle the given intent and order found activities + * based on historical data of previous choices. The historical data is stored + * in an application private file. If a client does not want to have persistent + * choice history the file can be omitted, thus the activities will be ordered + * based on historical usage for the current session. + * <p> + * </p> + * For each backing history file there is a singleton instance of this class. Thus, + * several clients that specify the same history file will share the same model. Note + * that if multiple clients are sharing the same model they should implement semantically + * equivalent functionality since setting the model intent will change the found + * activities and they may be inconsistent with the functionality of some of the clients. + * For example, choosing a share activity can be implemented by a single backing + * model and two different views for performing the selection. If however, one of the + * views is used for sharing but the other for importing, for example, then each + * view should be backed by a separate model. + * </p> + * <p> + * The way clients interact with this class is as follows: + * </p> + * <p> + * <pre> + * <code> + * // Get a model and set it to a couple of clients with semantically similar function. + * ActivityChooserModel dataModel = + * ActivityChooserModel.get(context, "task_specific_history_file_name.xml"); + * + * ActivityChooserModelClient modelClient1 = getActivityChooserModelClient1(); + * modelClient1.setActivityChooserModel(dataModel); + * + * ActivityChooserModelClient modelClient2 = getActivityChooserModelClient2(); + * modelClient2.setActivityChooserModel(dataModel); + * + * // Set an intent to choose a an activity for. + * dataModel.setIntent(intent); + * <pre> + * <code> + * </p> + * <p> + * <strong>Note:</strong> This class is thread safe. + * </p> + * + * @hide + */ +public class ActivityChooserModel extends DataSetObservable { + + /** + * Client that utilizes an {@link ActivityChooserModel}. + */ + public interface ActivityChooserModelClient { + + /** + * Sets the {@link ActivityChooserModel}. + * + * @param dataModel The model. + */ + public void setActivityChooserModel(ActivityChooserModel dataModel); + } + + /** + * Defines a sorter that is responsible for sorting the activities + * based on the provided historical choices and an intent. + */ + public interface ActivitySorter { + + /** + * Sorts the <code>activities</code> in descending order of relevance + * based on previous history and an intent. + * + * @param intent The {@link Intent}. + * @param activities Activities to be sorted. + * @param historicalRecords Historical records. + */ + // This cannot be done by a simple comparator since an Activity weight + // is computed from history. Note that Activity implements Comparable. + public void sort(Intent intent, List<Activity> activities, + List<HistoricalRecord> historicalRecords); + } + + /** + * Flag for selecting debug mode. + */ + private static final boolean DEBUG = false; + + /** + * Tag used for logging. + */ + private static final String LOG_TAG = ActivityChooserModel.class.getSimpleName(); + + /** + * The root tag in the history file. + */ + private static final String TAG_HISTORICAL_RECORDS = "historical-records"; + + /** + * The tag for a record in the history file. + */ + private static final String TAG_HISTORICAL_RECORD = "historical-record"; + + /** + * Attribute for the activity. + */ + private static final String ATTRIBUTE_ACTIVITY = "activity"; + + /** + * Attribute for the choice time. + */ + private static final String ATTRIBUTE_TIME = "time"; + + /** + * Attribute for the choice weight. + */ + private static final String ATTRIBUTE_WEIGHT = "weight"; + + /** + * The default name of the choice history file. + */ + public static final String DEFAULT_HISTORY_FILE_NAME = + "activity_choser_model_history.xml"; + + /** + * The default maximal length of the choice history. + */ + public static final int DEFAULT_HISTORY_MAX_LENGTH = 50; + + /** + * The amount with which to inflate a chosen activity when set as default. + */ + private static final int DEFAULT_ACTIVITY_INFLATION = 5; + + /** + * Default weight for a choice record. + */ + private static final float DEFAULT_HISTORICAL_RECORD_WEIGHT = 1.0f; + + /** + * The extension of the history file. + */ + private static final String HISTORY_FILE_EXTENSION = ".xml"; + + /** + * An invalid item index. + */ + private static final int INVALID_INDEX = -1; + + /** + * Lock to guard the model registry. + */ + private static final Object sRegistryLock = new Object(); + + /** + * This the registry for data models. + */ + private static final Map<String, ActivityChooserModel> sDataModelRegistry = + new HashMap<String, ActivityChooserModel>(); + + /** + * Lock for synchronizing on this instance. + */ + private final Object mInstanceLock = new Object(); + + /** + * List of activities that can handle the current intent. + */ + private final List<Activity> mActivitys = new ArrayList<Activity>(); + + /** + * List with historical choice records. + */ + private final List<HistoricalRecord> mHistoricalRecords = new ArrayList<HistoricalRecord>(); + + /** + * Monitor for added and removed packages. + */ + private final PackageMonitor mPackageMonitor = new DataModelPackageMonitor(); + + /** + * Context for accessing resources. + */ + private final Context mContext; + + /** + * The name of the history file that backs this model. + */ + private final String mHistoryFileName; + + /** + * The intent for which a activity is being chosen. + */ + private Intent mIntent; + + /** + * The sorter for ordering activities based on intent and past choices. + */ + private ActivitySorter mActivitySorter = new DefaultSorter(); + + /** + * The maximal length of the choice history. + */ + private int mHistoryMaxSize = DEFAULT_HISTORY_MAX_LENGTH; + + /** + * Flag whether choice history can be read. In general many clients can + * share the same data model and {@link #readHistoricalData()} may be called + * by arbitrary of them any number of times. Therefore, this class guarantees + * that the very first read succeeds and subsequent reads can be performed + * only after a call to {@link #persistHistoricalData()} followed by change + * of the share records. + */ + private boolean mCanReadHistoricalData = true; + + /** + * Flag whether the choice history was read. This is used to enforce that + * before calling {@link #persistHistoricalData()} a call to + * {@link #persistHistoricalData()} has been made. This aims to avoid a + * scenario in which a choice history file exits, it is not read yet and + * it is overwritten. Note that always all historical records are read in + * full and the file is rewritten. This is necessary since we need to + * purge old records that are outside of the sliding window of past choices. + */ + private boolean mReadShareHistoryCalled = false; + + /** + * Flag whether the choice records have changed. In general many clients can + * share the same data model and {@link #persistHistoricalData()} may be called + * by arbitrary of them any number of times. Therefore, this class guarantees + * that choice history will be persisted only if it has changed. + */ + private boolean mHistoricalRecordsChanged = true; + + /** + * Hander for scheduling work on client tread. + */ + private final Handler mHandler = new Handler(); + + /** + * Gets the data model backed by the contents of the provided file with historical data. + * Note that only one data model is backed by a given file, thus multiple calls with + * the same file name will return the same model instance. If no such instance is present + * it is created. + * <p> + * <strong>Note:</strong> To use the default historical data file clients should explicitly + * pass as file name {@link #DEFAULT_HISTORY_FILE_NAME}. If no persistence of the choice + * history is desired clients should pass <code>null</code> for the file name. In such + * case a new model is returned for each invocation. + * </p> + * + * <p> + * <strong>Always use difference historical data files for semantically different actions. + * For example, sharing is different from importing.</strong> + * </p> + * + * @param context Context for loading resources. + * @param historyFileName File name with choice history, <code>null</code> + * if the model should not be backed by a file. In this case the activities + * will be ordered only by data from the current session. + * + * @return The model. + */ + public static ActivityChooserModel get(Context context, String historyFileName) { + if (historyFileName == null) { + return new ActivityChooserModel(context, historyFileName); + } + synchronized (sRegistryLock) { + ActivityChooserModel dataModel = sDataModelRegistry.get(historyFileName); + if (dataModel == null) { + dataModel = new ActivityChooserModel(context, historyFileName); + sDataModelRegistry.put(historyFileName, dataModel); + } + return dataModel; + } + } + + /** + * Creates a new instance. + * + * @param context Context for loading resources. + * @param historyFileName The history XML file. + */ + private ActivityChooserModel(Context context, String historyFileName) { + mContext = context.getApplicationContext(); + if (!TextUtils.isEmpty(historyFileName) + && !historyFileName.endsWith(HISTORY_FILE_EXTENSION)) { + mHistoryFileName = historyFileName + HISTORY_FILE_EXTENSION; + } else { + mHistoryFileName = historyFileName; + } + mPackageMonitor.register(mContext, true); + } + + /** + * Sets an intent for which to choose a activity. + * <p> + * <strong>Note:</strong> Clients must set only semantically similar + * intents for each data model. + * <p> + * + * @param intent The intent. + */ + public void setIntent(Intent intent) { + synchronized (mInstanceLock) { + if (mIntent == intent) { + return; + } + mIntent = intent; + loadActivitiesLocked(); + } + } + + /** + * Gets the intent for which a activity is being chosen. + * + * @return The intent. + */ + public Intent getIntent() { + synchronized (mInstanceLock) { + return mIntent; + } + } + + /** + * Gets the number of activities that can handle the intent. + * + * @return The activity count. + * + * @see #setIntent(Intent) + */ + public int getActivityCount() { + synchronized (mInstanceLock) { + return mActivitys.size(); + } + } + + /** + * Gets an activity at a given index. + * + * @return The activity. + * + * @see Activity + * @see #setIntent(Intent) + */ + public ResolveInfo getActivity(int index) { + synchronized (mInstanceLock) { + return mActivitys.get(index).resolveInfo; + } + } + + /** + * Gets the index of a the given activity. + * + * @param activity The activity index. + * + * @return The index if found, -1 otherwise. + */ + public int getActivityIndex(ResolveInfo activity) { + List<Activity> activities = mActivitys; + final int activityCount = activities.size(); + for (int i = 0; i < activityCount; i++) { + Activity currentActivity = activities.get(i); + if (currentActivity.resolveInfo == activity) { + return i; + } + } + return INVALID_INDEX; + } + + /** + * Chooses a activity to handle the current intent. This will result in + * adding a historical record for that action and construct intent with + * its component name set such that it can be immediately started by the + * client. + * <p> + * <strong>Note:</strong> By calling this method the client guarantees + * that the returned intent will be started. This intent is returned to + * the client solely to let additional customization before the start. + * </p> + * + * @return Whether adding succeeded. + * + * @see HistoricalRecord + */ + public Intent chooseActivity(int index) { + Activity chosenActivity = mActivitys.get(index); + Activity defaultActivity = mActivitys.get(0); + + ComponentName chosenName = new ComponentName( + chosenActivity.resolveInfo.activityInfo.packageName, + chosenActivity.resolveInfo.activityInfo.name); + HistoricalRecord historicalRecord = new HistoricalRecord(chosenName, + System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT); + addHisoricalRecord(historicalRecord); + + Intent choiceIntent = new Intent(mIntent); + choiceIntent.setComponent(chosenName); + + return choiceIntent; + } + + /** + * Gets the default activity, The default activity is defined as the one + * with highest rank i.e. the first one in the list of activities that can + * handle the intent. + * + * @return The default activity, <code>null</code> id not activities. + * + * @see #getActivity(int) + */ + public ResolveInfo getDefaultActivity() { + synchronized (mInstanceLock) { + if (!mActivitys.isEmpty()) { + return mActivitys.get(0).resolveInfo; + } + } + return null; + } + + /** + * Sets the default activity. The default activity is set by adding a + * historical record with weight high enough that this activity will + * become the highest ranked. Such a strategy guarantees that the default + * will eventually change if not used. Also the weight of the record for + * setting a default is inflated with a constant amount to guarantee that + * it will stay as default for awhile. + * + * @param index The index of the activity to set as default. + */ + public void setDefaultActivity(int index) { + Activity newDefaultActivity = mActivitys.get(index); + Activity oldDefaultActivity = mActivitys.get(0); + + final float weight; + if (oldDefaultActivity != null) { + // Add a record with weight enough to boost the chosen at the top. + weight = oldDefaultActivity.weight - newDefaultActivity.weight + + DEFAULT_ACTIVITY_INFLATION; + } else { + weight = DEFAULT_HISTORICAL_RECORD_WEIGHT; + } + + ComponentName defaultName = new ComponentName( + newDefaultActivity.resolveInfo.activityInfo.packageName, + newDefaultActivity.resolveInfo.activityInfo.name); + HistoricalRecord historicalRecord = new HistoricalRecord(defaultName, + System.currentTimeMillis(), weight); + addHisoricalRecord(historicalRecord); + } + + /** + * Reads the history data from the backing file if the latter + * was provided. Calling this method more than once before a call + * to {@link #persistHistoricalData()} has been made has no effect. + * <p> + * <strong>Note:</strong> Historical data is read asynchronously and + * as soon as the reading is completed any registered + * {@link DataSetObserver}s will be notified. Also no historical + * data is read until this method is invoked. + * <p> + */ + public void readHistoricalData() { + synchronized (mInstanceLock) { + if (!mCanReadHistoricalData || !mHistoricalRecordsChanged) { + return; + } + mCanReadHistoricalData = false; + mReadShareHistoryCalled = true; + if (!TextUtils.isEmpty(mHistoryFileName)) { + AsyncTask.SERIAL_EXECUTOR.execute(new HistoryLoader()); + } + } + } + + /** + * Persists the history data to the backing file if the latter + * was provided. Calling this method before a call to {@link #readHistoricalData()} + * throws an exception. Calling this method more than one without choosing an + * activity has not effect. + * + * @throws IllegalStateException If this method is called before a call to + * {@link #readHistoricalData()}. + */ + public void persistHistoricalData() { + synchronized (mInstanceLock) { + if (!mReadShareHistoryCalled) { + throw new IllegalStateException("No preceding call to #readHistoricalData"); + } + if (!mHistoricalRecordsChanged) { + return; + } + mHistoricalRecordsChanged = false; + mCanReadHistoricalData = true; + if (!TextUtils.isEmpty(mHistoryFileName)) { + AsyncTask.SERIAL_EXECUTOR.execute(new HistoryPersister()); + } + } + } + + /** + * Sets the sorter for ordering activities based on historical data and an intent. + * + * @param activitySorter The sorter. + * + * @see ActivitySorter + */ + public void setActivitySorter(ActivitySorter activitySorter) { + synchronized (mInstanceLock) { + if (mActivitySorter == activitySorter) { + return; + } + mActivitySorter = activitySorter; + sortActivities(); + } + } + + /** + * Sorts the activities based on history and an intent. If + * a sorter is not specified this a default implementation is used. + * + * @see #setActivitySorter(ActivitySorter) + */ + private void sortActivities() { + synchronized (mInstanceLock) { + if (mActivitySorter != null && !mActivitys.isEmpty()) { + mActivitySorter.sort(mIntent, mActivitys, + Collections.unmodifiableList(mHistoricalRecords)); + notifyChanged(); + } + } + } + + /** + * Sets the maximal size of the historical data. Defaults to + * {@link #DEFAULT_HISTORY_MAX_LENGTH} + * <p> + * <strong>Note:</strong> Setting this property will immediately + * enforce the specified max history size by dropping enough old + * historical records to enforce the desired size. Thus, any + * records that exceed the history size will be discarded and + * irreversibly lost. + * </p> + * + * @param historyMaxSize The max history size. + */ + public void setHistoryMaxSize(int historyMaxSize) { + synchronized (mInstanceLock) { + if (mHistoryMaxSize == historyMaxSize) { + return; + } + mHistoryMaxSize = historyMaxSize; + pruneExcessiveHistoricalRecordsLocked(); + sortActivities(); + } + } + + /** + * Gets the history max size. + * + * @return The history max size. + */ + public int getHistoryMaxSize() { + synchronized (mInstanceLock) { + return mHistoryMaxSize; + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + mPackageMonitor.unregister(); + } + + /** + * Adds a historical record. + * + * @param historicalRecord The record to add. + * @return True if the record was added. + */ + private boolean addHisoricalRecord(HistoricalRecord historicalRecord) { + synchronized (mInstanceLock) { + final boolean added = mHistoricalRecords.add(historicalRecord); + if (added) { + mHistoricalRecordsChanged = true; + pruneExcessiveHistoricalRecordsLocked(); + sortActivities(); + } + return added; + } + } + + /** + * Prunes older excessive records to guarantee {@link #mHistoryMaxSize}. + */ + private void pruneExcessiveHistoricalRecordsLocked() { + List<HistoricalRecord> choiceRecords = mHistoricalRecords; + final int pruneCount = choiceRecords.size() - mHistoryMaxSize; + if (pruneCount <= 0) { + return; + } + mHistoricalRecordsChanged = true; + for (int i = 0; i < pruneCount; i++) { + HistoricalRecord prunedRecord = choiceRecords.remove(0); + if (DEBUG) { + Log.i(LOG_TAG, "Pruned: " + prunedRecord); + } + } + } + + /** + * Loads the activities. + */ + private void loadActivitiesLocked() { + mActivitys.clear(); + if (mIntent != null) { + List<ResolveInfo> resolveInfos = + mContext.getPackageManager().queryIntentActivities(mIntent, 0); + final int resolveInfoCount = resolveInfos.size(); + for (int i = 0; i < resolveInfoCount; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + mActivitys.add(new Activity(resolveInfo)); + } + sortActivities(); + } else { + notifyChanged(); + } + } + + /** + * Prunes historical records for a package that goes away. + * + * @param packageName The name of the package that goes away. + */ + private void pruneHistoricalRecordsForPackageLocked(String packageName) { + boolean recordsRemoved = false; + + List<HistoricalRecord> historicalRecords = mHistoricalRecords; + for (int i = 0; i < historicalRecords.size(); i++) { + HistoricalRecord historicalRecord = historicalRecords.get(i); + String recordPackageName = historicalRecord.activity.getPackageName(); + if (recordPackageName.equals(packageName)) { + historicalRecords.remove(historicalRecord); + recordsRemoved = true; + } + } + + if (recordsRemoved) { + mHistoricalRecordsChanged = true; + sortActivities(); + } + } + + /** + * Represents a record in the history. + */ + public final static class HistoricalRecord { + + /** + * The activity name. + */ + public final ComponentName activity; + + /** + * The choice time. + */ + public final long time; + + /** + * The record weight. + */ + public final float weight; + + /** + * Creates a new instance. + * + * @param activityName The activity component name flattened to string. + * @param time The time the activity was chosen. + * @param weight The weight of the record. + */ + public HistoricalRecord(String activityName, long time, float weight) { + this(ComponentName.unflattenFromString(activityName), time, weight); + } + + /** + * Creates a new instance. + * + * @param activityName The activity name. + * @param time The time the activity was chosen. + * @param weight The weight of the record. + */ + public HistoricalRecord(ComponentName activityName, long time, float weight) { + this.activity = activityName; + this.time = time; + this.weight = weight; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((activity == null) ? 0 : activity.hashCode()); + result = prime * result + (int) (time ^ (time >>> 32)); + result = prime * result + Float.floatToIntBits(weight); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + HistoricalRecord other = (HistoricalRecord) obj; + if (activity == null) { + if (other.activity != null) { + return false; + } + } else if (!activity.equals(other.activity)) { + return false; + } + if (time != other.time) { + return false; + } + if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append("; activity:").append(activity); + builder.append("; time:").append(time); + builder.append("; weight:").append(new BigDecimal(weight)); + builder.append("]"); + return builder.toString(); + } + } + + /** + * Represents an activity. + */ + public final class Activity implements Comparable<Activity> { + + /** + * The {@link ResolveInfo} of the activity. + */ + public final ResolveInfo resolveInfo; + + /** + * Weight of the activity. Useful for sorting. + */ + public float weight; + + /** + * Creates a new instance. + * + * @param resolveInfo activity {@link ResolveInfo}. + */ + public Activity(ResolveInfo resolveInfo) { + this.resolveInfo = resolveInfo; + } + + @Override + public int hashCode() { + return 31 + Float.floatToIntBits(weight); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Activity other = (Activity) obj; + if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) { + return false; + } + return true; + } + + public int compareTo(Activity another) { + return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append("resolveInfo:").append(resolveInfo.toString()); + builder.append("; weight:").append(new BigDecimal(weight)); + builder.append("]"); + return builder.toString(); + } + } + + /** + * Default activity sorter implementation. + */ + private final class DefaultSorter implements ActivitySorter { + private static final float WEIGHT_DECAY_COEFFICIENT = 0.95f; + + private final Map<String, Activity> mPackageNameToActivityMap = + new HashMap<String, Activity>(); + + public void sort(Intent intent, List<Activity> activities, + List<HistoricalRecord> historicalRecords) { + Map<String, Activity> packageNameToActivityMap = + mPackageNameToActivityMap; + packageNameToActivityMap.clear(); + + final int activityCount = activities.size(); + for (int i = 0; i < activityCount; i++) { + Activity activity = activities.get(i); + activity.weight = 0.0f; + String packageName = activity.resolveInfo.activityInfo.packageName; + packageNameToActivityMap.put(packageName, activity); + } + + final int lastShareIndex = historicalRecords.size() - 1; + float nextRecordWeight = 1; + for (int i = lastShareIndex; i >= 0; i--) { + HistoricalRecord historicalRecord = historicalRecords.get(i); + String packageName = historicalRecord.activity.getPackageName(); + Activity activity = packageNameToActivityMap.get(packageName); + activity.weight += historicalRecord.weight * nextRecordWeight; + nextRecordWeight = nextRecordWeight * WEIGHT_DECAY_COEFFICIENT; + } + + Collections.sort(activities); + + if (DEBUG) { + for (int i = 0; i < activityCount; i++) { + Log.i(LOG_TAG, "Sorted: " + activities.get(i)); + } + } + } + } + + /** + * Command for reading the historical records from a file off the UI thread. + */ + private final class HistoryLoader implements Runnable { + + public void run() { + FileInputStream fis = null; + try { + fis = mContext.openFileInput(mHistoryFileName); + } catch (FileNotFoundException fnfe) { + if (DEBUG) { + Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName); + } + return; + } + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + + int type = XmlPullParser.START_DOCUMENT; + while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { + type = parser.next(); + } + + if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) { + throw new XmlPullParserException("Share records file does not start with " + + TAG_HISTORICAL_RECORDS + " tag."); + } + + List<HistoricalRecord> readRecords = new ArrayList<HistoricalRecord>(); + + while (true) { + type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT) { + break; + } + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + String nodeName = parser.getName(); + if (!TAG_HISTORICAL_RECORD.equals(nodeName)) { + throw new XmlPullParserException("Share records file not well-formed."); + } + + String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY); + final long time = + Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME)); + final float weight = + Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT)); + + HistoricalRecord readRecord = new HistoricalRecord(activity, time, + weight); + readRecords.add(readRecord); + + if (DEBUG) { + Log.i(LOG_TAG, "Read " + readRecord.toString()); + } + } + + if (DEBUG) { + Log.i(LOG_TAG, "Read " + readRecords.size() + " historical records."); + } + + synchronized (mInstanceLock) { + Set<HistoricalRecord> uniqueShareRecords = + new LinkedHashSet<HistoricalRecord>(readRecords); + + // Make sure no duplicates. Example: Read a file with + // one record, add one record, persist the two records, + // add a record, read the persisted records - the + // read two records should not be added again. + List<HistoricalRecord> historicalRecords = mHistoricalRecords; + final int historicalRecordsCount = historicalRecords.size(); + for (int i = historicalRecordsCount - 1; i >= 0; i--) { + HistoricalRecord historicalRecord = historicalRecords.get(i); + uniqueShareRecords.add(historicalRecord); + } + + if (historicalRecords.size() == uniqueShareRecords.size()) { + return; + } + + // Make sure the oldest records go to the end. + historicalRecords.clear(); + historicalRecords.addAll(uniqueShareRecords); + + mHistoricalRecordsChanged = true; + + // Do this on the client thread since the client may be on the UI + // thread, wait for data changes which happen during sorting, and + // perform UI modification based on the data change. + mHandler.post(new Runnable() { + public void run() { + pruneExcessiveHistoricalRecordsLocked(); + sortActivities(); + } + }); + } + } catch (XmlPullParserException xppe) { + Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, ioe); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException ioe) { + /* ignore */ + } + } + } + } + } + + /** + * Command for persisting the historical records to a file off the UI thread. + */ + private final class HistoryPersister implements Runnable { + + public void run() { + FileOutputStream fos = null; + List<HistoricalRecord> records = null; + + synchronized (mInstanceLock) { + records = new ArrayList<HistoricalRecord>(mHistoricalRecords); + } + + try { + fos = mContext.openFileOutput(mHistoryFileName, Context.MODE_PRIVATE); + } catch (FileNotFoundException fnfe) { + Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, fnfe); + return; + } + + XmlSerializer serializer = Xml.newSerializer(); + + try { + serializer.setOutput(fos, null); + serializer.startDocument("UTF-8", true); + serializer.startTag(null, TAG_HISTORICAL_RECORDS); + + final int recordCount = records.size(); + for (int i = 0; i < recordCount; i++) { + HistoricalRecord record = records.remove(0); + serializer.startTag(null, TAG_HISTORICAL_RECORD); + serializer.attribute(null, ATTRIBUTE_ACTIVITY, record.activity.flattenToString()); + serializer.attribute(null, ATTRIBUTE_TIME, String.valueOf(record.time)); + serializer.attribute(null, ATTRIBUTE_WEIGHT, String.valueOf(record.weight)); + serializer.endTag(null, TAG_HISTORICAL_RECORD); + if (DEBUG) { + Log.i(LOG_TAG, "Wrote " + record.toString()); + } + } + + serializer.endTag(null, TAG_HISTORICAL_RECORDS); + serializer.endDocument(); + + if (DEBUG) { + Log.i(LOG_TAG, "Wrote " + recordCount + " historical records."); + } + } catch (IllegalArgumentException iae) { + Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, iae); + } catch (IllegalStateException ise) { + Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ise); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ioe); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + /* ignore */ + } + } + } + } + } + + /** + * Keeps in sync the historical records and activities with the installed applications. + */ + private final class DataModelPackageMonitor extends PackageMonitor { + + @Override + public void onPackageAdded(String packageName, int uid) { + synchronized (mInstanceLock) { + loadActivitiesLocked(); + } + } + + @Override + public void onPackageAppeared(String packageName, int reason) { + synchronized (mInstanceLock) { + loadActivitiesLocked(); + } + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + synchronized (mInstanceLock) { + pruneHistoricalRecordsForPackageLocked(packageName); + loadActivitiesLocked(); + } + } + + @Override + public void onPackageDisappeared(String packageName, int reason) { + synchronized (mInstanceLock) { + pruneHistoricalRecordsForPackageLocked(packageName); + loadActivitiesLocked(); + } + } + } +} diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java new file mode 100644 index 0000000..2fe8162 --- /dev/null +++ b/core/java/android/widget/ActivityChooserView.java @@ -0,0 +1,765 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Debug; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ActivityChooserModel.ActivityChooserModelClient; + +import com.android.internal.R; + +/** + * This class is a view for choosing an activity for handling a given {@link Intent}. + * <p> + * The view is composed of two adjacent buttons: + * <ul> + * <li> + * The left button is an immediate action and allows one click activity choosing. + * Tapping this button immediately executes the intent without requiring any further + * user input. Long press on this button shows a popup for changing the default + * activity. + * </li> + * <li> + * The right button is an overflow action and provides an optimized menu + * of additional activities. Tapping this button shows a popup anchored to this + * view, listing the most frequently used activities. This list is initially + * limited to a small number of items in frequency used order. The last item, + * "Show all..." serves as an affordance to display all available activities. + * </li> + * </ul> + * </p> + * </p> + * This view is backed by a {@link ActivityChooserModel}. Calling {@link #showPopup()} + * while this view is attached to the view hierarchy will show a popup with + * activities while if the view is not attached it will show a dialog. + * </p> + * + * @hide + */ +public class ActivityChooserView extends ViewGroup implements ActivityChooserModelClient { + + /** + * An adapter for displaying the activities in an {@link AdapterView}. + */ + private final ActivityChooserViewAdapter mAdapter; + + /** + * Implementation of various interfaces to avoid publishing them in the APIs. + */ + private final Callbacks mCallbacks; + + /** + * The content of this view. + */ + private final LinearLayout mActivityChooserContent; + + /** + * The expand activities action button; + */ + private final ImageButton mExpandActivityOverflowButton; + + /** + * The default activities action button; + */ + private final ImageButton mDefaultActionButton; + + /** + * The header for handlers list. + */ + private final View mListHeaderView; + + /** + * The footer for handlers list. + */ + private final View mListFooterView; + + /** + * The title of the header view. + */ + private TextView mListHeaderViewTitle; + + /** + * The title for expanding the activities list. + */ + private final String mListHeaderViewTitleSelectDefault; + + /** + * The title if no activity exist. + */ + private final String mListHeaderViewTitleNoActivities; + + /** + * Popup window for showing the activity overflow list. + */ + private ListPopupWindow mListPopupWindow; + + /** + * Alert dialog for showing the activity overflow list. + */ + private AlertDialog mAlertDialog; + + /** + * Listener for the dismissal of the popup/alert. + */ + private PopupWindow.OnDismissListener mOnDismissListener; + + /** + * Flag whether a default activity currently being selected. + */ + private boolean mIsSelectingDefaultActivity; + + /** + * The count of activities in the popup. + */ + private int mInitialActivityCount = ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT; + + /** + * Flag whether this view is attached to a window. + */ + private boolean mIsAttachedToWindow; + + /** + * Flag whether this view is showing an alert dialog. + */ + private boolean mIsShowingAlertDialog; + + /** + * Flag whether this view is showing a popup window. + */ + private boolean mIsShowingPopuWindow; + + /** + * Create a new instance. + * + * @param context The application environment. + */ + public ActivityChooserView(Context context) { + this(context, null); + } + + /** + * Create a new instance. + * + * @param context The application environment. + * @param attrs A collection of attributes. + */ + public ActivityChooserView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.actionButtonStyle); + } + + /** + * Create a new instance. + * + * @param context The application environment. + * @param attrs A collection of attributes. + * @param defStyle The default style to apply to this view. + */ + public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray attributesArray = context.obtainStyledAttributes(attrs, + R.styleable.ActivityChooserView, defStyle, 0); + + mInitialActivityCount = attributesArray.getInt( + R.styleable.ActivityChooserView_initialActivityCount, + ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT); + + Drawable expandActivityOverflowButtonDrawable = attributesArray.getDrawable( + R.styleable.ActivityChooserView_expandActivityOverflowButtonDrawable); + + LayoutInflater inflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.activity_chooser_view, this, true); + + mCallbacks = new Callbacks(); + + mActivityChooserContent = (LinearLayout) findViewById(R.id.activity_chooser_view_content); + + mDefaultActionButton = (ImageButton) findViewById(R.id.default_activity_button); + mDefaultActionButton.setOnClickListener(mCallbacks); + mDefaultActionButton.setOnLongClickListener(mCallbacks); + + mExpandActivityOverflowButton = (ImageButton) findViewById(R.id.expand_activities_button); + mExpandActivityOverflowButton.setOnClickListener(mCallbacks); + mExpandActivityOverflowButton.setBackgroundDrawable(expandActivityOverflowButtonDrawable); + + mListHeaderView = inflater.inflate(R.layout.activity_chooser_list_header, null); + mListFooterView = inflater.inflate(R.layout.activity_chooser_list_footer, null); + + mListHeaderViewTitle = (TextView) mListHeaderView.findViewById(R.id.title); + mListHeaderViewTitleSelectDefault = context.getString( + R.string.activity_chooser_view_select_default); + mListHeaderViewTitleNoActivities = context.getString( + R.string.activity_chooser_view_no_activities); + + mAdapter = new ActivityChooserViewAdapter(); + mAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + super.onChanged(); + updateButtons(); + } + }); + } + + /** + * {@inheritDoc} + */ + public void setActivityChooserModel(ActivityChooserModel dataModel) { + mAdapter.setDataModel(dataModel); + if (isShowingPopup()) { + dismissPopup(); + showPopup(); + } + } + + /** + * Sets the background for the button that expands the activity + * overflow list. + * + * <strong>Note:</strong> Clients would like to set this drawable + * as a clue about the action the chosen activity will perform. For + * example, if share activity is to be chosen the drawable should + * give a clue that sharing is to be performed. + * + * @param drawable The drawable. + */ + public void setExpandActivityOverflowButtonDrawable(Drawable drawable) { + mExpandActivityOverflowButton.setBackgroundDrawable(drawable); + } + + /** + * Shows the popup window with activities. + * + * @return True if the popup was shown, false if already showing. + */ + public boolean showPopup() { + if (isShowingPopup()) { + return false; + } + mIsSelectingDefaultActivity = false; + showPopupUnchecked(mInitialActivityCount); + return true; + } + + /** + * Shows the popup no matter if it was already showing. + * + * @param maxActivityCount The max number of activities to display. + */ + private void showPopupUnchecked(int maxActivityCount) { + mAdapter.setMaxActivityCount(maxActivityCount); + if (mIsSelectingDefaultActivity) { + if (mAdapter.getActivityCount() > 0) { + mListHeaderViewTitle.setText(mListHeaderViewTitleSelectDefault); + } else { + mListHeaderViewTitle.setText(mListHeaderViewTitleNoActivities); + } + mAdapter.setHeaderView(mListHeaderView); + } else { + mAdapter.setHeaderView(null); + } + + if (mAdapter.getActivityCount() > maxActivityCount + 1) { + mAdapter.setFooterView(mListFooterView); + } else { + mAdapter.setFooterView(null); + } + + if (!mIsAttachedToWindow || mIsShowingAlertDialog) { + AlertDialog alertDialog = getAlertDilalog(); + if (!alertDialog.isShowing()) { + alertDialog.setCustomTitle(this); + alertDialog.show(); + mIsShowingAlertDialog = true; + } + } else { + ListPopupWindow popupWindow = getListPopupWindow(); + if (!popupWindow.isShowing()) { + popupWindow.setContentWidth(mAdapter.measureContentWidth()); + popupWindow.show(); + mIsShowingPopuWindow = true; + } + } + } + + /** + * Dismisses the popup window with activities. + * + * @return True if dismissed, false if already dismissed. + */ + public boolean dismissPopup() { + if (!isShowingPopup()) { + return false; + } + if (mIsShowingAlertDialog) { + getAlertDilalog().dismiss(); + } else if (mIsShowingPopuWindow) { + getListPopupWindow().dismiss(); + } + return true; + } + + /** + * Gets whether the popup window with activities is shown. + * + * @return True if the popup is shown. + */ + public boolean isShowingPopup() { + if (mIsShowingAlertDialog) { + return getAlertDilalog().isShowing(); + } else if (mIsShowingPopuWindow) { + return getListPopupWindow().isShowing(); + } + return false; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + ActivityChooserModel dataModel = mAdapter.getDataModel(); + if (dataModel != null) { + dataModel.readHistoricalData(); + } + mIsAttachedToWindow = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + ActivityChooserModel dataModel = mAdapter.getDataModel(); + if (dataModel != null) { + dataModel.persistHistoricalData(); + } + mIsAttachedToWindow = false; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mActivityChooserContent.measure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(mActivityChooserContent.getMeasuredWidth(), + mActivityChooserContent.getMeasuredHeight()); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mActivityChooserContent.layout(left, top, right, bottom); + if (mIsShowingPopuWindow) { + if (isShown()) { + showPopupUnchecked(mAdapter.getMaxActivityCount()); + } else { + dismissPopup(); + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + mActivityChooserContent.onDraw(canvas); + } + + public ActivityChooserModel getDataModel() { + return mAdapter.getDataModel(); + } + + /** + * Sets a listener to receive a callback when the popup is dismissed. + * + * @param listener The listener to be notified. + */ + public void setOnDismissListener(PopupWindow.OnDismissListener listener) { + mOnDismissListener = listener; + } + + /** + * Sets the initial count of items shown in the activities popup + * i.e. the items before the popup is expanded. This is an upper + * bound since it is not guaranteed that such number of intent + * handlers exist. + * + * @param itemCount The initial popup item count. + */ + public void setInitialActivityCount(int itemCount) { + mInitialActivityCount = itemCount; + } + + /** + * Gets the list popup window which is lazily initialized. + * + * @return The popup. + */ + private ListPopupWindow getListPopupWindow() { + if (mListPopupWindow == null) { + mListPopupWindow = new ListPopupWindow(getContext()); + mListPopupWindow.setAdapter(mAdapter); + mListPopupWindow.setAnchorView(ActivityChooserView.this); + mListPopupWindow.setModal(true); + mListPopupWindow.setOnItemClickListener(mCallbacks); + mListPopupWindow.setOnDismissListener(mCallbacks); + } + return mListPopupWindow; + } + + /** + * Gets the alert dialog which is lazily initialized. + * + * @return The popup. + */ + private AlertDialog getAlertDilalog() { + if (mAlertDialog == null) { + Builder builder = new Builder(getContext()); + builder.setAdapter(mAdapter, null); + mAlertDialog = builder.create(); + mAlertDialog.getListView().setOnItemClickListener(mCallbacks); + mAlertDialog.setOnDismissListener(mCallbacks); + } + return mAlertDialog; + } + + /** + * Updates the buttons state. + */ + private void updateButtons() { + final int activityCount = mAdapter.getActivityCount(); + if (activityCount > 0) { + mDefaultActionButton.setVisibility(VISIBLE); + if (mAdapter.getCount() > 0) { + mExpandActivityOverflowButton.setEnabled(true); + } else { + mExpandActivityOverflowButton.setEnabled(false); + } + ResolveInfo activity = mAdapter.getDefaultActivity(); + PackageManager packageManager = mContext.getPackageManager(); + mDefaultActionButton.setBackgroundDrawable(activity.loadIcon(packageManager)); + } else { + mDefaultActionButton.setVisibility(View.INVISIBLE); + mExpandActivityOverflowButton.setEnabled(false); + } + } + + /** + * Interface implementation to avoid publishing them in the APIs. + */ + private class Callbacks implements AdapterView.OnItemClickListener, + View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener, + DialogInterface.OnDismissListener { + + // AdapterView#OnItemClickListener + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + ActivityChooserViewAdapter adapter = (ActivityChooserViewAdapter) parent.getAdapter(); + final int itemViewType = adapter.getItemViewType(position); + switch (itemViewType) { + case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_HEADER: { + /* do nothing */ + } break; + case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_FOOTER: { + showPopupUnchecked(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED); + } break; + case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_ACTIVITY: { + dismissPopup(); + if (mIsSelectingDefaultActivity) { + mAdapter.getDataModel().setDefaultActivity(position); + } else { + // The first item in the model is default action => adjust index + Intent launchIntent = mAdapter.getDataModel().chooseActivity(position + 1); + mContext.startActivity(launchIntent); + } + } break; + default: + throw new IllegalArgumentException(); + } + } + + // View.OnClickListener + public void onClick(View view) { + if (view == mDefaultActionButton) { + dismissPopup(); + ResolveInfo defaultActivity = mAdapter.getDefaultActivity(); + final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity); + Intent launchIntent = mAdapter.getDataModel().chooseActivity(index); + mContext.startActivity(launchIntent); + } else if (view == mExpandActivityOverflowButton) { + mIsSelectingDefaultActivity = false; + showPopupUnchecked(mInitialActivityCount); + } else { + throw new IllegalArgumentException(); + } + } + + // OnLongClickListener#onLongClick + @Override + public boolean onLongClick(View view) { + if (view == mDefaultActionButton) { + if (mAdapter.getCount() > 0) { + mIsSelectingDefaultActivity = true; + showPopupUnchecked(mInitialActivityCount); + } + } else { + throw new IllegalArgumentException(); + } + return true; + } + + // PopUpWindow.OnDismissListener#onDismiss + public void onDismiss() { + mIsShowingPopuWindow = false; + notifyOnDismissListener(); + } + + // DialogInterface.OnDismissListener#onDismiss + @Override + public void onDismiss(DialogInterface dialog) { + mIsShowingAlertDialog = false; + AlertDialog alertDialog = (AlertDialog) dialog; + alertDialog.setCustomTitle(null); + notifyOnDismissListener(); + } + + private void notifyOnDismissListener() { + if (mOnDismissListener != null) { + mOnDismissListener.onDismiss(); + } + } + } + + /** + * Adapter for backing the list of activities shown in the popup. + */ + private class ActivityChooserViewAdapter extends BaseAdapter { + + public static final int MAX_ACTIVITY_COUNT_UNLIMITED = Integer.MAX_VALUE; + + public static final int MAX_ACTIVITY_COUNT_DEFAULT = 4; + + private static final int ITEM_VIEW_TYPE_HEADER = 0; + + private static final int ITEM_VIEW_TYPE_ACTIVITY = 1; + + private static final int ITEM_VIEW_TYPE_FOOTER = 2; + + private static final int ITEM_VIEW_TYPE_COUNT = 3; + + private final DataSetObserver mDataSetOberver = new DataSetObserver() { + + @Override + public void onChanged() { + super.onChanged(); + notifyDataSetChanged(); + } + @Override + public void onInvalidated() { + super.onInvalidated(); + notifyDataSetInvalidated(); + } + }; + + private ActivityChooserModel mDataModel; + + private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT; + + private ResolveInfo mDefaultActivity; + + private View mHeaderView; + + private View mFooterView; + + public void setDataModel(ActivityChooserModel dataModel) { + mDataModel = dataModel; + mDataModel.registerObserver(mDataSetOberver); + notifyDataSetChanged(); + } + + @Override + public void notifyDataSetChanged() { + if (mDataModel.getActivityCount() > 0) { + mDefaultActivity = mDataModel.getDefaultActivity(); + } else { + mDefaultActivity = null; + } + super.notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + if (mHeaderView != null && position == 0) { + return ITEM_VIEW_TYPE_HEADER; + } else if (mFooterView != null && position == getCount() - 1) { + return ITEM_VIEW_TYPE_FOOTER; + } else { + return ITEM_VIEW_TYPE_ACTIVITY; + } + } + + @Override + public int getViewTypeCount() { + return ITEM_VIEW_TYPE_COUNT; + } + + public int getCount() { + int count = 0; + int activityCount = mDataModel.getActivityCount(); + if (activityCount > 0) { + activityCount--; + } + count = Math.min(activityCount, mMaxActivityCount); + if (mHeaderView != null) { + count++; + } + if (mFooterView != null) { + count++; + } + return count; + } + + public Object getItem(int position) { + final int itemViewType = getItemViewType(position); + switch (itemViewType) { + case ITEM_VIEW_TYPE_HEADER: + return mHeaderView; + case ITEM_VIEW_TYPE_FOOTER: + return mFooterView; + case ITEM_VIEW_TYPE_ACTIVITY: + int targetIndex = (mHeaderView == null) ? position : position - 1; + if (mDefaultActivity != null) { + targetIndex++; + } + return mDataModel.getActivity(targetIndex); + default: + throw new IllegalArgumentException(); + } + } + + public long getItemId(int position) { + return position; + } + + @Override + public boolean isEnabled(int position) { + final int itemViewType = getItemViewType(position); + switch (itemViewType) { + case ITEM_VIEW_TYPE_HEADER: + return false; + case ITEM_VIEW_TYPE_FOOTER: + case ITEM_VIEW_TYPE_ACTIVITY: + return true; + default: + throw new IllegalArgumentException(); + } + } + + public View getView(int position, View convertView, ViewGroup parent) { + final int itemViewType = getItemViewType(position); + switch (itemViewType) { + case ITEM_VIEW_TYPE_HEADER: + return mHeaderView; + case ITEM_VIEW_TYPE_FOOTER: + return mFooterView; + case ITEM_VIEW_TYPE_ACTIVITY: + if (convertView == null || convertView.getId() != R.id.list_item) { + convertView = LayoutInflater.from(getContext()).inflate( + R.layout.activity_chooser_view_list_item, parent, false); + } + PackageManager packageManager = mContext.getPackageManager(); + // Set the icon + ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); + ResolveInfo activity = (ResolveInfo) getItem(position); + iconView.setBackgroundDrawable(activity.loadIcon(packageManager)); + // Set the title. + TextView titleView = (TextView) convertView.findViewById(R.id.title); + titleView.setText(activity.loadLabel(packageManager)); + return convertView; + default: + throw new IllegalArgumentException(); + } + } + + public int measureContentWidth() { + // The user may have specified some of the target not to be show but we + // want to measure all of them since after expansion they should fit. + final int oldMaxActivityCount = mMaxActivityCount; + mMaxActivityCount = MAX_ACTIVITY_COUNT_UNLIMITED; + + int contentWidth = 0; + View itemView = null; + + final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int count = getCount(); + + for (int i = 0; i < count; i++) { + itemView = getView(i, itemView, null); + itemView.measure(widthMeasureSpec, heightMeasureSpec); + contentWidth = Math.max(contentWidth, itemView.getMeasuredWidth()); + } + + mMaxActivityCount = oldMaxActivityCount; + + return contentWidth; + } + + public void setMaxActivityCount(int maxActivityCount) { + if (mMaxActivityCount != maxActivityCount) { + mMaxActivityCount = maxActivityCount; + notifyDataSetChanged(); + } + } + + public ResolveInfo getDefaultActivity() { + return mDefaultActivity; + } + + public void setHeaderView(View headerView) { + if (mHeaderView != headerView) { + mHeaderView = headerView; + notifyDataSetChanged(); + } + } + + public void setFooterView(View footerView) { + if (mFooterView != footerView) { + mFooterView = footerView; + notifyDataSetChanged(); + } + } + + public int getActivityCount() { + return mDataModel.getActivityCount(); + } + + public int getMaxActivityCount() { + return mMaxActivityCount; + } + + public ActivityChooserModel getDataModel() { + return mDataModel; + } + } +} diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 1570224..7c0470e 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; +import android.util.Pair; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -33,7 +34,6 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -67,7 +67,7 @@ import static java.lang.Math.min; * * <h4>Default Cell Assignment</h4> * - * If no child specifies the row and column indices of the cell it + * If a child does not specify the row and column indices of the cell it * wishes to occupy, GridLayout assigns cell locations automatically using its: * {@link GridLayout#setOrientation(int) orientation}, * {@link GridLayout#setRowCount(int) rowCount} and @@ -94,8 +94,8 @@ import static java.lang.Math.min; * * Like {@link LinearLayout}, a child's ability to stretch is controlled * using <em>weights</em>, which are specified using the - * {@link GridLayout.LayoutParams#rowWeight rowWeight} and - * {@link GridLayout.LayoutParams#columnWeight columnWeight} layout parameters. + * {@link GridLayout.LayoutParams#widthSpec widthSpec} and + * {@link GridLayout.LayoutParams#heightSpec heightSpec} layout parameters. * <p> * <p> * See {@link GridLayout.LayoutParams} for a full description of the @@ -171,9 +171,7 @@ public class GridLayout extends ViewGroup { private static final String TAG = GridLayout.class.getName(); private static final boolean DEBUG = false; private static final double GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2; - private static final int MIN = 0; private static final int PRF = 1; - private static final int MAX = 2; // Defaults @@ -184,6 +182,7 @@ public class GridLayout extends ViewGroup { private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS; // todo remove this private static final int DEFAULT_CONTAINER_MARGIN = 20; + private static final int MAX_SIZE = 100000; // TypedArray indices @@ -205,36 +204,16 @@ public class GridLayout extends ViewGroup { private int mAlignmentMode = DEFAULT_ALIGNMENT_MODE; private int mDefaultGravity = Gravity.NO_GRAVITY; - /* package */ boolean accommodateBothMinAndMax = false; - // Constructors /** * {@inheritDoc} */ - public GridLayout(Context context) { - this(context, null, 0); - } - - /** - * {@inheritDoc} - */ public GridLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (DEBUG) { setWillNotDraw(false); } - processAttributes(context, attrs); - } - - /** - * {@inheritDoc} - */ - public GridLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - private void processAttributes(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout); try { setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT)); @@ -249,6 +228,20 @@ public class GridLayout extends ViewGroup { } } + /** + * {@inheritDoc} + */ + public GridLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * {@inheritDoc} + */ + public GridLayout(Context context) { + this(context, null); + } + // Implementation /** @@ -527,11 +520,10 @@ public class GridLayout extends ViewGroup { return result; } - private static int sum(float[] a) { - int result = 0; - for (int i = 0, length = a.length; i < length; i++) { - result += a[i]; - } + private static <T> T[] append(T[] a, T[] b) { + T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length); + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); return result; } @@ -603,13 +595,13 @@ public class GridLayout extends ViewGroup { if (isGone(c)) continue; LayoutParams lp = getLayoutParams1(c); - Group colGroup = lp.columnGroup; - Interval cols = colGroup.span; - int colSpan = cols.size(); + final Group colGroup = lp.columnGroup; + final Interval cols = colGroup.span; + final int colSpan = cols.size(); - Group rowGroup = lp.rowGroup; - Interval rows = rowGroup.span; - int rowSpan = rows.size(); + final Group rowGroup = lp.rowGroup; + final Interval rows = rowGroup.span; + final int rowSpan = rows.size(); if (horizontal) { row = valueIfDefined2(rows.min, row); @@ -700,8 +692,8 @@ public class GridLayout extends ViewGroup { } private void drawRectangle(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) { - // x2 = x2 - 1; - // y2 = y2 - 1; + x2 = x2 - 1; + y2 = y2 - 1; graphics.drawLine(x1, y1, x1, y2, paint); graphics.drawLine(x1, y1, x2, y1, paint); graphics.drawLine(x1, y2, x2, y2, paint); @@ -734,9 +726,9 @@ public class GridLayout extends ViewGroup { drawLine(canvas, 0, y, width - 1, y, paint); } } + // Draw bounds paint.setColor(Color.BLUE); - for (int i = 0; i < getChildCount(); i++) { View c = getChildAt(i); drawRectangle(canvas, @@ -748,7 +740,6 @@ public class GridLayout extends ViewGroup { // Draw margins paint.setColor(Color.YELLOW); - for (int i = 0; i < getChildCount(); i++) { View c = getChildAt(i); drawRectangle(canvas, @@ -819,11 +810,11 @@ public class GridLayout extends ViewGroup { protected void onMeasure(int widthSpec, int heightSpec) { measureChildrenWithMargins(widthSpec, heightSpec); - int computedWidth = getPaddingLeft() + mHorizontalAxis.getMin() + getPaddingRight(); - int computedHeight = getPaddingTop() + mVerticalAxis.getMin() + getPaddingBottom(); + int width = getPaddingLeft() + mHorizontalAxis.getMeasure(widthSpec) + getPaddingRight(); + int height = getPaddingTop() + mVerticalAxis.getMeasure(heightSpec) + getPaddingBottom(); - int measuredWidth = Math.max(computedWidth, getSuggestedMinimumWidth()); - int measuredHeight = Math.max(computedHeight, getSuggestedMinimumHeight()); + int measuredWidth = Math.max(width, getSuggestedMinimumWidth()); + int measuredHeight = Math.max(height, getSuggestedMinimumHeight()); setMeasuredDimension( resolveSizeAndState(measuredWidth, widthSpec, 0), @@ -834,12 +825,12 @@ public class GridLayout extends ViewGroup { return (alignment == UNDEFINED) ? 0 : alignment; } - private int getMeasurement(View c, boolean horizontal, int measurementType) { + private int getMeasurement(View c, boolean horizontal) { return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); } - private int getMeasurementIncludingMargin(View c, boolean horizontal, int measurementType) { - int result = getMeasurement(c, horizontal, measurementType); + private int getMeasurementIncludingMargin(View c, boolean horizontal) { + int result = getMeasurement(c, horizontal); if (mAlignmentMode == ALIGN_MARGINS) { return result + getTotalMargin(c, horizontal); } @@ -889,17 +880,17 @@ public class GridLayout extends ViewGroup { Interval colSpan = columnGroup.span; Interval rowSpan = rowGroup.span; - int x1 = mHorizontalAxis.getLocationIncludingMargin(c, true, colSpan.min); - int y1 = mVerticalAxis.getLocationIncludingMargin(c, true, rowSpan.min); + int x1 = mHorizontalAxis.getLocationIncludingMargin(true, colSpan.min); + int y1 = mVerticalAxis.getLocationIncludingMargin(true, rowSpan.min); - int x2 = mHorizontalAxis.getLocationIncludingMargin(c, false, colSpan.max); - int y2 = mVerticalAxis.getLocationIncludingMargin(c, false, rowSpan.max); + int x2 = mHorizontalAxis.getLocationIncludingMargin(false, colSpan.max); + int y2 = mVerticalAxis.getLocationIncludingMargin(false, rowSpan.max); int cellWidth = x2 - x1; int cellHeight = y2 - y1; - int pWidth = getMeasurement(c, true, PRF); - int pHeight = getMeasurement(c, false, PRF); + int pWidth = getMeasurement(c, true); + int pHeight = getMeasurement(c, false); Alignment hAlign = columnGroup.alignment; Alignment vAlign = rowGroup.alignment; @@ -910,9 +901,8 @@ public class GridLayout extends ViewGroup { Bounds rowBounds = mVerticalAxis.getGroupBounds().getValue(i); // Gravity offsets: the location of the alignment group relative to its cell group. - int type = PRF; - int c2ax = protect(hAlign.getAlignmentValue(null, cellWidth - colBounds.size(), type)); - int c2ay = protect(vAlign.getAlignmentValue(null, cellHeight - rowBounds.size(), type)); + int c2ax = protect(hAlign.getAlignmentValue(null, cellWidth - colBounds.size(true))); + int c2ay = protect(vAlign.getAlignmentValue(null, cellHeight - rowBounds.size(true))); if (mAlignmentMode == ALIGN_MARGINS) { int leftMargin = getMargin(c, true, true); @@ -925,8 +915,8 @@ public class GridLayout extends ViewGroup { int mHeight = topMargin + pHeight + bottomMargin; // Alignment offsets: the location of the view relative to its alignment group. - int a2vx = colBounds.getOffset(c, hAlign, type, mWidth); - int a2vy = rowBounds.getOffset(c, vAlign, type, mHeight); + int a2vx = colBounds.getOffset(c, hAlign, mWidth); + int a2vy = rowBounds.getOffset(c, vAlign, mHeight); dx = c2ax + a2vx + leftMargin; dy = c2ay + a2vy + topMargin; @@ -935,13 +925,14 @@ public class GridLayout extends ViewGroup { cellHeight -= topMargin + bottomMargin; } else { // Alignment offsets: the location of the view relative to its alignment group. - int a2vx = colBounds.getOffset(c, hAlign, type, pWidth); - int a2vy = rowBounds.getOffset(c, vAlign, type, pHeight); + int a2vx = colBounds.getOffset(c, hAlign, pWidth); + int a2vy = rowBounds.getOffset(c, vAlign, pHeight); dx = c2ax + a2vx; dy = c2ay + a2vy; } + int type = PRF; int width = hAlign.getSizeInCell(c, pWidth, cellWidth, type); int height = vAlign.getSizeInCell(c, pHeight, cellHeight, type); @@ -962,7 +953,7 @@ public class GridLayout extends ViewGroup { private class Axis { private static final int MIN_VALUE = -1000000; - private static final int UNVISITED = 0; + private static final int NEW = 0; private static final int PENDING = 1; private static final int COMPLETE = 2; @@ -975,8 +966,11 @@ public class GridLayout extends ViewGroup { PackedMap<Group, Bounds> groupBounds; public boolean groupBoundsValid = false; - PackedMap<Interval, MutableInt> spanSizes; - public boolean spanSizesValid = false; + PackedMap<Interval, MutableInt> forwardLinks; + public boolean forwardLinksValid = false; + + PackedMap<Interval, MutableInt> backwardLinks; + public boolean backwardLinksValid = false; public int[] leadingMargins; public boolean leadingMarginsValid = false; @@ -987,14 +981,14 @@ public class GridLayout extends ViewGroup { public Arc[] arcs; public boolean arcsValid = false; - public int[] minima; - public boolean minimaValid = false; - - public float[] weights; public int[] locations; + public boolean locationsValid = false; private boolean mOrderPreserved = DEFAULT_ORDER_PRESERVED; + private MutableInt parentMin = new MutableInt(0); + private MutableInt parentMax = new MutableInt(-MAX_SIZE); + private Axis(boolean horizontal) { this.horizontal = horizontal; } @@ -1036,22 +1030,19 @@ public class GridLayout extends ViewGroup { } private PackedMap<Group, Bounds> createGroupBounds() { - int N = getChildCount(); - Group[] groups = new Group[N]; - Arrays.fill(groups, Group.GONE); - Bounds[] bounds = new Bounds[N]; - Arrays.fill(bounds, Bounds.GONE); - for (int i = 0; i < N; i++) { + Assoc<Group, Bounds> assoc = Assoc.of(Group.class, Bounds.class); + for (int i = 0, N = getChildCount(); i < N; i++) { View c = getChildAt(i); - if (isGone(c)) continue; - LayoutParams lp = getLayoutParams(c); - Group group = horizontal ? lp.columnGroup : lp.rowGroup; - - groups[i] = group; - bounds[i] = group.alignment.getBounds(); + if (isGone(c)) { + assoc.put(Group.GONE, Bounds.GONE); + } else { + LayoutParams lp = getLayoutParams(c); + Group group = horizontal ? lp.columnGroup : lp.rowGroup; + Bounds bounds = group.alignment.getBounds(); + assoc.put(group, bounds); + } } - - return new PackedMap<Group, Bounds>(groups, bounds); + return assoc.pack(); } private void computeGroupBounds() { @@ -1064,13 +1055,7 @@ public class GridLayout extends ViewGroup { if (isGone(c)) continue; LayoutParams lp = getLayoutParams(c); Group g = horizontal ? lp.columnGroup : lp.rowGroup; - - Bounds bounds = groupBounds.getValue(i); - - int size = getMeasurementIncludingMargin(c, horizontal, PRF); - // todo test this works correctly when the returned value is UNDEFINED - int before = g.alignment.getAlignmentValue(c, size, PRF); - bounds.include(before, size - before); + groupBounds.getValue(i).include(c, g, GridLayout.this, this, lp); } } @@ -1086,80 +1071,91 @@ public class GridLayout extends ViewGroup { } // Add values computed by alignment - taking the max of all alignments in each span - private PackedMap<Interval, MutableInt> createSpanSizes() { - PackedMap<Group, Bounds> groupBounds = getGroupBounds(); - int N = groupBounds.keys.length; - Interval[] spans = new Interval[N]; - MutableInt[] values = new MutableInt[N]; - for (int i = 0; i < N; i++) { - Interval key = groupBounds.keys[i].span; - - spans[i] = key; - values[i] = new MutableInt(); + private PackedMap<Interval, MutableInt> createLinks(boolean min) { + Assoc<Interval, MutableInt> result = Assoc.of(Interval.class, MutableInt.class); + Group[] keys = getGroupBounds().keys; + for (int i = 0, N = keys.length; i < N; i++) { + Interval span = min ? keys[i].span : keys[i].span.inverse(); + result.put(span, new MutableInt()); } - return new PackedMap<Interval, MutableInt>(spans, values); + return result.pack(); } - private void computeSpanSizes() { - MutableInt[] spans = spanSizes.values; + private void computeLinks(PackedMap<Interval, MutableInt> links, boolean min) { + MutableInt[] spans = links.values; for (int i = 0; i < spans.length; i++) { spans[i].reset(); } - Bounds[] bounds = getGroupBounds().values; // use getter to trigger a re-evaluation + // use getter to trigger a re-evaluation + Bounds[] bounds = getGroupBounds().values; for (int i = 0; i < bounds.length; i++) { - int value = bounds[i].size(); - - MutableInt valueHolder = spanSizes.getValue(i); + int size = bounds[i].size(min); + int value = min ? size : -size; + MutableInt valueHolder = links.getValue(i); valueHolder.value = max(valueHolder.value, value); } } - private PackedMap<Interval, MutableInt> getSpanSizes() { - if (spanSizes == null) { - spanSizes = createSpanSizes(); + private PackedMap<Interval, MutableInt> getForwardLinks() { + if (forwardLinks == null) { + forwardLinks = createLinks(true); } - if (!spanSizesValid) { - computeSpanSizes(); - spanSizesValid = true; + if (!forwardLinksValid) { + computeLinks(forwardLinks, true); + forwardLinksValid = true; } - return spanSizes; + return forwardLinks; } - private void include(List<Arc> arcs, Interval key, MutableInt size) { - // this bit below should really be computed outside here - - // its just to stop default (col>0) constraints obliterating valid entries - for (Arc arc : arcs) { - Interval span = arc.span; - if (span.equals(key)) { - return; - } + private PackedMap<Interval, MutableInt> getBackwardLinks() { + if (backwardLinks == null) { + backwardLinks = createLinks(false); } - arcs.add(new Arc(key, size)); + if (!backwardLinksValid) { + computeLinks(backwardLinks, false); + backwardLinksValid = true; + } + return backwardLinks; } - private void include2(List<Arc> arcs, Interval span, MutableInt min, MutableInt max, - boolean both) { - include(arcs, span, min); - if (both) { - // todo -// include(arcs, span.inverse(), max.neg()); + private void include(List<Arc> arcs, Interval key, MutableInt size, + boolean ignoreIfAlreadyPresent) { + /* + Remove self referential links. + These appear: + . as parental constraints when GridLayout has no children + . when components have been marked as GONE + */ + if (key.size() == 0) { + return; } + // this bit below should really be computed outside here - + // its just to stop default (row/col > 0) constraints obliterating valid entries + if (ignoreIfAlreadyPresent) { + for (Arc arc : arcs) { + Interval span = arc.span; + if (span.equals(key)) { + return; + } + } + } + arcs.add(new Arc(key, size)); } - private void include2(List<Arc> arcs, Interval span, int min, int max, boolean both) { - include2(arcs, span, new MutableInt(min), new MutableInt(max), both); + private void include(List<Arc> arcs, Interval key, MutableInt size) { + include(arcs, key, size, true); } // Group arcs by their first vertex, returning an array of arrays. // This is linear in the number of arcs. private Arc[][] groupArcsByFirstVertex(Arc[] arcs) { - int N = getCount() + 1;// the number of vertices + int N = getCount() + 1; // the number of vertices Arc[][] result = new Arc[N][]; int[] sizes = new int[N]; for (Arc arc : arcs) { sizes[arc.span.min]++; - } + } for (int i = 0; i < sizes.length; i++) { result[i] = new Arc[sizes[i]]; } @@ -1173,38 +1169,46 @@ public class GridLayout extends ViewGroup { return result; } - private Arc[] topologicalSort(final Arc[] arcs, int start) { - // todo ensure the <start> vertex is added in edge cases - final List<Arc> result = new ArrayList<Arc>(); - new Object() { - Arc[][] arcsByFirstVertex = groupArcsByFirstVertex(arcs); + private Arc[] topologicalSort(final Arc[] arcs) { + return new Object() { + Arc[] result = new Arc[arcs.length]; + int cursor = result.length - 1; + Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs); int[] visited = new int[getCount() + 1]; - boolean completesCycle(int loc) { - int state = visited[loc]; - if (state == UNVISITED) { - visited[loc] = PENDING; - for (Arc arc : arcsByFirstVertex[loc]) { - Interval span = arc.span; - // the recursive call - if (completesCycle(span.max)) { - // which arcs get set here is dependent on the order - // in which we explore nodes - arc.completesCycle = true; + void walk(int loc) { + switch (visited[loc]) { + case NEW: { + visited[loc] = PENDING; + for (Arc arc : arcsByVertex[loc]) { + walk(arc.span.max); + result[cursor--] = arc; } - result.add(arc); + visited[loc] = COMPLETE; + break; + } + case PENDING: { + assert false; + break; + } + case COMPLETE: { + break; } - visited[loc] = COMPLETE; - } else if (state == PENDING) { - return true; - } else if (state == COMPLETE) { } - return false; } - }.completesCycle(start); - Collections.reverse(result); - assert arcs.length == result.size(); - return result.toArray(new Arc[result.size()]); + + Arc[] sort() { + for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) { + walk(loc); + } + assert cursor == -1; + return result; + } + }.sort(); + } + + private Arc[] topologicalSort(List<Arc> arcs) { + return topologicalSort(arcs.toArray(new Arc[arcs.size()])); } private boolean[] findUsed(Collection<Arc> arcs) { @@ -1254,43 +1258,64 @@ public class GridLayout extends ViewGroup { return result; } + private void addComponentSizes(List<Arc> result, PackedMap<Interval, MutableInt> links) { + for (int i = 0; i < links.keys.length; i++) { + Interval key = links.keys[i]; + include(result, key, links.values[i], false); + } + } + private Arc[] createArcs() { - List<Arc> result = new ArrayList<Arc>(); + List<Arc> mins = new ArrayList<Arc>(); + List<Arc> maxs = new ArrayList<Arc>(); - // Add all the preferred elements that were not defined by the user. - PackedMap<Interval, MutableInt> spanSizes = getSpanSizes(); - for (int i = 0; i < spanSizes.keys.length; i++) { - Interval key = spanSizes.keys[i]; - if (key == Interval.GONE) continue; - MutableInt value = spanSizes.values[i]; - // todo remove value duplicate - include2(result, key, value, value, accommodateBothMinAndMax); - } + // Add the minimum values from the components. + addComponentSizes(mins, getForwardLinks()); + // Add the maximum values from the components. + addComponentSizes(maxs, getBackwardLinks()); // Find redundant rows/cols and glue them together with 0-length arcs to link the tree - boolean[] used = findUsed(result); + boolean[] used = findUsed(mins); for (int i = 0; i < getCount(); i++) { if (!used[i]) { Interval span = new Interval(i, i + 1); - include(result, span, new MutableInt(0)); - include(result, span.inverse(), new MutableInt(0)); + include(mins, span, new MutableInt(0)); + include(maxs, span.inverse(), new MutableInt(0)); } } + // Add ordering constraints to prevent row/col sizes from going negative if (mOrderPreserved) { - // Add preferred gaps + // Add a constraint for every row/col for (int i = 0; i < getCount(); i++) { if (used[i]) { - include2(result, new Interval(i, i + 1), 0, 0, false); + include(mins, new Interval(i, i + 1), new MutableInt(0)); } } } else { + // Add a constraint for each row/col that separates opposing component edges for (Interval gap : getSpacers()) { - include2(result, gap, 0, 0, false); + include(mins, gap, new MutableInt(0)); } } - Arc[] arcs = result.toArray(new Arc[result.size()]); - return topologicalSort(arcs, 0); + + // Add the container constraints. Use the version of include that allows + // duplicate entries in case a child spans the entire grid. + int N = getCount(); + include(mins, new Interval(0, N), parentMin, false); + include(maxs, new Interval(N, 0), parentMax, false); + + // Sort + Arc[] sMins = topologicalSort(mins); + Arc[] sMaxs = topologicalSort(maxs); + + return append(sMins, sMaxs); + } + + private void computeArcs() { + // getting the links validates the values that are shared by the arc list + getForwardLinks(); + getBackwardLinks(); } public Arc[] getArcs() { @@ -1298,13 +1323,16 @@ public class GridLayout extends ViewGroup { arcs = createArcs(); } if (!arcsValid) { - getSpanSizes(); + computeArcs(); arcsValid = true; } return arcs; } private boolean relax(int[] locations, Arc entry) { + if (!entry.valid) { + return false; + } Interval span = entry.span; int u = span.min; int v = span.max; @@ -1351,7 +1379,8 @@ public class GridLayout extends ViewGroup { typical layout problems complete after the first iteration and the algorithm completes in O(N) steps with very low constants. */ - private int[] solve(Arc[] arcs, int[] locations) { + private void solve(Arc[] arcs, int[] locations) { + String axis = horizontal ? "horizontal" : "vertical"; int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. boolean changed = false; @@ -1359,20 +1388,44 @@ public class GridLayout extends ViewGroup { for (int i = 0; i < N; i++) { changed = false; for (int j = 0, length = arcs.length; j < length; j++) { - changed = changed | relax(locations, arcs[j]); + changed |= relax(locations, arcs[j]); } if (!changed) { if (DEBUG) { - Log.d(TAG, "Iteration " + - " completed after " + (1 + i) + " steps out of " + N); + Log.d(TAG, axis + " iteration completed in " + (1 + i) + " steps of " + N); } - break; + return; } } - if (changed) { - Log.d(TAG, "*** Algorithm failed to terminate ***"); + + Log.d(TAG, "The " + axis + " constraints contained a contradiction. Resolving... "); + Log.d(TAG, Arrays.toString(arcs)); + + boolean[] culprits = new boolean[arcs.length]; + for (int i = 0; i < N; i++) { + for (int j = 0, length = arcs.length; j < length; j++) { + culprits[j] |= relax(locations, arcs[j]); + } } - return locations; + for (int i = 0; i < culprits.length; i++) { + if (culprits[i]) { + Arc arc = arcs[i]; + // Only remove max values, min values alone cannot be inconsistent + if (arc.span.min < arc.span.max) { + continue; + } + Log.d(TAG, "Removing: " + arc); + arc.valid = false; + break; + } + } + solve1(arcs, locations); + } + + private void solve1(Arc[] arcs, int[] a) { + Arrays.fill(a, MIN_VALUE); + a[0] = 0; + solve(arcs, a); } private void computeMargins(boolean leading) { @@ -1418,11 +1471,11 @@ public class GridLayout extends ViewGroup { for (int i = 0, N = getCount(); i < N; i++) { int margins = leadingMargins[i] + trailingMargins[i + 1]; delta += margins; - minima[i + 1] += delta; + locations[i + 1] += delta; } } - private int getLocationIncludingMargin(View view, boolean leading, int index) { + private int getLocationIncludingMargin(boolean leading, int index) { int location = locations[index]; int margin; if (mAlignmentMode != ALIGN_MARGINS) { @@ -1433,53 +1486,22 @@ public class GridLayout extends ViewGroup { return leading ? (location + margin) : (location - margin); } - private void computeMinima(int[] a) { - Arrays.fill(a, MIN_VALUE); - a[0] = 0; - solve(getArcs(), a); + private void computeLocations(int[] a) { + solve1(getArcs(), a); if (mAlignmentMode != ALIGN_MARGINS) { addMargins(); } } - private int[] getMinima() { - if (minima == null) { - int N = getCount() + 1; - minima = new int[N]; - } - if (!minimaValid) { - computeMinima(minima); - minimaValid = true; - } - return minima; - } - - private void computeWeights() { - for (int i = 0, N = getChildCount(); i < N; i++) { - View c = getChildAt(i); - if (isGone(c)) continue; - LayoutParams lp = getLayoutParams(c); - Group g = horizontal ? lp.columnGroup : lp.rowGroup; - Interval span = g.span; - int penultimateIndex = span.max - 1; - weights[penultimateIndex] += horizontal ? lp.columnWeight : lp.rowWeight; - } - } - - private float[] getWeights() { - if (weights == null) { - int N = getCount(); - weights = new float[N]; - } - computeWeights(); - return weights; - } - private int[] getLocations() { if (locations == null) { int N = getCount() + 1; locations = new int[N]; } + if (!locationsValid) { + computeLocations(locations); + locationsValid = true; + } return locations; } @@ -1489,48 +1511,53 @@ public class GridLayout extends ViewGroup { return max2(locations, 0) - locations[0]; } - private int getMin() { - return size(getMinima()); + private void setParentConstraints(int min, int max) { + parentMin.value = min; + parentMax.value = -max; + locationsValid = false; } - private void layout(int targetSize) { - int[] mins = getMinima(); - - int totalDelta = max(0, targetSize - size(mins)); // confine to expansion - - float[] weights = getWeights(); - float totalWeight = sum(weights); + private int getMeasure(int min, int max) { + setParentConstraints(min, max); + return size(getLocations()); + } - if (totalWeight == 0f && weights.length > 0) { - weights[weights.length - 1] = 1; - totalWeight = 1; + private int getMeasure(int measureSpec) { + int mode = MeasureSpec.getMode(measureSpec); + int size = MeasureSpec.getSize(measureSpec); + switch (mode) { + case MeasureSpec.UNSPECIFIED: { + return getMeasure(0, MAX_SIZE); + } + case MeasureSpec.EXACTLY: { + return getMeasure(size, size); + } + case MeasureSpec.AT_MOST: { + return getMeasure(0, size); + } + default: { + assert false; + return 0; + } } + } - int[] locations = getLocations(); - int cumulativeDelta = 0; - - // note |weights| = |locations| - 1 - for (int i = 0; i < weights.length; i++) { - float weight = weights[i]; - int delta = (int) (totalDelta * weight / totalWeight); - cumulativeDelta += delta; - locations[i + 1] = mins[i + 1] + cumulativeDelta; - - totalDelta -= delta; - totalWeight -= weight; - } + private void layout(int size) { + setParentConstraints(size, size); + getLocations(); } private void invalidateStructure() { countValid = false; groupBounds = null; - spanSizes = null; + forwardLinks = null; + backwardLinks = null; + leadingMargins = null; trailingMargins = null; arcs = null; - minima = null; - weights = null; + locations = null; invalidateValues(); @@ -1538,11 +1565,14 @@ public class GridLayout extends ViewGroup { private void invalidateValues() { groupBoundsValid = false; - spanSizesValid = false; - arcsValid = false; + forwardLinksValid = false; + backwardLinksValid = false; + leadingMarginsValid = false; trailingMarginsValid = false; - minimaValid = false; + arcsValid = false; + + locationsValid = false; } } @@ -1592,16 +1622,16 @@ public class GridLayout extends ViewGroup { * <li>{@link #rowGroup}{@code .alignment} = {@link #BASELINE} </li> * <li>{@link #columnGroup}{@code .span} = {@code [0, 1]} </li> * <li>{@link #columnGroup}{@code .alignment} = {@link #LEFT} </li> - * <li>{@link #rowWeight} = {@code 0f} </li> - * <li>{@link #columnWeight} = {@code 0f} </li> + * <li>{@link #widthSpec} = {@link #FIXED} </li> + * <li>{@link #heightSpec} = {@link #FIXED} </li> * </ul> * * @attr ref android.R.styleable#GridLayout_Layout_layout_row * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan - * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight + * @attr ref android.R.styleable#GridLayout_Layout_layout_heightSpec * @attr ref android.R.styleable#GridLayout_Layout_layout_column * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan - * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight + * @attr ref android.R.styleable#GridLayout_Layout_layout_widthSpec * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity */ public static class LayoutParams extends MarginLayoutParams { @@ -1621,14 +1651,15 @@ public class GridLayout extends ViewGroup { new Group(DEFAULT_SPAN, DEFAULT_COLUMN_ALIGNMENT); private static final Group DEFAULT_ROW_GROUP = new Group(DEFAULT_SPAN, DEFAULT_ROW_ALIGNMENT); - private static final int DEFAULT_WEIGHT_0 = 0; - private static final int DEFAULT_WEIGHT_1 = 1; + private static final Spec DEFAULT_SPEC = FIXED; + private static final int DEFAULT_SPEC_INDEX = 0; // Misc private static final Rect CONTAINER_BOUNDS = new Rect(0, 0, 2, 2); private static final Alignment[] COLUMN_ALIGNMENTS = { LEFT, CENTER, RIGHT }; private static final Alignment[] ROW_ALIGNMENTS = { TOP, CENTER, BOTTOM }; + private static final Spec[] SPECS = { FIXED, CAN_SHRINK, CAN_STRETCH }; // TypedArray indices @@ -1641,10 +1672,10 @@ public class GridLayout extends ViewGroup { private static final int COLUMN = styleable.GridLayout_Layout_layout_column; private static final int COLUMN_SPAN = styleable.GridLayout_Layout_layout_columnSpan; - private static final int COLUMN_WEIGHT = styleable.GridLayout_Layout_layout_columnWeight; + private static final int WIDTH_SPEC = styleable.GridLayout_Layout_layout_widthSpec; private static final int ROW = styleable.GridLayout_Layout_layout_row; private static final int ROW_SPAN = styleable.GridLayout_Layout_layout_rowSpan; - private static final int ROW_WEIGHT = styleable.GridLayout_Layout_layout_rowWeight; + private static final int HEIGHT_SPEC = styleable.GridLayout_Layout_layout_heightSpec; private static final int GRAVITY = styleable.GridLayout_Layout_layout_gravity; // Instance variables @@ -1660,28 +1691,29 @@ public class GridLayout extends ViewGroup { */ public Group columnGroup; /** - * The proportional space that should be taken by the associated row group + * The proportional space that should be taken by the associated column group * during excess space distribution. */ - public float rowWeight; + public Spec widthSpec; /** - * The proportional space that should be taken by the associated column group + * The proportional space that should be taken by the associated row group * during excess space distribution. */ - public float columnWeight; + public Spec heightSpec; // Constructors private LayoutParams( int width, int height, int left, int top, int right, int bottom, - Group rowGroup, Group columnGroup, float rowWeight, float columnWeight) { + Group rowGroup, Group columnGroup, + Spec widthSpec, Spec heightSpec) { super(width, height); setMargins(left, top, right, bottom); this.rowGroup = rowGroup; this.columnGroup = columnGroup; - this.rowWeight = rowWeight; - this.columnWeight = columnWeight; + this.heightSpec = heightSpec; + this.widthSpec = widthSpec; } /** @@ -1695,7 +1727,7 @@ public class GridLayout extends ViewGroup { public LayoutParams(Group rowGroup, Group columnGroup) { this(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, - rowGroup, columnGroup, DEFAULT_WEIGHT_0, DEFAULT_WEIGHT_0); + rowGroup, columnGroup, DEFAULT_SPEC, DEFAULT_SPEC); } /** @@ -1728,8 +1760,8 @@ public class GridLayout extends ViewGroup { super(that); this.columnGroup = that.columnGroup; this.rowGroup = that.rowGroup; - this.columnWeight = that.columnWeight; - this.rowWeight = that.rowWeight; + this.widthSpec = that.widthSpec; + this.heightSpec = that.heightSpec; } // AttributeSet constructors @@ -1813,11 +1845,6 @@ public class GridLayout extends ViewGroup { !definesVertical(gravity), defaultAlignment); } - private int getDefaultWeight(int size) { - //return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0; - return DEFAULT_WEIGHT_0; - } - private void init(Context context, AttributeSet attrs, int defaultGravity) { TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout_Layout); try { @@ -1827,13 +1854,13 @@ public class GridLayout extends ViewGroup { int columnSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); Interval hSpan = new Interval(column, column + columnSpan); this.columnGroup = new Group(hSpan, getColumnAlignment(gravity, width)); - this.columnWeight = a.getFloat(COLUMN_WEIGHT, getDefaultWeight(width)); + this.widthSpec = SPECS[a.getInt(WIDTH_SPEC, DEFAULT_SPEC_INDEX)]; int row = a.getInt(ROW, DEFAULT_ROW); int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); Interval vSpan = new Interval(row, row + rowSpan); this.rowGroup = new Group(vSpan, getRowAlignment(gravity, height)); - this.rowWeight = a.getFloat(ROW_WEIGHT, getDefaultWeight(height)); + this.heightSpec = SPECS[a.getInt(HEIGHT_SPEC, DEFAULT_SPEC_INDEX)]; } finally { a.recycle(); } @@ -1874,7 +1901,7 @@ public class GridLayout extends ViewGroup { private static class Arc { public final Interval span; public final MutableInt value; - public boolean completesCycle; + public boolean valid = true; public Arc(Interval span, MutableInt value) { this.span = span; @@ -1883,7 +1910,7 @@ public class GridLayout extends ViewGroup { @Override public String toString() { - return span + " " + (completesCycle ? "+>" : "->") + " " + value; + return span + " " + (!valid ? "+>" : "->") + " " + value; } } @@ -1903,6 +1930,41 @@ public class GridLayout extends ViewGroup { private void reset() { value = Integer.MIN_VALUE; } + + @Override + public String toString() { + return Integer.toString(value); + } + } + + private static class Assoc<K, V> extends ArrayList<Pair<K, V>> { + private final Class<K> keyType; + private final Class<V> valueType; + + private Assoc(Class<K> keyType, Class<V> valueType) { + this.keyType = keyType; + this.valueType = valueType; + } + + private static <K, V> Assoc<K, V> of(Class<K> keyType, Class<V> valueType) { + return new Assoc<K, V>(keyType, valueType); + } + + public void put(K key, V value) { + add(Pair.create(key, value)); + } + + @SuppressWarnings(value = "unchecked") + public PackedMap<K, V> pack() { + int N = size(); + K[] keys = (K[]) Array.newInstance(keyType, N); + V[] values = (V[]) Array.newInstance(valueType, N); + for (int i = 0; i < N; i++) { + keys[i] = get(i).first; + values[i] = get(i).second; + } + return new PackedMap<K, V>(keys, values); + } } /* @@ -1989,6 +2051,7 @@ public class GridLayout extends ViewGroup { public int before; public int after; + public boolean canStretch; private Bounds() { reset(); @@ -1997,6 +2060,7 @@ public class GridLayout extends ViewGroup { protected void reset() { before = Integer.MIN_VALUE; after = Integer.MIN_VALUE; + canStretch = false; } protected void include(int before, int after) { @@ -2004,12 +2068,26 @@ public class GridLayout extends ViewGroup { this.after = max(this.after, after); } - protected int size() { + protected int size(boolean min) { + if (!min && canStretch) { + return MAX_SIZE; + } return before + after; } - protected int getOffset(View c, Alignment alignment, int type, int size) { - return before - alignment.getAlignmentValue(c, size, type); + protected int getOffset(View c, Alignment alignment, int size) { + return before - alignment.getAlignmentValue(c, size); + } + + protected void include(View c, Group g, GridLayout gridLayout, Axis axis, LayoutParams lp) { + Spec spec = axis.horizontal ? lp.widthSpec : lp.heightSpec; + if (spec == CAN_STRETCH) { + canStretch = true; + } + int size = gridLayout.getMeasurementIncludingMargin(c, axis.horizontal); + // todo test this works correctly when the returned value is UNDEFINED + int before = g.alignment.getAlignmentValue(c, size); + include(before, size - before); } @Override @@ -2032,7 +2110,7 @@ public class GridLayout extends ViewGroup { * Intervals are often written as {@code [min, max]} and represent the set of values * {@code x} such that {@code min <= x < max}. */ - /* package */ static class Interval { + static class Interval { private static final Interval GONE = new Interval(UNDEFINED, UNDEFINED); /** @@ -2129,7 +2207,7 @@ public class GridLayout extends ViewGroup { * See {@link GridLayout} for a description of the conventions used by GridLayout * for grid indices. */ - /* package */ final Interval span; + final Interval span; /** * Specifies how cells should be aligned in this group. * For row groups, this specifies the vertical alignment. @@ -2147,7 +2225,7 @@ public class GridLayout extends ViewGroup { * @param span the span * @param alignment the alignment */ - /* package */ Group(Interval span, Alignment alignment) { + Group(Interval span, Alignment alignment) { this.span = span; this.alignment = alignment; } @@ -2248,17 +2326,16 @@ public class GridLayout extends ViewGroup { * so that the locations defined by the alignment values * are the same for all of the views in a group. * <p> - */ public static abstract class Alignment { private static final Alignment GONE = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { assert false; return 0; } }; - /*pp*/ Alignment() { + Alignment() { } /** @@ -2269,12 +2346,9 @@ public class GridLayout extends ViewGroup { * * @param view the view to which this alignment should be applied * @param viewSize the measured size of the view - * @param measurementType This parameter is currently unused as GridLayout only supports - * one type of measurement: {@link View#measure(int, int)}. - * * @return the alignment value */ - /*pp*/ abstract int getAlignmentValue(View view, int viewSize, int measurementType); + abstract int getAlignmentValue(View view, int viewSize); /** * Returns the size of the view specified by this alignment. @@ -2291,24 +2365,24 @@ public class GridLayout extends ViewGroup { * * @return the aligned size */ - /*pp*/ int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) { + int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) { return viewSize; } - /*pp*/ Bounds getBounds() { + Bounds getBounds() { return new Bounds(); } } private static final Alignment LEADING = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { return 0; } }; private static final Alignment TRAILING = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { return viewSize; } }; @@ -2343,7 +2417,7 @@ public class GridLayout extends ViewGroup { * LayoutParams#columnGroup columnGroups}. */ public static final Alignment CENTER = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { return viewSize >> 1; } }; @@ -2356,7 +2430,7 @@ public class GridLayout extends ViewGroup { * @see View#getBaseline() */ public static final Alignment BASELINE = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { if (view == null) { return UNDEFINED; } @@ -2378,7 +2452,7 @@ public class GridLayout extends ViewGroup { @Override protected void reset() { super.reset(); - size = 0; + size = Integer.MIN_VALUE; } @Override @@ -2388,13 +2462,13 @@ public class GridLayout extends ViewGroup { } @Override - protected int size() { - return max(super.size(), size); + protected int size(boolean min) { + return max(super.size(min), size); } @Override - protected int getOffset(View c, Alignment alignment, int type, int size) { - return max(0, super.getOffset(c, alignment, type, size)); + protected int getOffset(View c, Alignment alignment, int size) { + return max(0, super.getOffset(c, alignment, size)); } }; } @@ -2406,7 +2480,7 @@ public class GridLayout extends ViewGroup { * {@link LayoutParams#columnGroup columnGroups}. */ public static final Alignment FILL = new Alignment() { - public int getAlignmentValue(View view, int viewSize, int measurementType) { + public int getAlignmentValue(View view, int viewSize) { return UNDEFINED; } @@ -2415,4 +2489,41 @@ public class GridLayout extends ViewGroup { return cellSize; } }; + + /** + * Spec's tell GridLayout how to derive minimum and maximum size values for a + * component. Specifications are made with respect to a child's 'measured size'. + * A child's measured size is, in turn, controlled by its height and width + * layout parameters which either specify a size or, in the case of + * WRAP_CONTENT, defer to the computed size of the component. + */ + public static abstract class Spec { + } + + /** + * Indicates that a view requests precisely the size specified by its layout parameters. + * + * @see Spec + */ + public static final Spec FIXED = new Spec() { + }; + + /** + * Indicates that a view's size should lie between its minimum and the size specified by + * its layout parameters. + * + * @see Spec + */ + public static final Spec CAN_SHRINK = new Spec() { + }; + + /** + * Indicates that a view's size should be greater than or equal to the size specified by + * its layout parameters. + * + * @see Spec + */ + public static final Spec CAN_STRETCH = new Spec() { + }; + } diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java new file mode 100644 index 0000000..d6e426f --- /dev/null +++ b/core/java/android/widget/ShareActionProvider.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.util.TypedValue; +import android.view.ActionProvider; +import android.view.MenuItem; +import android.view.View; + +import com.android.internal.R; + +/** + * This is a provider for a share action. It is responsible for creating views + * that enable data sharing and also to perform a default action for showing + * a share dialog. + * <p> + * Here is how to use the action provider with custom backing file in a {@link MenuItem}: + * </p> + * <p> + * <pre> + * <code> + * // In Activity#onCreateOptionsMenu + * public boolean onCreateOptionsMenu(Menu menu) { + * // Get the menu item. + * MenuItem menuItem = menu.findItem(R.id.my_menu_item); + * // Get the provider and hold onto it to set/change the share intent. + * mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider(); + * // Set history different from the default before getting the action + * // view since a call to {@link MenuItem#getActionView() MenuItem.getActionView()} calls + * // {@link ActionProvider#onCreateActionView()} which uses the backing file name. Omit this + * // line if using the default share history file is desired. + * mShareActionProvider.setShareHistoryFileName("custom_share_history.xml"); + * // Get the action view and hold onto it to set the share intent. + * mActionView = menuItem.getActionView(); + * . . . + * } + * + * // Somewhere in the application. + * public void doShare(Intent shareIntent) { + * // When you want to share set the share intent. + * mShareActionProvider.setShareIntent(mActionView, shareIntent); + * } + * </pre> + * </code> + * </p> + * <p> + * <strong>Note:</strong> While the sample snippet demonstrates how to use this provider + * in the context of a menu item, the use of the provider is not limited to menu items. + * </p> + * + * @see ActionProvider + */ +public class ShareActionProvider extends ActionProvider { + + /** + * The default name for storing share history. + */ + public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml"; + + private final Context mContext; + private String mShareHistoryFileName = DEFAULT_SHARE_HISTORY_FILE_NAME; + + /** + * Creates a new instance. + * + * @param context Context for accessing resources. + */ + public ShareActionProvider(Context context) { + super(context); + mContext = context; + } + + /** + * {@inheritDoc} + */ + @Override + public View onCreateActionView() { + ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName); + ActivityChooserView activityChooserView = new ActivityChooserView(mContext); + activityChooserView.setActivityChooserModel(dataModel); + TypedValue outTypedValue = new TypedValue(); + mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true); + Drawable drawable = mContext.getResources().getDrawable(outTypedValue.resourceId); + activityChooserView.setExpandActivityOverflowButtonDrawable(drawable); + return activityChooserView; + } + + /** + * {@inheritDoc} + */ + @Override + public void onPerformDefaultAction(View actionView) { + if (actionView instanceof ActivityChooserView) { + ActivityChooserView activityChooserView = (ActivityChooserView) actionView; + activityChooserView.showPopup(); + } else { + throw new IllegalArgumentException("actionView not instance of ActivityChooserView"); + } + } + + /** + * Sets the file name of a file for persisting the share history which + * history will be used for ordering share targets. This file will be used + * for all view created by {@link #onCreateActionView()}. Defaults to + * {@link #DEFAULT_SHARE_HISTORY_FILE_NAME}. Set to <code>null</code> + * if share history should not be persisted between sessions. + * <p> + * <strong>Note:</strong> The history file name can be set any time, however + * only the action views created by {@link #onCreateActionView()} after setting + * the file name will be backed by the provided file. + * <p> + * + * @param shareHistoryFile The share history file name. + */ + public void setShareHistoryFileName(String shareHistoryFile) { + mShareHistoryFileName = shareHistoryFile; + } + + /** + * Sets an intent with information about the share action. Here is a + * sample for constructing a share intent: + * <p> + * <pre> + * <code> + * Intent shareIntent = new Intent(Intent.ACTION_SEND); + * shareIntent.setType("image/*"); + * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg")); + * shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString()); + * </pre> + * </code> + * </p> + * + * @param actionView An action view created by {@link #onCreateActionView()}. + * @param shareIntent The share intent. + * + * @see Intent#ACTION_SEND + * @see Intent#ACTION_SEND_MULTIPLE + */ + public void setShareIntent(View actionView, Intent shareIntent) { + if (actionView instanceof ActivityChooserView) { + ActivityChooserView activityChooserView = (ActivityChooserView) actionView; + activityChooserView.getDataModel().setIntent(shareIntent); + } else { + throw new IllegalArgumentException("actionView not instance of ActivityChooserView"); + } + } +} diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 2a70ac8..c91f1a6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -340,6 +340,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private WordIterator mWordIterator; + // The alignment to pass to Layout, or null if not resolved. + private Layout.Alignment mLayoutAlignment; + + // The default value for mTextAlign. + private TextAlign mTextAlign = TextAlign.INHERIT; + + private static enum TextAlign { + INHERIT, GRAVITY, TEXT_START, TEXT_END, CENTER, VIEW_START, VIEW_END; + } + /* * Kick-start the font cache for the zygote process (to pay the cost of * initializing freetype for our default font only once). @@ -5532,6 +5542,73 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener physicalWidth, false); } + @Override + protected void resetLayoutDirectionResolution() { + super.resetLayoutDirectionResolution(); + + if (mLayoutAlignment != null && + (mTextAlign == TextAlign.VIEW_START || + mTextAlign == TextAlign.VIEW_END)) { + mLayoutAlignment = null; + } + } + + private Layout.Alignment getLayoutAlignment() { + if (mLayoutAlignment == null) { + Layout.Alignment alignment; + TextAlign textAlign = mTextAlign; + switch (textAlign) { + case INHERIT: + // fall through to gravity temporarily + // intention is to inherit value through view hierarchy. + case GRAVITY: + switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { + case Gravity.START: + alignment = Layout.Alignment.ALIGN_NORMAL; + break; + case Gravity.END: + alignment = Layout.Alignment.ALIGN_OPPOSITE; + break; + case Gravity.LEFT: + alignment = Layout.Alignment.ALIGN_LEFT; + break; + case Gravity.RIGHT: + alignment = Layout.Alignment.ALIGN_RIGHT; + break; + case Gravity.CENTER_HORIZONTAL: + alignment = Layout.Alignment.ALIGN_CENTER; + break; + default: + alignment = Layout.Alignment.ALIGN_NORMAL; + break; + } + break; + case TEXT_START: + alignment = Layout.Alignment.ALIGN_NORMAL; + break; + case TEXT_END: + alignment = Layout.Alignment.ALIGN_OPPOSITE; + break; + case CENTER: + alignment = Layout.Alignment.ALIGN_CENTER; + break; + case VIEW_START: + alignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; + break; + case VIEW_END: + alignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; + break; + default: + alignment = Layout.Alignment.ALIGN_NORMAL; + break; + } + mLayoutAlignment = alignment; + } + return mLayoutAlignment; + } + /** * The width passed in is now the desired layout width, * not the full view width with padding. @@ -5552,25 +5629,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hintWidth = 0; } - final int layoutDirection = getResolvedLayoutDirection(); - final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); - - Layout.Alignment alignment; - switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { - case Gravity.CENTER_HORIZONTAL: - alignment = Layout.Alignment.ALIGN_CENTER; - break; - - case Gravity.RIGHT: - // Note, Layout resolves ALIGN_OPPOSITE to left or - // right based on the paragraph direction. - alignment = Layout.Alignment.ALIGN_OPPOSITE; - break; - - default: - alignment = Layout.Alignment.ALIGN_NORMAL; - } - + Layout.Alignment alignment = getLayoutAlignment(); boolean shouldEllipsize = mEllipsize != null && mInput == null; if (mText instanceof Spannable) { @@ -10045,6 +10104,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case TEXT_DIRECTION_ANY_RTL: resolvedTextDirection = getTextDirectionFromAnyRtl(mText); break; + case TEXT_DIRECTION_CHAR_COUNT: + resolvedTextDirection = getTextDirectionFromCharCount(mText); + break; case TEXT_DIRECTION_LTR: resolvedTextDirection = TEXT_DIRECTION_LTR; break; @@ -10078,6 +10140,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ private static int getTextDirectionFromFirstStrong(final CharSequence cs) { final int length = cs.length(); + if (length == 0) { + return TEXT_DIRECTION_UNDEFINED; + } for(int i = 0; i < length; i++) { final char c = cs.charAt(i); final byte dir = Character.getDirectionality(c); @@ -10100,6 +10165,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ private static int getTextDirectionFromAnyRtl(final CharSequence cs) { final int length = cs.length(); + if (length == 0) { + return TEXT_DIRECTION_UNDEFINED; + } boolean foundStrongLtr = false; boolean foundStrongRtl = false; for(int i = 0; i < length; i++) { @@ -10109,6 +10177,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener foundStrongLtr = true; } else if (isStrongRtlChar(dir)) { foundStrongRtl = true; + break; } } if (foundStrongRtl) { @@ -10121,6 +10190,41 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Get text direction following the "char count" heuristic. + * + * @param cs the CharSequence used to get the text direction. + * + * @return {@link #TEXT_DIRECTION_RTL} if direction it RTL, {@link #TEXT_DIRECTION_LTR} if + * direction it LTR or {@link #TEXT_DIRECTION_UNDEFINED} if direction cannot be found. + */ + private int getTextDirectionFromCharCount(CharSequence cs) { + final int length = cs.length(); + if (length == 0) { + return TEXT_DIRECTION_UNDEFINED; + } + int countLtr = 0; + int countRtl = 0; + for(int i = 0; i < length; i++) { + final char c = cs.charAt(i); + final byte dir = Character.getDirectionality(c); + if (isStrongLtrChar(dir)) { + countLtr++; + } else if (isStrongRtlChar(dir)) { + countRtl++; + } + } + final float percentLtr = ((float) countLtr) / (countLtr + countRtl); + if (percentLtr > DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD) { + return TEXT_DIRECTION_LTR; + } + final float percentRtl = ((float) countRtl) / (countLtr + countRtl); + if (percentRtl > DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD) { + return TEXT_DIRECTION_RTL; + } + return TEXT_DIRECTION_UNDEFINED; + } + + /** * Return true if the char direction is corresponding to a "strong RTL char" following the * Unicode Bidirectional Algorithm (UBA). */ diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 8d5df6f..519acf5 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -16,7 +16,6 @@ package com.android.internal.app; -import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.view.menu.SubMenuBuilder; @@ -36,7 +35,6 @@ import android.app.Dialog; import android.app.FragmentTransaction; import android.content.Context; import android.content.res.Configuration; -import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Handler; import android.view.ActionMode; @@ -580,6 +578,9 @@ public class ActionBarImpl extends ActionBar { mActionView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); + if (mTabScrollView != null && !mActionView.hasEmbeddedTabs() && mActionView.isCollapsed()) { + mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); + } } /** @@ -620,6 +621,7 @@ public class ActionBarImpl extends ActionBar { // Clear out the context mode views after the animation finishes mContextView.closeMode(); + mActionMode = null; if (mWasHiddenBeforeMode) { diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 773be5b..003c244 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -23,6 +23,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import java.util.List; + /** * A simple container used to carry information in VpnBuilder, VpnDialogs, * and com.android.server.connectivity.Vpn. Internal use only. @@ -33,11 +35,7 @@ public class VpnConfig implements Parcelable { public static final String ACTION_VPN_REVOKED = "android.net.vpn.action.REVOKED"; - public static void enforceCallingPackage(String packageName) { - if (!"com.android.vpndialogs".equals(packageName)) { - throw new SecurityException("Unauthorized Caller"); - } - } + public static final String LEGACY_VPN = "[Legacy VPN]"; public static Intent getIntentForConfirmation() { Intent intent = new Intent(); @@ -55,14 +53,15 @@ public class VpnConfig implements Parcelable { return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); } - public String packageName; - public String sessionName; - public String interfaceName; - public String configureActivity; + public String packagz; + public String interfaze; + public String session; public int mtu = -1; public String addresses; public String routes; - public String dnsServers; + public List<String> dnsServers; + public List<String> searchDomains; + public PendingIntent configureIntent; public long startTime = -1; @Override @@ -72,14 +71,15 @@ public class VpnConfig implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeString(packageName); - out.writeString(sessionName); - out.writeString(interfaceName); - out.writeString(configureActivity); + out.writeString(packagz); + out.writeString(interfaze); + out.writeString(session); out.writeInt(mtu); out.writeString(addresses); out.writeString(routes); - out.writeString(dnsServers); + out.writeStringList(dnsServers); + out.writeStringList(searchDomains); + out.writeParcelable(configureIntent, flags); out.writeLong(startTime); } @@ -88,14 +88,15 @@ public class VpnConfig implements Parcelable { @Override public VpnConfig createFromParcel(Parcel in) { VpnConfig config = new VpnConfig(); - config.packageName = in.readString(); - config.sessionName = in.readString(); - config.interfaceName = in.readString(); - config.configureActivity = in.readString(); + config.packagz = in.readString(); + config.interfaze = in.readString(); + config.session = in.readString(); config.mtu = in.readInt(); config.addresses = in.readString(); config.routes = in.readString(); - config.dnsServers = in.readString(); + config.dnsServers = in.createStringArrayList(); + config.searchDomains = in.createStringArrayList(); + config.configureIntent = in.readParcelable(null); config.startTime = in.readLong(); return config; } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java index a4bcf60..2685046 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItem.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java @@ -19,6 +19,7 @@ package com.android.internal.view.menu; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.view.ActionProvider; import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuItem; import android.view.SubMenu; @@ -238,6 +239,16 @@ public class ActionMenuItem implements MenuItem { } @Override + public ActionProvider getActionProvider() { + return null; + } + + @Override + public MenuItem setActionProvider(ActionProvider actionProvider) { + throw new UnsupportedOperationException(); + } + + @Override public MenuItem setShowAsActionFlags(int actionEnum) { setShowAsAction(actionEnum); return this; diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 322a854..2fec9cd 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -26,6 +26,7 @@ import android.view.MenuItem; import android.view.SoundEffectConstants; import android.view.View; import android.view.View.MeasureSpec; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.ImageButton; @@ -69,9 +70,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter { final Resources res = context.getResources(); if (!mReserveOverflowSet) { - // TODO Use the no-buttons specifier instead here - mReserveOverflow = res.getConfiguration() - .isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); + mReserveOverflow = !ViewConfiguration.get(context).hasPermanentMenuKey(); } if (!mWidthLimitSet) { diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 253511c..7b1dfb0 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.util.Log; +import android.view.ActionProvider; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.MenuItem; @@ -79,6 +80,7 @@ public final class MenuItemImpl implements MenuItem { private int mShowAsAction = SHOW_AS_ACTION_NEVER; private View mActionView; + private ActionProvider mActionProvider; private OnActionExpandListener mOnActionExpandListener; private boolean mIsActionViewExpanded = false; @@ -98,10 +100,8 @@ public final class MenuItemImpl implements MenuItem { /** - * Instantiates this menu item. The constructor - * {@link #MenuItemData(MenuBuilder, int, int, int, CharSequence, int)} is - * preferred due to lazy loading of the icon Drawable. - * + * Instantiates this menu item. + * * @param menu * @param group Item ordering grouping control. The item will be added after * all other items whose order is <= this number, and before any @@ -154,7 +154,7 @@ public final class MenuItemImpl implements MenuItem { mItemCallback.run(); return true; } - + if (mIntent != null) { try { mMenu.getContext().startActivity(mIntent); @@ -163,7 +163,14 @@ public final class MenuItemImpl implements MenuItem { Log.e(TAG, "Can't find activity to handle intent; ignoring", e); } } - + + if (mActionProvider != null) { + // The action view is created by the provider in this case. + View actionView = getActionView(); + mActionProvider.onPerformDefaultAction(actionView); + return true; + } + return false; } @@ -551,6 +558,7 @@ public final class MenuItemImpl implements MenuItem { public MenuItem setActionView(View view) { mActionView = view; + mActionProvider = null; mMenu.onItemActionRequestChanged(this); return this; } @@ -563,7 +571,25 @@ public final class MenuItemImpl implements MenuItem { } public View getActionView() { - return mActionView; + if (mActionView != null) { + return mActionView; + } else if (mActionProvider != null) { + mActionView = mActionProvider.onCreateActionView(); + return mActionView; + } else { + return null; + } + } + + public ActionProvider getActionProvider() { + return mActionProvider; + } + + public MenuItem setActionProvider(ActionProvider actionProvider) { + mActionView = null; + mActionProvider = actionProvider; + mMenu.onItemsChanged(false); + return this; } @Override diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index d710cfa..953328c 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -105,24 +105,19 @@ public class ActionBarContainer extends FrameLayout { public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int nonTabHeight = 0; - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); + if (mActionBarView == null) return; - if (child == mTabContainer) continue; - - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - nonTabHeight = Math.max(nonTabHeight, - child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); - } + final LayoutParams lp = (LayoutParams) mActionBarView.getLayoutParams(); + final int actionBarViewHeight = mActionBarView.isCollapsed() ? 0 : + mActionBarView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { final int mode = MeasureSpec.getMode(heightMeasureSpec); if (mode == MeasureSpec.AT_MOST) { final int maxHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(getMeasuredWidth(), - Math.min(nonTabHeight + mTabContainer.getMeasuredHeight(), maxHeight)); + Math.min(actionBarViewHeight + mTabContainer.getMeasuredHeight(), + maxHeight)); } } } @@ -137,12 +132,14 @@ public class ActionBarContainer extends FrameLayout { if ((mActionBarView.getDisplayOptions() & ActionBar.DISPLAY_SHOW_HOME) == 0) { // Not showing home, put tabs on top. final int count = getChildCount(); - for (int i = 0; i < count; i++){ + for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child == mTabContainer) continue; - child.offsetTopAndBottom(tabHeight); + if (!mActionBarView.isCollapsed()) { + child.offsetTopAndBottom(tabHeight); + } } mTabContainer.layout(l, 0, r, tabHeight); } else { diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index fc43994..3e3eeab 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -287,7 +287,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0); } - if (mMenuView != null) { + if (mMenuView != null && mMenuView.getParent() == this) { availableWidth = measureChildView(mMenuView, availableWidth, childSpecHeight, 0); } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 8eb046e..09bc1fc 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -117,6 +117,7 @@ public class ActionBarView extends AbsActionBarView { private boolean mUserTitle; private boolean mIncludeTabs; private boolean mIsCollapsable; + private boolean mIsCollapsed; private MenuBuilder mOptionsMenu; @@ -692,6 +693,10 @@ public class ActionBarView extends AbsActionBarView { mIsCollapsable = collapsable; } + public boolean isCollapsed() { + return mIsCollapsed; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int childCount = getChildCount(); @@ -708,9 +713,11 @@ public class ActionBarView extends AbsActionBarView { if (visibleChildren == 0) { // No size for an empty action bar when collapsable. setMeasuredDimension(0, 0); + mIsCollapsed = true; return; } } + mIsCollapsed = false; int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java index 5b4d7ab..2f7adf0 100644 --- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java +++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget; +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; import android.app.ActionBar; import android.content.Context; import android.graphics.drawable.Drawable; @@ -22,6 +25,7 @@ import android.text.TextUtils.TruncateAt; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; @@ -35,6 +39,13 @@ public class ScrollingTabContainerView extends HorizontalScrollView { int mMaxTabWidth; + protected Animator mVisibilityAnim; + protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); + + private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator(); + + private static final int FADE_DURATION = 200; + public ScrollingTabContainerView(Context context) { super(context); setHorizontalScrollBarEnabled(false); @@ -76,6 +87,30 @@ public class ScrollingTabContainerView extends HorizontalScrollView { } } + public void animateToVisibility(int visibility) { + if (mVisibilityAnim != null) { + mVisibilityAnim.cancel(); + } + if (visibility == VISIBLE) { + if (getVisibility() != VISIBLE) { + setAlpha(0); + } + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1); + anim.setDuration(FADE_DURATION); + anim.setInterpolator(sAlphaInterpolator); + + anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); + anim.start(); + } else { + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0); + anim.setDuration(FADE_DURATION); + anim.setInterpolator(sAlphaInterpolator); + + anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); + anim.start(); + } + } + public void animateToTab(int position) { final View tabView = mTabLayout.getChildAt(position); if (mTabSelector != null) { @@ -259,4 +294,38 @@ public class ScrollingTabContainerView extends HorizontalScrollView { } } } + + protected class VisibilityAnimListener implements Animator.AnimatorListener { + private boolean mCanceled = false; + private int mFinalVisibility; + + public VisibilityAnimListener withFinalVisibility(int visibility) { + mFinalVisibility = visibility; + return this; + } + + @Override + public void onAnimationStart(Animator animation) { + setVisibility(VISIBLE); + mVisibilityAnim = animation; + mCanceled = false; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) return; + + mVisibilityAnim = null; + setVisibility(mFinalVisibility); + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + } } diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java index 5b35104..04bb689 100644 --- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java +++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import android.animation.Animator; import android.animation.Animator.AnimatorListener; -import android.animation.ObjectAnimator; +import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; @@ -43,9 +43,9 @@ import com.android.internal.R; * A special widget containing a center and outer ring. Moving the center ring to the outer ring * causes an event that can be caught by implementing OnTriggerListener. */ -public class MultiWaveView extends View implements AnimatorUpdateListener { +public class MultiWaveView extends View { private static final String TAG = "MultiWaveView"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; // Wave state machine private static final int STATE_IDLE = 0; @@ -67,14 +67,15 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { } // Tune-able parameters - private static final int CHEVRON_INCREMENTAL_DELAY = 50; - private static final int CHEVRON_ANIMATION_DURATION = 1000; - private static final int RETURN_TO_HOME_DURATION = 150; - private static final int HIDE_ANIMATION_DELAY = 500; - private static final int HIDE_ANIMATION_DURACTION = 2000; + private static final int CHEVRON_INCREMENTAL_DELAY = 160; + private static final int CHEVRON_ANIMATION_DURATION = 650; + private static final int RETURN_TO_HOME_DELAY = 1200; + private static final int RETURN_TO_HOME_DURATION = 300; + private static final int HIDE_ANIMATION_DELAY = 200; + private static final int HIDE_ANIMATION_DURATION = RETURN_TO_HOME_DELAY; private static final int SHOW_ANIMATION_DURATION = 0; private static final int SHOW_ANIMATION_DELAY = 0; - private TimeInterpolator mChevronAnimationInterpolator = Ease.Quint.easeOut; + private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut; private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>(); private ArrayList<TargetDrawable> mChevronDrawables = new ArrayList<TargetDrawable>(); @@ -99,14 +100,31 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { private float mHitRadius = 0.0f; private float mSnapMargin = 0.0f; private boolean mDragging; + private int mNewTargetResources; - private AnimatorListener mResetListener = new Animator.AnimatorListener() { - public void onAnimationStart(Animator animation) { } - public void onAnimationRepeat(Animator animation) { } - public void onAnimationEnd(Animator animation) { + private AnimatorListener mResetListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animator) { switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); } - public void onAnimationCancel(Animator animation) { } + }; + + private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + invalidateGlobalRegion(mHandleDrawable); + invalidate(); + } + }; + + private boolean mAnimatingTargets; + private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animator) { + if (mNewTargetResources != 0) { + internalSetTargetResources(mNewTargetResources); + mNewTargetResources = 0; + hideTargets(false); + } + mAnimatingTargets = false; + } }; public MultiWaveView(Context context) { @@ -135,31 +153,23 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { mOuterRing = new TargetDrawable(res, a.getDrawable(R.styleable.MultiWaveView_waveDrawable)); // Read chevron animation drawables - Drawable leftChevron = a.getDrawable(R.styleable.MultiWaveView_leftChevronDrawable); - for (int i = 0; i < mFeedbackCount; i++) { - mChevronDrawables.add( - leftChevron != null ? new TargetDrawable(res, leftChevron) : null); - } - Drawable rightChevron = a.getDrawable(R.styleable.MultiWaveView_rightChevronDrawable); - for (int i = 0; i < mFeedbackCount; i++) { - mChevronDrawables.add( - rightChevron != null ? new TargetDrawable(res, rightChevron) : null); - } - Drawable topChevron = a.getDrawable(R.styleable.MultiWaveView_topChevronDrawable); - for (int i = 0; i < mFeedbackCount; i++) { - mChevronDrawables.add( - topChevron != null ? new TargetDrawable(res, topChevron) : null); - } - Drawable bottomChevron = a.getDrawable(R.styleable.MultiWaveView_bottomChevronDrawable); - for (int i = 0; i < mFeedbackCount; i++) { - mChevronDrawables.add( - bottomChevron != null ? new TargetDrawable(res, bottomChevron) : null); + final int chevrons[] = { R.styleable.MultiWaveView_leftChevronDrawable, + R.styleable.MultiWaveView_rightChevronDrawable, + R.styleable.MultiWaveView_topChevronDrawable, + R.styleable.MultiWaveView_bottomChevronDrawable + }; + for (int chevron : chevrons) { + Drawable chevronDrawable = a.getDrawable(chevron); + for (int i = 0; i < mFeedbackCount; i++) { + mChevronDrawables.add( + chevronDrawable != null ? new TargetDrawable(res, chevronDrawable) : null); + } } // Read array of target drawables TypedValue outValue = new TypedValue(); if (a.getValue(R.styleable.MultiWaveView_targetDrawables, outValue)) { - setTargetResources(outValue.resourceId); + internalSetTargetResources(outValue.resourceId); } if (mTargetDrawables == null || mTargetDrawables.size() == 0) { throw new IllegalStateException("Must specify at least one target drawable"); @@ -205,7 +215,7 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { case STATE_FIRST_TOUCH: stopHandleAnimation(); deactivateTargets(); - showTargets(); + showTargets(true); mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE); setGrabbedState(OnTriggerListener.CENTER_HANDLE); break; @@ -228,17 +238,18 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { * mFeedbackCount items in the order: left, right, top, bottom. */ private void startChevronAnimation() { - final float r = mHandleDrawable.getWidth() / 2; + final float r = mHandleDrawable.getWidth() * 0.4f; + final float chevronAnimationDistance = mOuterRadius * 0.8f; final float from[][] = { {mWaveCenterX - r, mWaveCenterY}, // left {mWaveCenterX + r, mWaveCenterY}, // right {mWaveCenterX, mWaveCenterY - r}, // top {mWaveCenterX, mWaveCenterY + r} }; // bottom final float to[][] = { - {mWaveCenterX - mOuterRadius, mWaveCenterY}, // left - {mWaveCenterX + mOuterRadius, mWaveCenterY}, // right - {mWaveCenterX, mWaveCenterY - mOuterRadius}, // top - {mWaveCenterX, mWaveCenterY + mOuterRadius} }; // bottom + {mWaveCenterX - chevronAnimationDistance, mWaveCenterY}, // left + {mWaveCenterX + chevronAnimationDistance, mWaveCenterY}, // right + {mWaveCenterX, mWaveCenterY - chevronAnimationDistance}, // top + {mWaveCenterX, mWaveCenterY + chevronAnimationDistance} }; // bottom mChevronAnimations.clear(); for (int direction = 0; direction < 4; direction++) { @@ -254,7 +265,7 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { "x", new float[] { from[direction][0], to[direction][0] }, "y", new float[] { from[direction][1], to[direction][1] }, "alpha", new float[] {1.0f, 0.0f}, - "onUpdate", this)); + "onUpdate", mUpdateListener)); } } } @@ -308,70 +319,109 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { } private void doFinish() { - // Inform listener of any active targets. Typically only one will be active. final int activeTarget = mActiveTarget; boolean targetHit = activeTarget != -1; - if (targetHit) { - Log.v(TAG, "Finish with target hit = " + targetHit); - dispatchTriggerEvent(mActiveTarget); - } - - setGrabbedState(OnTriggerListener.NO_HANDLE); - - // Animate finger outline back to home position - mHandleDrawable.setAlpha(targetHit ? 0.0f : 1.0f); - mHandleAnimation = Tweener.to(mHandleDrawable, RETURN_TO_HOME_DURATION, - "ease", Ease.Quart.easeOut, - "delay", targetHit ? HIDE_ANIMATION_DELAY : 0, - "alpha", 1.0f, - "x", mWaveCenterX, - "y", mWaveCenterY, - "onUpdate", this, - "onComplete", mResetListener); // Hide unselected targets hideTargets(true); // Highlight the selected one + mHandleDrawable.setAlpha(targetHit ? 0.0f : 1.0f); if (targetHit) { mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE); + + hideUnselected(activeTarget); + + // Inform listener of any active targets. Typically only one will be active. + if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit); + dispatchTriggerEvent(mActiveTarget); + mHandleAnimation = Tweener.to(mHandleDrawable, 0, + "ease", Ease.Quart.easeOut, + "delay", RETURN_TO_HOME_DELAY, + "alpha", 1.0f, + "x", mWaveCenterX, + "y", mWaveCenterY, + "onUpdate", mUpdateListener, + "onComplete", mResetListener); + } else { + // Animate finger outline back to home position + mHandleAnimation = Tweener.to(mHandleDrawable, RETURN_TO_HOME_DURATION, + "ease", Ease.Quart.easeOut, + "delay", 0, + "alpha", 1.0f, + "x", mWaveCenterX, + "y", mWaveCenterY, + "onUpdate", mUpdateListener, + "onComplete", mResetListener); } + + setGrabbedState(OnTriggerListener.NO_HANDLE); + } + + private void hideUnselected(int active) { + for (int i = 0; i < mTargetDrawables.size(); i++) { + if (i != active) { + mTargetDrawables.get(i).setAlpha(0.0f); + } + } + mOuterRing.setAlpha(0.0f); } private void hideTargets(boolean animate) { if (mTargetAnimations.size() > 0) { stopTargetAnimation(); } - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - mTargetAnimations.add(Tweener.to(target, - animate ? HIDE_ANIMATION_DURACTION : 0, + // Note: these animations should complete at the same time so that we can swap out + // the target assets asynchronously from the setTargetResources() call. + mAnimatingTargets = animate; + if (animate) { + final int duration = animate ? HIDE_ANIMATION_DURATION : 0; + for (TargetDrawable target : mTargetDrawables) { + target.setState(TargetDrawable.STATE_INACTIVE); + mTargetAnimations.add(Tweener.to(target, duration, + "alpha", 0.0f, + "delay", HIDE_ANIMATION_DELAY, + "onUpdate", mUpdateListener)); + } + mTargetAnimations.add(Tweener.to(mOuterRing, duration, "alpha", 0.0f, "delay", HIDE_ANIMATION_DELAY, - "onUpdate", this)); + "onUpdate", mUpdateListener, + "onComplete", mTargetUpdateListener)); + } else { + for (TargetDrawable target : mTargetDrawables) { + target.setState(TargetDrawable.STATE_INACTIVE); + target.setAlpha(0.0f); + } + mOuterRing.setAlpha(0.0f); } - mTargetAnimations.add(Tweener.to(mOuterRing, - animate ? HIDE_ANIMATION_DURACTION : 0, - "alpha", 0.0f, - "delay", HIDE_ANIMATION_DELAY, - "onUpdate", this)); } - private void showTargets() { + private void showTargets(boolean animate) { if (mTargetAnimations.size() > 0) { stopTargetAnimation(); } - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - mTargetAnimations.add(Tweener.to(target, SHOW_ANIMATION_DURATION, + mAnimatingTargets = animate; + if (animate) { + for (TargetDrawable target : mTargetDrawables) { + target.setState(TargetDrawable.STATE_INACTIVE); + mTargetAnimations.add(Tweener.to(target, SHOW_ANIMATION_DURATION, + "alpha", 1.0f, + "delay", SHOW_ANIMATION_DELAY, + "onUpdate", mUpdateListener)); + } + mTargetAnimations.add(Tweener.to(mOuterRing, SHOW_ANIMATION_DURATION, "alpha", 1.0f, "delay", SHOW_ANIMATION_DELAY, - "onUpdate", this)); + "onUpdate", mUpdateListener, + "onComplete", mTargetUpdateListener)); + } else { + for (TargetDrawable target : mTargetDrawables) { + target.setState(TargetDrawable.STATE_INACTIVE); + target.setAlpha(1.0f); + } + mOuterRing.setAlpha(1.0f); } - mTargetAnimations.add(Tweener.to(mOuterRing, SHOW_ANIMATION_DURATION, - "alpha", 1.0f, - "delay", SHOW_ANIMATION_DELAY, - "onUpdate", this)); } private void stopTargetAnimation() { @@ -387,12 +437,7 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { } } - /** - * Loads an array of drawables from the given resourceId. - * - * @param resourceId - */ - public void setTargetResources(int resourceId) { + private void internalSetTargetResources(int resourceId) { Resources res = getContext().getResources(); TypedArray array = res.obtainTypedArray(resourceId); int count = array.length(); @@ -402,6 +447,21 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { targetDrawables.add(new TargetDrawable(res, drawable)); } mTargetDrawables = targetDrawables; + updateTargetPositions(); + } + + /** + * Loads an array of drawables from the given resourceId. + * + * @param resourceId + */ + public void setTargetResources(int resourceId) { + if (mAnimatingTargets) { + // postpone this change until we return to the initial state + mNewTargetResources = resourceId; + } else { + internalSetTargetResources(resourceId); + } } /** @@ -590,20 +650,9 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { } } - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - final int width = right - left; - final int height = bottom - top; - - mWaveCenterX = mHorizontalOffset + Math.max(width, mOuterRing.getWidth() ) / 2; - mWaveCenterY = mVerticalOffset + Math.max(height, mOuterRing.getHeight()) / 2; - moveHandleTo(mWaveCenterX, mWaveCenterY, false); - mOuterRing.setX(mWaveCenterX); - mOuterRing.setY(Math.max(mWaveCenterY, mWaveCenterY)); - mOuterRing.setAlpha(0.0f); + private void performInitialLayout(float centerX, float centerY) { if (mOuterRadius == 0.0f) { - mOuterRadius = 0.5f*(float) Math.sqrt(dist2(mWaveCenterX, mWaveCenterY)); + mOuterRadius = 0.5f*(float) Math.sqrt(dist2(centerX, centerY)); } if (mHitRadius == 0.0f) { // Use the radius of inscribed circle of the first target. @@ -613,6 +662,35 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics()); } + hideChevrons(); + hideTargets(false); + moveHandleTo(centerX, centerY, false); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + final int width = right - left; + final int height = bottom - top; + float newWaveCenterX = mHorizontalOffset + Math.max(width, mOuterRing.getWidth() ) / 2; + float newWaveCenterY = mVerticalOffset + Math.max(height, mOuterRing.getHeight()) / 2; + if (newWaveCenterX != mWaveCenterX || newWaveCenterY != mWaveCenterY) { + if (mWaveCenterX == 0 && mWaveCenterY == 0) { + performInitialLayout(newWaveCenterX, newWaveCenterY); + } + mWaveCenterX = newWaveCenterX; + mWaveCenterY = newWaveCenterY; + + mOuterRing.setX(mWaveCenterX); + mOuterRing.setY(Math.max(mWaveCenterY, mWaveCenterY)); + + updateTargetPositions(); + } + if (DEBUG) dump(); + } + + private void updateTargetPositions() { + // Reposition the target drawables if the view changed. for (int i = 0; i < mTargetDrawables.size(); i++) { final TargetDrawable targetIcon = mTargetDrawables.get(i); double angle = -2.0f * Math.PI * i / mTargetDrawables.size(); @@ -620,11 +698,7 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { float yPosition = mWaveCenterY + mOuterRadius * (float) Math.sin(angle); targetIcon.setX(xPosition); targetIcon.setY(yPosition); - targetIcon.setAlpha(0.0f); } - hideChevrons(); - hideTargets(false); - if (DEBUG) dump(); } private void hideChevrons() { @@ -655,11 +729,6 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { mOnTriggerListener = listener; } - public void onAnimationUpdate(ValueAnimator animation) { - invalidateGlobalRegion(mHandleDrawable); - invalidate(); - } - private float square(float d) { return d * d; } diff --git a/core/res/res/drawable-hdpi/textfield_default.9.png b/core/res/res/drawable-hdpi/textfield_default.9.png Binary files differindex 4c20179..f7b6e99 100644..100755 --- a/core/res/res/drawable-hdpi/textfield_default.9.png +++ b/core/res/res/drawable-hdpi/textfield_default.9.png diff --git a/core/res/res/drawable-hdpi/textfield_disabled.9.png b/core/res/res/drawable-hdpi/textfield_disabled.9.png Binary files differindex 81569d1..3011502 100644..100755 --- a/core/res/res/drawable-hdpi/textfield_disabled.9.png +++ b/core/res/res/drawable-hdpi/textfield_disabled.9.png diff --git a/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png b/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png Binary files differindex 2591490..e0f82eb 100644..100755 --- a/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png +++ b/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png diff --git a/core/res/res/drawable-hdpi/textfield_selected.9.png b/core/res/res/drawable-hdpi/textfield_selected.9.png Binary files differindex a36ed72..cf2cae3 100644..100755 --- a/core/res/res/drawable-hdpi/textfield_selected.9.png +++ b/core/res/res/drawable-hdpi/textfield_selected.9.png diff --git a/core/res/res/drawable-mdpi/textfield_default.9.png b/core/res/res/drawable-mdpi/textfield_default.9.png Binary files differindex cc78e6c..1a59bb2 100644 --- a/core/res/res/drawable-mdpi/textfield_default.9.png +++ b/core/res/res/drawable-mdpi/textfield_default.9.png diff --git a/core/res/res/drawable-mdpi/textfield_disabled.9.png b/core/res/res/drawable-mdpi/textfield_disabled.9.png Binary files differindex 9c77149..800205a 100644 --- a/core/res/res/drawable-mdpi/textfield_disabled.9.png +++ b/core/res/res/drawable-mdpi/textfield_disabled.9.png diff --git a/core/res/res/drawable-mdpi/textfield_disabled_selected.9.png b/core/res/res/drawable-mdpi/textfield_disabled_selected.9.png Binary files differindex 6d47708..59e1536 100644 --- a/core/res/res/drawable-mdpi/textfield_disabled_selected.9.png +++ b/core/res/res/drawable-mdpi/textfield_disabled_selected.9.png diff --git a/core/res/res/drawable-mdpi/textfield_selected.9.png b/core/res/res/drawable-mdpi/textfield_selected.9.png Binary files differindex 0c1b446..faadace 100644 --- a/core/res/res/drawable-mdpi/textfield_selected.9.png +++ b/core/res/res/drawable-mdpi/textfield_selected.9.png diff --git a/core/res/res/drawable-xhdpi/popup_bottom_bright.9.png b/core/res/res/drawable-xhdpi/popup_bottom_bright.9.png Binary files differnew file mode 100644 index 0000000..cdc0afb --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_bottom_bright.9.png diff --git a/core/res/res/drawable-xhdpi/popup_bottom_dark.9.png b/core/res/res/drawable-xhdpi/popup_bottom_dark.9.png Binary files differnew file mode 100644 index 0000000..36b0448 --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_bottom_dark.9.png diff --git a/core/res/res/drawable-xhdpi/popup_bottom_medium.9.png b/core/res/res/drawable-xhdpi/popup_bottom_medium.9.png Binary files differnew file mode 100644 index 0000000..3a7a8b3 --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_bottom_medium.9.png diff --git a/core/res/res/drawable-xhdpi/popup_center_bright.9.png b/core/res/res/drawable-xhdpi/popup_center_bright.9.png Binary files differnew file mode 100644 index 0000000..b1a8e3e --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_center_bright.9.png diff --git a/core/res/res/drawable-xhdpi/popup_center_dark.9.png b/core/res/res/drawable-xhdpi/popup_center_dark.9.png Binary files differnew file mode 100644 index 0000000..87378e1 --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_center_dark.9.png diff --git a/core/res/res/drawable-xhdpi/popup_center_medium.9.png b/core/res/res/drawable-xhdpi/popup_center_medium.9.png Binary files differnew file mode 100644 index 0000000..ea29ed4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_center_medium.9.png diff --git a/core/res/res/drawable-xhdpi/popup_full_bright.9.png b/core/res/res/drawable-xhdpi/popup_full_bright.9.png Binary files differnew file mode 100644 index 0000000..114faa0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_full_bright.9.png diff --git a/core/res/res/drawable-xhdpi/popup_full_dark.9.png b/core/res/res/drawable-xhdpi/popup_full_dark.9.png Binary files differnew file mode 100644 index 0000000..996beaa --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_full_dark.9.png diff --git a/core/res/res/drawable-xhdpi/popup_top_bright.9.png b/core/res/res/drawable-xhdpi/popup_top_bright.9.png Binary files differnew file mode 100644 index 0000000..64e4139 --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_top_bright.9.png diff --git a/core/res/res/drawable-xhdpi/popup_top_dark.9.png b/core/res/res/drawable-xhdpi/popup_top_dark.9.png Binary files differnew file mode 100644 index 0000000..902bc29 --- /dev/null +++ b/core/res/res/drawable-xhdpi/popup_top_dark.9.png diff --git a/core/res/res/drawable-xhdpi/spinner_black_16.png b/core/res/res/drawable-xhdpi/spinner_black_16.png Binary files differnew file mode 100644 index 0000000..5b1422c --- /dev/null +++ b/core/res/res/drawable-xhdpi/spinner_black_16.png diff --git a/core/res/res/drawable-xhdpi/spinner_black_20.png b/core/res/res/drawable-xhdpi/spinner_black_20.png Binary files differnew file mode 100644 index 0000000..5f53e38 --- /dev/null +++ b/core/res/res/drawable-xhdpi/spinner_black_20.png diff --git a/core/res/res/drawable-xhdpi/spinner_black_48.png b/core/res/res/drawable-xhdpi/spinner_black_48.png Binary files differnew file mode 100644 index 0000000..3aab620 --- /dev/null +++ b/core/res/res/drawable-xhdpi/spinner_black_48.png diff --git a/core/res/res/drawable-xhdpi/spinner_black_76.png b/core/res/res/drawable-xhdpi/spinner_black_76.png Binary files differnew file mode 100644 index 0000000..2968d8c --- /dev/null +++ b/core/res/res/drawable-xhdpi/spinner_black_76.png diff --git a/core/res/res/drawable-xhdpi/spinner_white_16.png b/core/res/res/drawable-xhdpi/spinner_white_16.png Binary files differnew file mode 100644 index 0000000..69be752 --- /dev/null +++ b/core/res/res/drawable-xhdpi/spinner_white_16.png diff --git a/core/res/res/drawable-xhdpi/spinner_white_48.png b/core/res/res/drawable-xhdpi/spinner_white_48.png Binary files differnew file mode 100644 index 0000000..7b196bc --- /dev/null +++ b/core/res/res/drawable-xhdpi/spinner_white_48.png diff --git a/core/res/res/drawable-xhdpi/spinner_white_76.png b/core/res/res/drawable-xhdpi/spinner_white_76.png Binary files differnew file mode 100644 index 0000000..ba47005 --- /dev/null +++ b/core/res/res/drawable-xhdpi/spinner_white_76.png diff --git a/core/res/res/layout-land/ssl_certificate.xml b/core/res/res/layout-land/ssl_certificate.xml index 56e4e70..c3e6deb 100644 --- a/core/res/res/layout-land/ssl_certificate.xml +++ b/core/res/res/layout-land/ssl_certificate.xml @@ -20,6 +20,7 @@ android:layout_height="wrap_content" > <LinearLayout + android:id="@+id/body" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > diff --git a/core/res/res/layout/activity_chooser_list_footer.xml b/core/res/res/layout/activity_chooser_list_footer.xml new file mode 100644 index 0000000..7603a31 --- /dev/null +++ b/core/res/res/layout/activity_chooser_list_footer.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list_footer" + android:paddingLeft="16dip" + android:paddingRight="16dip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical"> + + <ImageView + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="2dip" + android:scaleType="fitXY" + android:gravity="fill_horizontal" + android:src="@drawable/divider_strong_holo" /> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="48dip" + android:gravity="center" + android:textAppearance="?android:attr/textAppearanceLargePopupMenu" + android:duplicateParentState="true" + android:singleLine="true" + android:text="@string/activity_chooser_view_see_all" /> + +</LinearLayout> diff --git a/core/res/res/layout/activity_chooser_list_header.xml b/core/res/res/layout/activity_chooser_list_header.xml new file mode 100644 index 0000000..867014b --- /dev/null +++ b/core/res/res/layout/activity_chooser_list_header.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list_header" + android:paddingLeft="16dip" + android:paddingRight="16dip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical"> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="?android:attr/dropdownListPreferredItemHeight" + android:gravity="center" + android:textAppearance="?android:attr/textAppearanceLargePopupMenu" + android:duplicateParentState="true" + android:singleLine="true" /> + + <ImageView + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="2dip" + android:scaleType="fitXY" + android:gravity="fill_horizontal" + android:src="@drawable/divider_strong_holo" /> + +</LinearLayout> diff --git a/core/res/res/layout/activity_chooser_view.xml b/core/res/res/layout/activity_chooser_view.xml new file mode 100644 index 0000000..ccf49fc --- /dev/null +++ b/core/res/res/layout/activity_chooser_view.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2011, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/activity_chooser_view_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:attr/actionButtonStyle"> + + <ImageButton android:id="@+id/default_activity_button" + android:layout_width="32dip" + android:layout_height="32dip" + android:layout_marginLeft="16dip" /> + + <ImageButton android:id="@+id/expand_activities_button" + android:layout_width="32dip" + android:layout_height="32dip" + android:layout_marginLeft="16dip" /> + +</LinearLayout> diff --git a/core/res/res/layout/activity_chooser_view_list_item.xml b/core/res/res/layout/activity_chooser_view_list_item.xml new file mode 100644 index 0000000..61b7e70 --- /dev/null +++ b/core/res/res/layout/activity_chooser_view_list_item.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list_item" + android:layout_width="match_parent" + android:layout_height="?android:attr/dropdownListPreferredItemHeight" + android:gravity="center_vertical" + android:paddingLeft="16dip" + android:paddingRight="16dip"> + + <ImageView + android:id="@+id/icon" + android:layout_width="32dip" + android:layout_height="32dip" + android:layout_gravity="center_vertical" + android:layout_marginRight="8dip" + android:duplicateParentState="true" /> + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLargePopupMenu" + android:singleLine="true" + android:duplicateParentState="true" + android:ellipsize="marquee" + android:fadingEdge="horizontal" /> + +</LinearLayout> diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml index 8ba08f6..30df91b 100644 --- a/core/res/res/layout/keyguard_screen_password_landscape.xml +++ b/core/res/res/layout/keyguard_screen_password_landscape.xml @@ -156,7 +156,7 @@ /> <!-- Column 1 --> - <Space android:layout_columnWeight="1" android:layout_rowSpan="11" /> + <Space android:layout_widthSpec="canStretch" android:layout_rowSpan="11" /> <!-- Column 2 - password entry field and PIN keyboard --> <LinearLayout diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml index 5588adc..c859720 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml @@ -169,7 +169,10 @@ </LinearLayout> <!-- Column 1 --> - <Space android:width="20dip" android:layout_columnWeight="1" android:layout_rowSpan="10" /> + <Space + android:width="20dip" + android:layout_heightSpec="canStretch" + android:layout_rowSpan="10" /> <!-- Column 2 --> <com.android.internal.widget.multiwaveview.MultiWaveView diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml index d0538dd..0070ed0 100644 --- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml +++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml @@ -100,9 +100,11 @@ <!-- TODO: remove hard coded height since layout_rowWeight doesn't seem to be working --> <Space - android:layout_height="43dip" - android:layout_gravity="fill" - android:layout_rowWeight="1" android:layout_columnWeight="1" /> + android:layout_height="43dip" + android:layout_gravity="fill" + android:layout_heightSpec="canStretch" + android:layout_widthSpec="canStretch" + /> <TextView android:id="@+id/carrier" android:layout_gravity="right" diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml index 774f830..28c5302 100644 --- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml +++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml @@ -125,7 +125,7 @@ android:layout_marginBottom="4dip" android:layout_marginLeft="8dip" android:layout_gravity="center|bottom" - android:layout_rowWeight="1" + android:layout_heightSpec="canStretch" /> <TextView diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml index 475aa59..fee27eb 100644 --- a/core/res/res/layout/search_view.xml +++ b/core/res/res/layout/search_view.xml @@ -54,8 +54,8 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical" - android:layout_marginLeft="16dip" - android:layout_marginRight="16dip" + android:layout_marginLeft="8dip" + android:layout_marginRight="8dip" android:layout_marginTop="4dip" android:layout_marginBottom="4dip" android:orientation="horizontal"> @@ -87,7 +87,6 @@ android:layout_gravity="bottom" android:paddingLeft="8dip" android:paddingRight="6dip" - android:drawablePadding="2dip" android:singleLine="true" android:ellipsize="end" android:background="@null" diff --git a/core/res/res/layout/ssl_certificate.xml b/core/res/res/layout/ssl_certificate.xml index 7206077..ae661ce 100644 --- a/core/res/res/layout/ssl_certificate.xml +++ b/core/res/res/layout/ssl_certificate.xml @@ -20,6 +20,7 @@ android:layout_height="wrap_content" > <LinearLayout + android:id="@+id/body" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index fd61cfd..aa8c510 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1978,17 +1978,21 @@ <!-- Default --> <enum name="inherit" value="0" /> <!-- Default for the root view. The first strong directional character determines the - paragraph direction. If there is o strong directional character, the paragraph + paragraph direction. If there is no strong directional character, the paragraph direction is the view’s resolved layout direction. --> <enum name="firstStrong" value="1" /> <!-- The paragraph direction is RTL if it contains any strong RTL character, otherwise it is LTR if it contains any strong LTR characters. If there are neither, the paragraph direction is the view’s resolved layout direction. --> <enum name="anyRtl" value="2" /> - <!-- The text direction is left to right. --> - <enum name="ltr" value="3" /> - <!-- The text direction is right to left. --> - <enum name="rtl" value="4" /> + <!-- The paragraph direction is the same as the one held by a 60% majority of the + characters. If there is no majority then the paragraph direction is the resolved + layout direction of the View. --> + <enum name="charCount" value="3" /> + <!-- The paragraph direction is left to right. --> + <enum name="ltr" value="4" /> + <!-- The paragraph direction is right to left. --> + <enum name="rtl" value="5" /> </attr> </declare-styleable> @@ -2208,7 +2212,8 @@ <!-- The event types this serivce would like to receive as specified in {@link android.view.accessibility.AccessibilityEvent}. This setting can be changed at runtime by calling - {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> + {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) + android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> <attr name="accessibilityEventTypes"> <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED} events.--> <flag name="typeViewClicked" value="0x00000001" /> @@ -2232,17 +2237,24 @@ <flag name="typeTouchExplorationGestureStart" value="0x00000200" /> <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END} events. --> <flag name="typeTouchExplorationGestureEnd" value="0x00000400" /> + <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events. --> + <flag name="typeWindowContentChanged" value="0x00000800" /> + <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED} events. --> + <flag name="typeViewScrolled" value="0x000001000" /> + <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED} events. --> + <flag name="typeViewTextSelectionChanged" value="0x000002000" /> <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPES_ALL_MASK} i.e. all events. --> <flag name="typeAllMask" value="0xffffffff" /> </attr> <!-- Comma separated package names from which this serivce would like to receive events (leave out for all packages). - This setting can be changed at runtime by calling - {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> + {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) + android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> <attr name="packageNames" format="string" /> <!-- The feedback types this serivce provides as specified in {@link android.accessibilityservice.AccessibilityServiceInfo}. This setting can be changed at runtime by calling - {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> + {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) + android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> <attr name="accessibilityFeedbackType"> <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_SPOKEN} feedback. --> <flag name="feedbackSpoken" value="0x00000001" /> @@ -2255,14 +2267,16 @@ <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_GENERIC} feedback. --> <flag name="feedbackGeneric" value="0x00000010" /> </attr> - <!-- The minimal period in milliseconds between two accessibility events are sent - to this serivce. This setting can be changed at runtime by calling - {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> + <!-- The minimal period in milliseconds between two accessibility events of the same type + are sent to this serivce. This setting can be changed at runtime by calling + {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) + android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. -->> <attr name="notificationTimeout" format="integer" /> <!-- Additional flags as specified in {@link android.accessibilityservice.AccessibilityServiceInfo}. This setting can be changed at runtime by calling - {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> + {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) + android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> <attr name="accessibilityFlags"> <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#DEFAULT} --> <flag name="flagDefault" value="0x00000001" /> @@ -2271,7 +2285,7 @@ the settings for this service. This setting cannot be changed at runtime. --> <attr name="settingsActivity" /> <!-- Flag whether the accessibility service wants to be able to retrieve the - focused window content. This setting cannot be changed at runtime. --> + active window content. This setting cannot be changed at runtime. --> <attr name="canRetrieveWindowContent" format="boolean" /> </declare-styleable> @@ -3314,11 +3328,6 @@ The default is one. See {@link android.widget.GridLayout.Group}. --> <attr name="layout_rowSpan" format="integer" min="1" /> - <!-- A number indicating the relative proportion of available space that - should be taken by this group of cells. - The default is zero. - See {@link android.widget.GridLayout.LayoutParams#columnWeight}. --> - <attr name="layout_rowWeight" format="float" /> <!-- The column boundary delimiting the left of the group of cells occupied by this view. --> <attr name="layout_column" /> @@ -3327,15 +3336,38 @@ The default is one. See {@link android.widget.GridLayout.Group}. --> <attr name="layout_columnSpan" format="integer" min="1" /> - <!-- A number indicating the relative proportion of available space that - should be taken by this group of cells. - The default is zero. - See {@link android.widget.GridLayout.LayoutParams#columnWeight}.--> - <attr name="layout_columnWeight" format="float" /> <!-- Gravity specifies how a component should be placed in its group of cells. The default is LEFT | BASELINE. See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. --> <attr name="layout_gravity" /> + <!-- A value specifying how much deficit or excess width this component can accomodate. + The default is FIXED. + See {@link android.widget.GridLayout.LayoutParams#widthSpec}.--> + <attr name="layout_widthSpec" > + <!-- If possible, width should be exactly as specified. + See {@link android.widget.GridLayout#FIXED}. --> + <enum name="fixed" value="0" /> + <!-- If possible, width should be less than or equal to the specified width. + See {@link android.widget.GridLayout#CAN_SHRINK}. --> + <enum name="canShrink" value="1" /> + <!-- If possible, width should be greater than or equal to the specified width. + See {@link android.widget.GridLayout#CAN_STRETCH}. --> + <enum name="canStretch" value="2" /> + </attr> + <!-- A value specifying how much deficit or excess height this component can accomodate. + The default is FIXED. + See {@link android.widget.GridLayout.LayoutParams#heightSpec}.--> + <attr name="layout_heightSpec" > + <!-- If possible, height should be exactly as specified. + See {@link android.widget.GridLayout#FIXED}. --> + <enum name="fixed" value="0" /> + <!-- If possible, height should be less than or equal to the specified height. + See {@link android.widget.GridLayout#CAN_SHRINK}. --> + <enum name="canShrink" value="1" /> + <!-- If possible, height should be greater than or equal to the specified height. + See {@link android.widget.GridLayout#CAN_STRETCH}. --> + <enum name="canStretch" value="2" /> + </attr> </declare-styleable> <declare-styleable name="FrameLayout_Layout"> <attr name="layout_gravity" /> @@ -4554,6 +4586,25 @@ for more info. --> <attr name="actionViewClass" format="string" /> + <!-- The name of an optional ActionProvider class to instantiate an action view + and perform operations such as default action for that menu item. + See {@link android.view.MenuItem#setActionProvider(android.view.ActionProvider)} + for more info. --> + <attr name="actionProviderClass" format="string" /> + + </declare-styleable> + + <!-- Attrbitutes for a ActvityChooserView. --> + <declare-styleable name="ActivityChooserView"> + <!-- The maximal number of items initially shown in the activity list. --> + <attr name="initialActivityCount" format="string" /> + <!-- The drawable to show in the button for expanding the activities overflow popup. + <strong>Note:</strong> Clients would like to set this drawable + as a clue about the action the chosen activity will perform. For + example, if share activity is to be chosen the drawable should + give a clue that sharing is to be performed. + --> + <attr name="expandActivityOverflowButtonDrawable" format="reference" /> </declare-styleable> <!-- **************************************************************** --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 827153e..87b9be4 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -441,18 +441,6 @@ <!-- Enables swipe versus poly-finger touch disambiguation in the KeyboardView --> <bool name="config_swipeDisambiguation">true</bool> - <!-- Enables special filtering code in the framework for raw touch events - from the touch driver. This code exists for one particular device, - and should not be enabled for any others. Hopefully in the future - it will be removed when the lower-level touch driver generates better - data. --> - <bool name="config_filterTouchEvents">false</bool> - - <!-- Enables special filtering code in the framework for raw touch events - from the touch driver. This code exists for one particular device, - and should not be enabled for any others. --> - <bool name="config_filterJumpyTouchEvents">false</bool> - <!-- Specifies the amount of time to disable virtual keys after the screen is touched in order to filter out accidental virtual key presses due to swiping gestures or taps near the edge of the display. May be 0 to disable the feature. @@ -658,4 +646,8 @@ This is intended to allow packaging drivers or tools for installation on a PC. --> <string translatable="false" name="config_isoImagePath"></string> + <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be + autodetected from the Configuration. --> + <bool name="config_showNavigationBar">false</bool> + </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index db6f98f..54e484e 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1738,9 +1738,11 @@ <public type="attr" name="layout_row" /> <public type="attr" name="layout_rowSpan" /> - <public type="attr" name="layout_rowWeight" /> <public type="attr" name="layout_columnSpan" /> - <public type="attr" name="layout_columnWeight" /> + + <public type="attr" name="layout_widthSpec" /> + <public type="attr" name="layout_heightSpec" /> + <public type="attr" name="actionModeSelectAllDrawable" /> <public type="attr" name="isAuxiliary" /> @@ -1783,4 +1785,7 @@ <public type="string" name="status_bar_notification_info_overflow" /> <public type="attr" name="textDirection"/> + + <public type="attr" name="actionProviderClass" /> + </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 88ed9c6..9897c80 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2073,6 +2073,18 @@ <!-- Do not translate. Regex used by AutoFill. --> <string name="autofill_fax_re">fax<!-- fr-FR -->|télécopie|telecopie<!-- ja-JP -->|ファックス<!-- ru -->|факс<!-- zh-CN -->|传真<!-- zh-TW -->|傳真</string> + <!-- Do not translate. Regex used by AutoFill. --> + <string name="autofill_country_code_re">country.*code|ccode|_cc</string> + + <!-- Do not translate. Regex used by AutoFill. --> + <string name="autofill_area_code_notext_re">^\($</string> + + <!-- Do not translate. Regex used by AutoFill. --> + <string name="autofill_phone_prefix_separator_re">^-$|^\)$</string> + + <!-- Do not translate. Regex used by AutoFill. --> + <string name="autofill_phone_suffix_separator_re">^-$</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readHistoryBookmarks">read Browser\'s history and bookmarks</string> @@ -2613,10 +2625,14 @@ <!-- USB_STORAGE_ERROR dialog ok button--> <string name="dlg_ok">OK</string> - <!-- USB_PREFERENCES: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across. This is the title --> - <string name="usb_preferences_notification_title">USB connected</string> + <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in MTP mode. This is the title --> + <string name="usb_mtp_notification_title">Connected as a media device</string> + <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in PTP mode. This is the title --> + <string name="usb_ptp_notification_title">Connected as a camera</string> + <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in mass storage mode (for installer CD image). This is the title --> + <string name="usb_cd_installer_notification_title">Connected as an installer</string> <!-- See USB_PREFERENCES. This is the message. --> - <string name="usb_preferece_notification_message">Select to configure USB file transfer.</string> + <string name="usb_notification_message">Touch for other USB options</string> <!-- External media format dialog strings --> <!-- This is the label for the activity, and should never be visible to the user. --> @@ -2783,15 +2799,10 @@ <!-- Do Not Translate: Alternate eri.xml --> <string name="alternate_eri_file">/data/eri.xml</string> - <string name="pptp_vpn_description">Point-to-Point Tunneling Protocol</string> - <string name="l2tp_vpn_description">Layer 2 Tunneling Protocol</string> - <string name="l2tp_ipsec_psk_vpn_description">Pre-shared key based L2TP/IPSec VPN</string> - <string name="l2tp_ipsec_crt_vpn_description">Certificate based L2TP/IPSec VPN</string> - - <!-- Ticker text to show when VPN is active. --> - <string name="vpn_ticker"><xliff:g id="app" example="FooVPN client">%s</xliff:g> is activating VPN...</string> <!-- The title of the notification when VPN is active. --> - <string name="vpn_title">VPN is activated by <xliff:g id="app" example="FooVPN client">%s</xliff:g></string> + <string name="vpn_title">VPN is activated.</string> + <!-- The title of the notification when VPN is active with an application name. --> + <string name="vpn_title_long">VPN is activated by <xliff:g id="app" example="FooVPN client">%s</xliff:g></string> <!-- The text of the notification when VPN is active. --> <string name="vpn_text">Tap to manage the network.</string> <!-- The text of the notification when VPN is active with a session name. --> @@ -2915,13 +2926,6 @@ <!-- Dialog action for when there are too many deletes that would take place and we want user confirmation, and the user wants to do nothing for now --> <string name="sync_do_nothing">Do nothing for now.</string> - <!-- Title of the VPN service notification: VPN connected [CHAR LIMIT=NONE] --> - <string name="vpn_notification_title_connected"><xliff:g id="profilename" example="Home PPTP">%s</xliff:g> VPN connected</string> - <!-- Title of the VPN service notification: VPN disconnected [CHAR LIMIT=NONE] --> - <string name="vpn_notification_title_disconnected"><xliff:g id="profilename" example="Home PPTP">%s</xliff:g> VPN disconnected</string> - <!-- Message of the VPN service notification: Hint to reconnect VPN [CHAR LIMIT=NONE] --> - <string name="vpn_notification_hint_disconnected">Touch to reconnect to a VPN.</string> - <!-- Choose Account Activity label --> <string name="choose_account_label">Select an account</string> @@ -2986,4 +2990,11 @@ <!-- Label for an information field on an SSL Certificate Dialog --> <string name="expires_on">Expires on:</string> + <!-- Title for a button to expand the list of activities in ActivityChooserView [CHAR LIMIT=25] --> + <string name="activity_chooser_view_see_all">See all...</string> + <!-- Title for a message that there are no activities in ActivityChooserView [CHAR LIMIT=25] --> + <string name="activity_chooser_view_no_activities">No activities</string> + <!-- Title for a message that prompts selection of a default share handler in ActivityChooserView [CHAR LIMIT=25] --> + <string name="activity_chooser_view_select_default">Select default</string> + </resources> diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java index a37f1a3..6db67c0 100644 --- a/core/tests/coretests/src/android/widget/TextViewTest.java +++ b/core/tests/coretests/src/android/widget/TextViewTest.java @@ -177,4 +177,38 @@ public class TextViewTest extends AndroidTestCase { tv.setTextDirection(View.TEXT_DIRECTION_RTL); assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); } + + @SmallTest + public void testCharCountHeuristic() { + LinearLayout ll = new LinearLayout(mContext); + ll.setLayoutDirection(View.LAYOUT_DIRECTION_RTL); + + TextView tv = new TextView(mContext); + ll.addView(tv); + + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("this is a test"); + assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection()); + + // resetResolvedTextDirection is not part of the public API so simply use setTextDirection + tv.setTextDirection(View.TEXT_DIRECTION_LTR); + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("\u05DD\u05DE"); // hebrew + assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); + + tv.setTextDirection(View.TEXT_DIRECTION_LTR); + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("this is a test \u05DD\u05DE"); // latin more than 60% + hebrew + assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection()); + + tv.setTextDirection(View.TEXT_DIRECTION_LTR); + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("t \u05DD\u05DE"); // latin + hebrew more than 60% + assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); + + tv.setTextDirection(View.TEXT_DIRECTION_LTR); + tv.setTextDirection(View.TEXT_DIRECTION_CHAR_COUNT); + tv.setText("ab \u05DD\u05DE"); // latin + hebrew at 50% each + assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); + } } diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 90a7ac2..1647ff34 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -174,12 +174,15 @@ public class SurfaceTexture { * Retrieve the timestamp associated with the texture image set by the most recent call to * updateTexImage. * - * This timestamp is in nanoseconds, and is guaranteed to be monotonically increasing. The + * This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp + * should be unaffected by time-of-day adjustments, and for a camera should be strictly + * monotonic but for a MediaPlayer may be reset when the position is set. The * specific meaning and zero point of the timestamp depends on the source providing images to * the SurfaceTexture. Unless otherwise specified by the image source, timestamps cannot * generally be compared across SurfaceTexture instances, or across multiple program * invocations. It is mostly useful for determining time offsets between subsequent frames. */ + public long getTimestamp() { return nativeGetTimestamp(); } diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index c82fb9b..e36360c 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -139,7 +139,7 @@ public: // setFrameAvailableListener sets the listener object that will be notified // when a new frame becomes available. - void setFrameAvailableListener(const sp<FrameAvailableListener>& l); + void setFrameAvailableListener(const sp<FrameAvailableListener>& listener); // getAllocator retrieves the binder object that must be referenced as long // as the GraphicBuffers dequeued from this SurfaceTexture are referenced. @@ -343,7 +343,7 @@ private: uint32_t mNextTransform; // mTexName is the name of the OpenGL texture to which streamed images will - // be bound when updateTexImage is called. It is set at construction time + // be bound when updateTexImage is called. It is set at construction time // changed with a call to setTexName. const GLuint mTexName; diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h index 18e8a5f..4328d3c 100644 --- a/include/media/MediaPlayerInterface.h +++ b/include/media/MediaPlayerInterface.h @@ -103,6 +103,10 @@ public: virtual status_t initCheck() = 0; virtual bool hardwareOutput() = 0; + virtual status_t setUID(uid_t uid) { + return INVALID_OPERATION; + } + virtual status_t setDataSource( const char *url, const KeyedVector<String8, String8> *headers = NULL) = 0; diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h index a31395e..37dbcd8 100644 --- a/include/media/stagefright/MediaSource.h +++ b/include/media/stagefright/MediaSource.h @@ -22,6 +22,7 @@ #include <media/stagefright/MediaErrors.h> #include <utils/RefBase.h> +#include <utils/Vector.h> namespace android { @@ -99,6 +100,15 @@ struct MediaSource : public RefBase { return ERROR_UNSUPPORTED; } + // The consumer of this media source requests that the given buffers + // are to be returned exclusively in response to read calls. + // This will be called after a successful start() and before the + // first read() call. + // Callee assumes ownership of the buffers if no error is returned. + virtual status_t setBuffers(const Vector<MediaBuffer *> &buffers) { + return ERROR_UNSUPPORTED; + } + protected: virtual ~MediaSource(); diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h index 99b72ad..57f678c 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -121,6 +121,8 @@ enum { // To store the timed text format data kKeyTextFormatData = 'text', // raw data + + kKeyRequiresSecureBuffers = 'secu', // bool (int32_t) }; enum { diff --git a/include/media/stagefright/MetadataBufferType.h b/include/media/stagefright/MetadataBufferType.h new file mode 100644 index 0000000..275c19f --- /dev/null +++ b/include/media/stagefright/MetadataBufferType.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METADATA_BUFFER_TYPE_H +#define METADATA_BUFFER_TYPE_H + +namespace android { +/* + * MetadataBufferType defines the type of the metadata buffers that + * can be passed to video encoder component for encoding, via Stagefright + * media recording framework. To see how to work with the metadata buffers + * in media recording framework, please consult HardwareAPI.h + * + * The creator of metadata buffers and video encoder share common knowledge + * on what is actually being stored in these metadata buffers, and + * how the information can be used by the video encoder component + * to locate the actual pixel data as the source input for video + * encoder, plus whatever other information that is necessary. Stagefright + * media recording framework does not need to know anything specific about the + * metadata buffers, except for receving each individual metadata buffer + * as the source input, making a copy of the metadata buffer, and passing the + * copy via OpenMAX API to the video encoder component. + * + * The creator of the metadata buffers must ensure that the first + * 4 bytes in every metadata buffer indicates its buffer type, + * and the rest of the metadata buffer contains the + * actual metadata information. When a video encoder component receives + * a metadata buffer, it uses the first 4 bytes in that buffer to find + * out the type of the metadata buffer, and takes action appropriate + * to that type of metadata buffers (for instance, locate the actual + * pixel data input and then encoding the input data to produce a + * compressed output buffer). + * + * The following shows the layout of a metadata buffer, + * where buffer type is a 4-byte field of MetadataBufferType, + * and the payload is the metadata information. + * + * -------------------------------------------------------------- + * | buffer type | payload | + * -------------------------------------------------------------- + * + */ +typedef enum { + + /* + * kMetadataBufferTypeCameraSource is used to indicate that + * the source of the metadata buffer is the camera component. + */ + kMetadataBufferTypeCameraSource = 0, + + /* + * kMetadataBufferTypeGrallocSource is used to indicate that + * the payload of the metadata buffers can be interpreted as + * a buffer_handle_t. + */ + kMetadataBufferTypeGrallocSource = 1, + + // Add more here... + +} MetadataBufferType; + +} // namespace android + +#endif // METADATA_BUFFER_TYPE_H diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h index 92331a1..7f3c497 100644 --- a/include/media/stagefright/OMXCodec.h +++ b/include/media/stagefright/OMXCodec.h @@ -53,6 +53,9 @@ struct OMXCodec : public MediaSource, // Enable GRALLOC_USAGE_PROTECTED for output buffers from native window kEnableGrallocUsageProtected = 128, + + // Secure decoding mode + kUseSecureInputBuffers = 256, }; static sp<MediaSource> Create( const sp<IOMX> &omx, @@ -164,6 +167,10 @@ private: bool mOMXLivesLocally; IOMX::node_id mNode; uint32_t mQuirks; + + // Flags specified in the creation of the codec. + uint32_t mFlags; + bool mIsEncoder; char *mMIME; char *mComponentName; @@ -205,15 +212,12 @@ private: List<size_t> mFilledBuffers; Condition mBufferFilled; - bool mIsMetaDataStoredInVideoBuffers; - bool mOnlySubmitOneBufferAtOneTime; - bool mEnableGrallocUsageProtected; - // Used to record the decoding time for an output picture from // a video encoder. List<int64_t> mDecodingTimeList; - OMXCodec(const sp<IOMX> &omx, IOMX::node_id node, uint32_t quirks, + OMXCodec(const sp<IOMX> &omx, IOMX::node_id node, + uint32_t quirks, uint32_t flags, bool isEncoder, const char *mime, const char *componentName, const sp<MediaSource> &source, const sp<ANativeWindow> &nativeWindow); @@ -287,6 +291,10 @@ private: void drainInputBuffers(); void fillOutputBuffers(); + bool drainAnyInputBuffer(); + BufferInfo *findInputBufferByDataPointer(void *ptr); + BufferInfo *findEmptyInputBuffer(); + // Returns true iff a flush was initiated and a completion event is // upcoming, false otherwise (A flush was not necessary as we own all // the buffers on that port). @@ -313,7 +321,7 @@ private: void dumpPortStatus(OMX_U32 portIndex); - status_t configureCodec(const sp<MetaData> &meta, uint32_t flags); + status_t configureCodec(const sp<MetaData> &meta); static uint32_t getComponentQuirks( const char *componentName, bool isEncoder); diff --git a/include/utils/BitSet.h b/include/utils/BitSet.h index de748b5..600017e 100644 --- a/include/utils/BitSet.h +++ b/include/utils/BitSet.h @@ -44,6 +44,9 @@ struct BitSet32 { // Returns true if the bit set does not contain any marked bits. inline bool isEmpty() const { return ! value; } + // Returns true if the bit set does not contain any unmarked bits. + inline bool isFull() const { return value == 0xffffffff; } + // Returns true if the specified bit is marked. inline bool hasBit(uint32_t n) const { return value & valueForBit(n); } diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index b567207..e91bcab 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -45,8 +45,12 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import libcore.util.Objects; +import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore; /** * The {@code KeyChain} class provides access to private keys and @@ -385,7 +389,21 @@ public final class KeyChain { } IKeyChainService keyChainService = keyChainConnection.getService(); byte[] certificateBytes = keyChainService.getCertificate(alias, authToken); - return new X509Certificate[] { toCertificate(certificateBytes) }; + List<X509Certificate> chain = new ArrayList<X509Certificate>(); + chain.add(toCertificate(certificateBytes)); + TrustedCertificateStore store = new TrustedCertificateStore(); + for (int i = 0; true; i++) { + X509Certificate cert = chain.get(i); + if (Objects.equal(cert.getSubjectX500Principal(), cert.getIssuerX500Principal())) { + break; + } + X509Certificate issuer = store.findIssuer(cert); + if (issuer == null) { + break; + } + chain.add(issuer); + } + return chain.toArray(new X509Certificate[chain.size()]); } catch (RemoteException e) { throw new KeyChainException(e); } catch (RuntimeException e) { diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index 0925001..3bf6477 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -148,6 +148,11 @@ status_t SurfaceTexture::setBufferCount(int bufferCount) { LOGV("SurfaceTexture::setBufferCount"); Mutex::Autolock lock(mMutex); + if (bufferCount > NUM_BUFFER_SLOTS) { + LOGE("setBufferCount: bufferCount larger than slots available"); + return BAD_VALUE; + } + // Error out if the user has dequeued buffers for (int i=0 ; i<mBufferCount ; i++) { if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) { @@ -208,7 +213,7 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, uint32_t format, uint32_t usage) { LOGV("SurfaceTexture::dequeueBuffer"); - if ((w && !h) || (!w & h)) { + if ((w && !h) || (!w && h)) { LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h); return BAD_VALUE; } @@ -699,10 +704,10 @@ nsecs_t SurfaceTexture::getTimestamp() { } void SurfaceTexture::setFrameAvailableListener( - const sp<FrameAvailableListener>& l) { + const sp<FrameAvailableListener>& listener) { LOGV("SurfaceTexture::setFrameAvailableListener"); Mutex::Autolock lock(mMutex); - mFrameAvailableListener = l; + mFrameAvailableListener = listener; } sp<IBinder> SurfaceTexture::getAllocator() { diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java index ffc3346..29dec63 100755 --- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java +++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java @@ -205,7 +205,7 @@ public class GpsNetInitiatedHandler { mNiNotification.defaults &= ~Notification.DEFAULT_SOUND; } - mNiNotification.flags = Notification.FLAG_ONGOING_EVENT; + mNiNotification.flags = Notification.FLAG_ONGOING_EVENT | Notification.FLAG_AUTO_CANCEL; mNiNotification.tickerText = getNotifTicker(notif, mContext); // if not to popup dialog immediately, pending intent will open the dialog diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 33312d1..482b437 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -611,15 +611,11 @@ public class MediaPlayer * needed. Not calling this method when playing back a video will * result in only the audio track being played. * - * @param sh the SurfaceHolder to use for video display - */ - /* - * This portion of comment has a non-Javadoc prefix so as not to refer to a - * hidden method. When unhidden, merge it with the previous javadoc comment. - * * Either a surface or surface texture must be set if a display or video sink * is needed. Not calling this method or {@link #setTexture(SurfaceTexture)} * when playing back a video will result in only the audio track being played. + * + * @param sh the SurfaceHolder to use for video display */ public void setDisplay(SurfaceHolder sh) { mSurfaceHolder = sh; @@ -648,7 +644,8 @@ public class MediaPlayer * SurfaceTexture set as the video sink have an unspecified zero point, * and cannot be directly compared between different media sources or different * instances of the same media source, or across multiple runs of the same - * program. + * program. The timestamp is normally monotonically increasing and unaffected + * by time-of-day adjustments, but is reset when the position is set. */ public void setTexture(SurfaceTexture st) { ParcelSurfaceTexture pst = null; diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index a77dff1..1e7c969 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -250,7 +250,11 @@ sp<IMediaPlayer> MediaPlayerService::create( const KeyedVector<String8, String8> *headers, int audioSessionId) { int32_t connId = android_atomic_inc(&mNextConnId); - sp<Client> c = new Client(this, pid, connId, client, audioSessionId); + + sp<Client> c = new Client( + this, pid, connId, client, audioSessionId, + IPCThreadState::self()->getCallingUid()); + LOGV("Create new client(%d) from pid %d, url=%s, connId=%d, audioSessionId=%d", connId, pid, url, connId, audioSessionId); if (NO_ERROR != c->setDataSource(url, headers)) @@ -268,7 +272,11 @@ sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClie int fd, int64_t offset, int64_t length, int audioSessionId) { int32_t connId = android_atomic_inc(&mNextConnId); - sp<Client> c = new Client(this, pid, connId, client, audioSessionId); + + sp<Client> c = new Client( + this, pid, connId, client, audioSessionId, + IPCThreadState::self()->getCallingUid()); + LOGV("Create new client(%d) from pid %d, fd=%d, offset=%lld, length=%lld, audioSessionId=%d", connId, pid, fd, offset, length, audioSessionId); if (NO_ERROR != c->setDataSource(fd, offset, length)) { @@ -286,7 +294,10 @@ sp<IMediaPlayer> MediaPlayerService::create( pid_t pid, const sp<IMediaPlayerClient> &client, const sp<IStreamSource> &source, int audioSessionId) { int32_t connId = android_atomic_inc(&mNextConnId); - sp<Client> c = new Client(this, pid, connId, client, audioSessionId); + + sp<Client> c = new Client( + this, pid, connId, client, audioSessionId, + IPCThreadState::self()->getCallingUid()); LOGV("Create new client(%d) from pid %d, audioSessionId=%d", connId, pid, audioSessionId); @@ -496,8 +507,10 @@ void MediaPlayerService::removeClient(wp<Client> client) mClients.remove(client); } -MediaPlayerService::Client::Client(const sp<MediaPlayerService>& service, pid_t pid, - int32_t connId, const sp<IMediaPlayerClient>& client, int audioSessionId) +MediaPlayerService::Client::Client( + const sp<MediaPlayerService>& service, pid_t pid, + int32_t connId, const sp<IMediaPlayerClient>& client, + int audioSessionId, uid_t uid) { LOGV("Client(%d) constructor", connId); mPid = pid; @@ -507,6 +520,7 @@ MediaPlayerService::Client::Client(const sp<MediaPlayerService>& service, pid_t mLoop = false; mStatus = NO_INIT; mAudioSessionId = audioSessionId; + mUID = uid; #if CALLBACK_ANTAGONIZER LOGD("create Antagonizer"); @@ -671,6 +685,9 @@ sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerT if (p == NULL) { p = android::createPlayer(playerType, this, notify); } + + p->setUID(mUID); + return p; } diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index 8bab471..e32b92a 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -306,7 +306,8 @@ private: pid_t pid, int32_t connId, const sp<IMediaPlayerClient>& client, - int audioSessionId); + int audioSessionId, + uid_t uid); Client(); virtual ~Client(); @@ -336,6 +337,7 @@ private: bool mLoop; int32_t mConnId; int mAudioSessionId; + uid_t mUID; // Metadata filters. media::Metadata::Filter mMetadataAllow; // protected by mLock diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp index 870e290..40e055c 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.cpp +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -47,6 +47,12 @@ status_t StagefrightPlayer::initCheck() { return OK; } +status_t StagefrightPlayer::setUID(uid_t uid) { + mPlayer->setUID(uid); + + return OK; +} + status_t StagefrightPlayer::setDataSource( const char *url, const KeyedVector<String8, String8> *headers) { return mPlayer->setDataSource(url, headers); diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h index 85a546d..cbc6d49 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.h +++ b/media/libmediaplayerservice/StagefrightPlayer.h @@ -31,6 +31,8 @@ public: virtual status_t initCheck(); + virtual status_t setUID(uid_t uid); + virtual status_t setDataSource( const char *url, const KeyedVector<String8, String8> *headers); diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp index b3b3af5..5a5330d 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp @@ -35,8 +35,11 @@ namespace android { NuPlayer::HTTPLiveSource::HTTPLiveSource( const char *url, - const KeyedVector<String8, String8> *headers) + const KeyedVector<String8, String8> *headers, + bool uidValid, uid_t uid) : mURL(url), + mUIDValid(uidValid), + mUID(uid), mFlags(0), mEOS(false), mOffset(0) { @@ -65,7 +68,8 @@ void NuPlayer::HTTPLiveSource::start() { mLiveLooper->start(); mLiveSession = new LiveSession( - (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0); + (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0, + mUIDValid, mUID); mLiveLooper->registerHandler(mLiveSession); diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h index 7a337e9..36c67c5 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h @@ -29,7 +29,9 @@ struct LiveSession; struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { HTTPLiveSource( const char *url, - const KeyedVector<String8, String8> *headers); + const KeyedVector<String8, String8> *headers, + bool uidValid = false, + uid_t uid = 0); virtual void start(); @@ -54,6 +56,8 @@ private: AString mURL; KeyedVector<String8, String8> mExtraHeaders; + bool mUIDValid; + uid_t mUID; uint32_t mFlags; bool mEOS; off64_t mOffset; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index effa703..b06f20d 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -44,7 +44,8 @@ namespace android { //////////////////////////////////////////////////////////////////////////////// NuPlayer::NuPlayer() - : mAudioEOS(false), + : mUIDValid(false), + mAudioEOS(false), mVideoEOS(false), mScanSourcesPending(false), mScanSourcesGeneration(0), @@ -57,6 +58,11 @@ NuPlayer::NuPlayer() NuPlayer::~NuPlayer() { } +void NuPlayer::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + void NuPlayer::setDriver(const wp<NuPlayerDriver> &driver) { mDriver = driver; } @@ -72,7 +78,7 @@ void NuPlayer::setDataSource( const char *url, const KeyedVector<String8, String8> *headers) { sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); - msg->setObject("source", new HTTPLiveSource(url, headers)); + msg->setObject("source", new HTTPLiveSource(url, headers, mUIDValid, mUID)); msg->post(); } diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h index fb5b001..cf9185b 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -33,6 +33,8 @@ struct NuPlayerDriver; struct NuPlayer : public AHandler { NuPlayer(); + void setUID(uid_t uid); + void setDriver(const wp<NuPlayerDriver> &driver); void setDataSource(const sp<IStreamSource> &source); @@ -84,6 +86,8 @@ private: }; wp<NuPlayerDriver> mDriver; + bool mUIDValid; + uid_t mUID; sp<Source> mSource; sp<NativeWindowWrapper> mNativeWindow; sp<MediaPlayerBase::AudioSink> mAudioSink; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp index e1213f4..7cd8b6c 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp @@ -55,6 +55,12 @@ status_t NuPlayerDriver::initCheck() { return OK; } +status_t NuPlayerDriver::setUID(uid_t uid) { + mPlayer->setUID(uid); + + return OK; +} + status_t NuPlayerDriver::setDataSource( const char *url, const KeyedVector<String8, String8> *headers) { CHECK_EQ((int)mState, (int)UNINITIALIZED); diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h index 145fd80..1bb7ca2 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h @@ -28,6 +28,8 @@ struct NuPlayerDriver : public MediaPlayerInterface { virtual status_t initCheck(); + virtual status_t setUID(uid_t uid); + virtual status_t setDataSource( const char *url, const KeyedVector<String8, String8> *headers); diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index 513eda8..d4d07b2 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -1190,6 +1190,17 @@ bool ACodec::BaseState::onOMXMessage(const sp<AMessage> &msg) { CHECK(msg->findInt32("data1", &data1)); CHECK(msg->findInt32("data2", &data2)); + if (event == OMX_EventCmdComplete + && data1 == OMX_CommandFlush + && data2 == (int32_t)OMX_ALL) { + // Use of this notification is not consistent across + // implementations. We'll drop this notification and rely + // on flush-complete notifications on the individual port + // indices instead. + + return true; + } + return onOMXEvent( static_cast<OMX_EVENTTYPE>(event), static_cast<OMX_U32>(data1), @@ -2119,6 +2130,7 @@ bool ACodec::ExecutingToIdleState::onOMXEvent( return BaseState::onOMXEvent(event, data1, data2); } } + void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() { if (mCodec->allYourBuffersAreBelongToUs()) { CHECK_EQ(mCodec->mOMX->sendCommand( @@ -2282,6 +2294,11 @@ bool ACodec::FlushingState::onOMXEvent( if (data2 == kPortIndexInput || data2 == kPortIndexOutput) { CHECK(!mFlushComplete[data2]); mFlushComplete[data2] = true; + + if (mFlushComplete[kPortIndexInput] + && mFlushComplete[kPortIndexOutput]) { + changeStateIfWeOwnAllBuffers(); + } } else { CHECK_EQ(data2, OMX_ALL); CHECK(mFlushComplete[kPortIndexInput]); diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index aa7edcc..77c25d1 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -128,6 +128,9 @@ struct AwesomeNativeWindowRenderer : public AwesomeRenderer { } virtual void render(MediaBuffer *buffer) { + int64_t timeUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs)); + native_window_set_buffers_timestamp(mNativeWindow.get(), timeUs * 1000); status_t err = mNativeWindow->queueBuffer( mNativeWindow.get(), buffer->graphicBuffer().get()); if (err != 0) { @@ -180,6 +183,7 @@ void addBatteryData(uint32_t params) { //////////////////////////////////////////////////////////////////////////////// AwesomePlayer::AwesomePlayer() : mQueueStarted(false), + mUIDValid(false), mTimeSource(NULL), mVideoRendererIsPreview(false), mAudioPlayer(NULL), @@ -243,6 +247,13 @@ void AwesomePlayer::setListener(const wp<MediaPlayerBase> &listener) { mListener = listener; } +void AwesomePlayer::setUID(uid_t uid) { + LOGI("AwesomePlayer running on behalf of uid %d", uid); + + mUID = uid; + mUIDValid = true; +} + status_t AwesomePlayer::setDataSource( const char *uri, const KeyedVector<String8, String8> *headers) { Mutex::Autolock autoLock(mLock); @@ -1928,6 +1939,10 @@ status_t AwesomePlayer::finishSetDataSource_l() { ? HTTPBase::kFlagIncognito : 0); + if (mUIDValid) { + mConnectingDataSource->setUID(mUID); + } + mLock.unlock(); status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders); mLock.lock(); @@ -2009,6 +2024,10 @@ status_t AwesomePlayer::finishSetDataSource_l() { mRTSPController = new ARTSPController(mLooper); mConnectingRTSPController = mRTSPController; + if (mUIDValid) { + mConnectingRTSPController->setUID(mUID); + } + mLock.unlock(); status_t err = mRTSPController->connect(mUri.string()); mLock.lock(); diff --git a/media/libstagefright/HTTPBase.cpp b/media/libstagefright/HTTPBase.cpp index c0ae29d..0d24551 100644 --- a/media/libstagefright/HTTPBase.cpp +++ b/media/libstagefright/HTTPBase.cpp @@ -37,7 +37,8 @@ HTTPBase::HTTPBase() mTotalTransferBytes(0), mPrevBandwidthMeasureTimeUs(0), mPrevEstimatedBandWidthKbps(0), - mBandWidthCollectFreqMs(5000) { + mBandWidthCollectFreqMs(5000), + mUIDValid(false) { } // static @@ -119,4 +120,19 @@ status_t HTTPBase::setBandwidthStatCollectFreq(int32_t freqMs) { return OK; } +void HTTPBase::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + +bool HTTPBase::getUID(uid_t *uid) const { + if (!mUIDValid) { + return false; + } + + *uid = mUID; + + return true; +} + } // namespace android diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp index a156da6..d526ebd 100644 --- a/media/libstagefright/HTTPStream.cpp +++ b/media/libstagefright/HTTPStream.cpp @@ -43,6 +43,7 @@ const char *HTTPStream::kStatusKey = ":status:"; // MUST be lowercase. HTTPStream::HTTPStream() : mState(READY), + mUIDValid(false), mSocket(-1), mSSLContext(NULL), mSSL(NULL) { @@ -57,6 +58,11 @@ HTTPStream::~HTTPStream() { } } +void HTTPStream::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + static bool MakeSocketBlocking(int s, bool blocking) { // Make socket non-blocking. int flags = fcntl(s, F_GETFL, 0); @@ -250,6 +256,10 @@ status_t HTTPStream::connect(const char *server, int port, bool https) { continue; } + if (mUIDValid) { + RegisterSocketUser(mSocket, mUID); + } + setReceiveTimeout(30); // Time out reads after 30 secs by default. int s = mSocket; @@ -596,5 +606,18 @@ void HTTPStream::setReceiveTimeout(int seconds) { CHECK_EQ(0, setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))); } +// static +void HTTPStream::RegisterSocketUser(int s, uid_t uid) { + // Lower bits MUST be 0. + static const uint64_t kTag = 0xdeadbeef00000000ll; + + AString line = StringPrintf("t %d %llu %d", s, kTag, uid); + + int fd = open("/proc/net/xt_qtaguid/ctrl", O_WRONLY); + write(fd, line.c_str(), line.size()); + close(fd); + fd = -1; +} + } // namespace android diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp index dac2ee4..2949767 100644 --- a/media/libstagefright/NuHTTPDataSource.cpp +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -140,6 +140,11 @@ status_t NuHTTPDataSource::connect( return ERROR_MALFORMED; } + uid_t uid; + if (getUID(&uid)) { + mHTTP.setUID(uid); + } + return connect(host, port, path, https, headers, offset); } diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index e36b01f..1ac2c1f 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -477,6 +477,15 @@ sp<MediaSource> OMXCodec::Create( const char *matchComponentName, uint32_t flags, const sp<ANativeWindow> &nativeWindow) { + int32_t requiresSecureBuffers; + if (source->getFormat()->findInt32( + kKeyRequiresSecureBuffers, + &requiresSecureBuffers) + && requiresSecureBuffers) { + flags |= kIgnoreCodecSpecificData; + flags |= kUseSecureInputBuffers; + } + const char *mime; bool success = meta->findCString(kKeyMIMEType, &mime); CHECK(success); @@ -530,17 +539,17 @@ sp<MediaSource> OMXCodec::Create( LOGV("Successfully allocated OMX node '%s'", componentName); sp<OMXCodec> codec = new OMXCodec( - omx, node, quirks, + omx, node, quirks, flags, createEncoder, mime, componentName, source, nativeWindow); observer->setCodec(codec); - err = codec->configureCodec(meta, flags); + err = codec->configureCodec(meta); if (err == OK) { if (!strcmp("OMX.Nvidia.mpeg2v.decode", componentName)) { - codec->mOnlySubmitOneBufferAtOneTime = true; + codec->mFlags |= kOnlySubmitOneInputBufferAtOneTime; } return codec; @@ -553,24 +562,11 @@ sp<MediaSource> OMXCodec::Create( return NULL; } -status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) { - mIsMetaDataStoredInVideoBuffers = false; - if (flags & kStoreMetaDataInVideoBuffers) { - mIsMetaDataStoredInVideoBuffers = true; - } - - mOnlySubmitOneBufferAtOneTime = false; - if (flags & kOnlySubmitOneInputBufferAtOneTime) { - mOnlySubmitOneBufferAtOneTime = true; - } +status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { + LOGV("configureCodec protected=%d", + (mFlags & kEnableGrallocUsageProtected) ? 1 : 0); - mEnableGrallocUsageProtected = false; - if (flags & kEnableGrallocUsageProtected) { - mEnableGrallocUsageProtected = true; - } - LOGV("configureCodec protected=%d", mEnableGrallocUsageProtected); - - if (!(flags & kIgnoreCodecSpecificData)) { + if (!(mFlags & kIgnoreCodecSpecificData)) { uint32_t type; const void *data; size_t size; @@ -745,7 +741,7 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) { initOutputFormat(meta); - if ((flags & kClientNeedsFramebuffer) + if ((mFlags & kClientNeedsFramebuffer) && !strncmp(mComponentName, "OMX.SEC.", 8)) { OMX_INDEXTYPE index; @@ -1468,7 +1464,8 @@ status_t OMXCodec::setVideoOutputFormat( } OMXCodec::OMXCodec( - const sp<IOMX> &omx, IOMX::node_id node, uint32_t quirks, + const sp<IOMX> &omx, IOMX::node_id node, + uint32_t quirks, uint32_t flags, bool isEncoder, const char *mime, const char *componentName, @@ -1478,6 +1475,7 @@ OMXCodec::OMXCodec( mOMXLivesLocally(omx->livesLocally(getpid())), mNode(node), mQuirks(quirks), + mFlags(flags), mIsEncoder(isEncoder), mMIME(strdup(mime)), mComponentName(strdup(componentName)), @@ -1645,13 +1643,14 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { return allocateOutputBuffersFromNativeWindow(); } - if (mEnableGrallocUsageProtected && portIndex == kPortIndexOutput) { + if ((mFlags & kEnableGrallocUsageProtected) && portIndex == kPortIndexOutput) { LOGE("protected output buffers must be stent to an ANativeWindow"); return PERMISSION_DENIED; } status_t err = OK; - if (mIsMetaDataStoredInVideoBuffers && portIndex == kPortIndexInput) { + if ((mFlags & kStoreMetaDataInVideoBuffers) + && portIndex == kPortIndexInput) { err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexInput, OMX_TRUE); if (err != OK) { LOGE("Storing meta data in video buffers is not supported"); @@ -1687,7 +1686,8 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { IOMX::buffer_id buffer; if (portIndex == kPortIndexInput - && (mQuirks & kRequiresAllocateBufferOnInputPorts)) { + && ((mQuirks & kRequiresAllocateBufferOnInputPorts) + || (mFlags & kUseSecureInputBuffers))) { if (mOMXLivesLocally) { mem.clear(); @@ -1748,6 +1748,31 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { // dumpPortStatus(portIndex); + if (portIndex == kPortIndexInput && (mFlags & kUseSecureInputBuffers)) { + Vector<MediaBuffer *> buffers; + for (size_t i = 0; i < def.nBufferCountActual; ++i) { + const BufferInfo &info = mPortBuffers[kPortIndexInput].itemAt(i); + + MediaBuffer *mbuf = new MediaBuffer(info.mData, info.mSize); + buffers.push(mbuf); + } + + status_t err = mSource->setBuffers(buffers); + + if (err != OK) { + for (size_t i = 0; i < def.nBufferCountActual; ++i) { + buffers.editItemAt(i)->release(); + } + buffers.clear(); + + CODEC_LOGE( + "Codec requested to use secure input buffers but " + "upstream source didn't support that."); + + return err; + } + } + return OK; } @@ -1815,7 +1840,7 @@ status_t OMXCodec::allocateOutputBuffersFromNativeWindow() { // XXX: Currently this error is logged, but not fatal. usage = 0; } - if (mEnableGrallocUsageProtected) { + if (mFlags & kEnableGrallocUsageProtected) { usage |= GRALLOC_USAGE_PROTECTED; } @@ -2067,7 +2092,12 @@ void OMXCodec::on_message(const omx_message &msg) { } else if (mState != ERROR && mPortStatus[kPortIndexInput] != SHUTTING_DOWN) { CHECK_EQ((int)mPortStatus[kPortIndexInput], (int)ENABLED); - drainInputBuffer(&buffers->editItemAt(i)); + + if (mFlags & kUseSecureInputBuffers) { + drainAnyInputBuffer(); + } else { + drainInputBuffer(&buffers->editItemAt(i)); + } } break; } @@ -2804,32 +2834,81 @@ void OMXCodec::fillOutputBuffers() { void OMXCodec::drainInputBuffers() { CHECK(mState == EXECUTING || mState == RECONFIGURING); - Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput]; - for (size_t i = 0; i < buffers->size(); ++i) { - BufferInfo *info = &buffers->editItemAt(i); + if (mFlags & kUseSecureInputBuffers) { + Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput]; + for (size_t i = 0; i < buffers->size(); ++i) { + if (!drainAnyInputBuffer() + || (mFlags & kOnlySubmitOneInputBufferAtOneTime)) { + break; + } + } + } else { + Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput]; + for (size_t i = 0; i < buffers->size(); ++i) { + BufferInfo *info = &buffers->editItemAt(i); - if (info->mStatus != OWNED_BY_US) { - continue; + if (info->mStatus != OWNED_BY_US) { + continue; + } + + if (!drainInputBuffer(info)) { + break; + } + + if (mFlags & kOnlySubmitOneInputBufferAtOneTime) { + break; + } } + } +} - if (!drainInputBuffer(info)) { - break; +bool OMXCodec::drainAnyInputBuffer() { + return drainInputBuffer((BufferInfo *)NULL); +} + +OMXCodec::BufferInfo *OMXCodec::findInputBufferByDataPointer(void *ptr) { + Vector<BufferInfo> *infos = &mPortBuffers[kPortIndexInput]; + for (size_t i = 0; i < infos->size(); ++i) { + BufferInfo *info = &infos->editItemAt(i); + + if (info->mData == ptr) { + CODEC_LOGV( + "input buffer data ptr = %p, buffer_id = %p", + ptr, + info->mBuffer); + + return info; } + } - if (mOnlySubmitOneBufferAtOneTime) { - break; + TRESPASS(); +} + +OMXCodec::BufferInfo *OMXCodec::findEmptyInputBuffer() { + Vector<BufferInfo> *infos = &mPortBuffers[kPortIndexInput]; + for (size_t i = 0; i < infos->size(); ++i) { + BufferInfo *info = &infos->editItemAt(i); + + if (info->mStatus == OWNED_BY_US) { + return info; } } + + TRESPASS(); } bool OMXCodec::drainInputBuffer(BufferInfo *info) { - CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US); + if (info != NULL) { + CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US); + } if (mSignalledEOS) { return false; } if (mCodecSpecificDataIndex < mCodecSpecificData.size()) { + CHECK(!(mFlags & kUseSecureInputBuffers)); + const CodecSpecificData *specific = mCodecSpecificData[mCodecSpecificDataIndex]; @@ -2925,6 +3004,11 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { break; } + if (mFlags & kUseSecureInputBuffers) { + info = findInputBufferByDataPointer(srcBuffer->data()); + CHECK(info != NULL); + } + size_t remainingBytes = info->mSize - offset; if (srcBuffer->range_length() > remainingBytes) { @@ -2960,14 +3044,24 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { releaseBuffer = false; info->mMediaBuffer = srcBuffer; } else { - if (mIsMetaDataStoredInVideoBuffers) { + if (mFlags & kStoreMetaDataInVideoBuffers) { releaseBuffer = false; info->mMediaBuffer = srcBuffer; } - memcpy((uint8_t *)info->mData + offset, - (const uint8_t *)srcBuffer->data() - + srcBuffer->range_offset(), - srcBuffer->range_length()); + + if (mFlags & kUseSecureInputBuffers) { + // Data in "info" is already provided at this time. + + releaseBuffer = false; + + CHECK(info->mMediaBuffer == NULL); + info->mMediaBuffer = srcBuffer; + } else { + memcpy((uint8_t *)info->mData + offset, + (const uint8_t *)srcBuffer->data() + + srcBuffer->range_offset(), + srcBuffer->range_length()); + } } int64_t lastBufferTimeUs; @@ -3036,6 +3130,16 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { info->mBuffer, offset, timestampUs, timestampUs / 1E6); + if (info == NULL) { + CHECK(mFlags & kUseSecureInputBuffers); + CHECK(signalEOS); + + // This is fishy, there's still a MediaBuffer corresponding to this + // info available to the source at this point even though we're going + // to use it to signal EOS to the codec. + info = findEmptyInputBuffer(); + } + err = mOMX->emptyBuffer( mNode, info->mBuffer, 0, offset, flags, timestampUs); diff --git a/media/libstagefright/WVMExtractor.cpp b/media/libstagefright/WVMExtractor.cpp index 7072d58..26eda0c 100644 --- a/media/libstagefright/WVMExtractor.cpp +++ b/media/libstagefright/WVMExtractor.cpp @@ -33,25 +33,26 @@ #include <utils/Errors.h> +/* The extractor lifetime is short - just long enough to get + * the media sources constructed - so the shared lib needs to remain open + * beyond the lifetime of the extractor. So keep the handle as a global + * rather than a member of the extractor + */ +void *gVendorLibHandle = NULL; + namespace android { -Mutex WVMExtractor::sMutex; -uint32_t WVMExtractor::sActiveExtractors = 0; -void *WVMExtractor::sVendorLibHandle = NULL; +static Mutex gWVMutex; WVMExtractor::WVMExtractor(const sp<DataSource> &source) : mDataSource(source) { { - Mutex::Autolock autoLock(sMutex); - - if (sVendorLibHandle == NULL) { - CHECK(sActiveExtractors == 0); - sVendorLibHandle = dlopen("libwvm.so", RTLD_NOW); + Mutex::Autolock autoLock(gWVMutex); + if (gVendorLibHandle == NULL) { + gVendorLibHandle = dlopen("libwvm.so", RTLD_NOW); } - sActiveExtractors++; - - if (sVendorLibHandle == NULL) { + if (gVendorLibHandle == NULL) { LOGE("Failed to open libwvm.so"); return; } @@ -59,7 +60,7 @@ WVMExtractor::WVMExtractor(const sp<DataSource> &source) typedef WVMLoadableExtractor *(*GetInstanceFunc)(sp<DataSource>); GetInstanceFunc getInstanceFunc = - (GetInstanceFunc) dlsym(sVendorLibHandle, + (GetInstanceFunc) dlsym(gVendorLibHandle, "_ZN7android11GetInstanceENS_2spINS_10DataSourceEEE"); if (getInstanceFunc) { @@ -71,17 +72,6 @@ WVMExtractor::WVMExtractor(const sp<DataSource> &source) } WVMExtractor::~WVMExtractor() { - Mutex::Autolock autoLock(sMutex); - - CHECK(sActiveExtractors > 0); - sActiveExtractors--; - - // Close lib after last use - if (sActiveExtractors == 0) { - if (sVendorLibHandle != NULL) - dlclose(sVendorLibHandle); - sVendorLibHandle = NULL; - } } size_t WVMExtractor::countTracks() { diff --git a/media/libstagefright/chromium_http/support.cpp b/media/libstagefright/chromium_http/support.cpp index 967f126..f4b3668 100644 --- a/media/libstagefright/chromium_http/support.cpp +++ b/media/libstagefright/chromium_http/support.cpp @@ -115,31 +115,31 @@ SfRequestContext::SfRequestContext() { mUserAgent = ua.c_str(); - net_log_ = new SfNetLog; + set_net_log(new SfNetLog()); - host_resolver_ = + set_host_resolver( net::CreateSystemHostResolver( net::HostResolver::kDefaultParallelism, NULL /* resolver_proc */, - net_log_); + net_log())); - ssl_config_service_ = - net::SSLConfigService::CreateSystemSSLConfigService(); + set_ssl_config_service( + net::SSLConfigService::CreateSystemSSLConfigService()); - proxy_service_ = net::ProxyService::CreateWithoutProxyResolver( - new net::ProxyConfigServiceAndroid, net_log_); + set_proxy_service(net::ProxyService::CreateWithoutProxyResolver( + new net::ProxyConfigServiceAndroid, net_log())); - http_transaction_factory_ = new net::HttpCache( - host_resolver_, + set_http_transaction_factory(new net::HttpCache( + host_resolver(), new net::CertVerifier(), - dnsrr_resolver_, - dns_cert_checker_.get(), - proxy_service_.get(), - ssl_config_service_.get(), - net::HttpAuthHandlerFactory::CreateDefault(host_resolver_), - network_delegate_, - net_log_, - NULL); // backend_factory + dnsrr_resolver(), + dns_cert_checker(), + proxy_service(), + ssl_config_service(), + net::HttpAuthHandlerFactory::CreateDefault(host_resolver()), + network_delegate(), + net_log(), + NULL)); // backend_factory } const std::string &SfRequestContext::GetUserAgent(const GURL &url) const { diff --git a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp index e3292e6..0096760 100644 --- a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp +++ b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp @@ -475,7 +475,9 @@ status_t AVCEncoder::read( } status_t err = mSource->read(&mInputBuffer, options); if (err != OK) { - LOGE("Failed to read input video frame: %d", err); + if (err != ERROR_END_OF_STREAM) { + LOGE("Failed to read input video frame: %d", err); + } outputBuffer->release(); return err; } diff --git a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp index 15ed219..d7249c1 100644 --- a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp +++ b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp @@ -398,10 +398,13 @@ status_t M4vH263Encoder::read( } // Ready for accepting an input video frame - if (OK != mSource->read(&mInputBuffer, options)) { - LOGE("Failed to read from data source"); + status_t err = mSource->read(&mInputBuffer, options); + if (OK != err) { + if (err != ERROR_END_OF_STREAM) { + LOGE("Failed to read from data source"); + } outputBuffer->release(); - return UNKNOWN_ERROR; + return err; } if (mInputBuffer->size() - ((mVideoWidth * mVideoHeight * 3) >> 1) != 0) { diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp index 165683e..8ecc17c 100644 --- a/media/libstagefright/httplive/LiveSession.cpp +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -41,8 +41,10 @@ namespace android { const int64_t LiveSession::kMaxPlaylistAgeUs = 15000000ll; -LiveSession::LiveSession(uint32_t flags) +LiveSession::LiveSession(uint32_t flags, bool uidValid, uid_t uid) : mFlags(flags), + mUIDValid(uidValid), + mUID(uid), mDataSource(new LiveDataSource), mHTTPDataSource( HTTPBase::Create( @@ -58,6 +60,9 @@ LiveSession::LiveSession(uint32_t flags) mSeekDone(false), mDisconnectPending(false), mMonitorQueueGeneration(0) { + if (mUIDValid) { + mHTTPDataSource->setUID(mUID); + } } LiveSession::~LiveSession() { @@ -408,13 +413,20 @@ rinse_repeat: if (firstTime) { Mutex::Autolock autoLock(mLock); - int32_t targetDuration; - if (!mPlaylist->isComplete() - || !mPlaylist->meta()->findInt32( - "target-duration", &targetDuration)) { + if (!mPlaylist->isComplete()) { mDurationUs = -1; } else { - mDurationUs = 1000000ll * targetDuration * mPlaylist->size(); + mDurationUs = 0; + for (size_t i = 0; i < mPlaylist->size(); ++i) { + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt( + i, NULL /* uri */, &itemMeta)); + + int64_t itemDurationUs; + CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); + + mDurationUs += itemDurationUs; + } } } @@ -431,14 +443,26 @@ rinse_repeat: bool bandwidthChanged = false; if (mSeekTimeUs >= 0) { - int32_t targetDuration; - if (mPlaylist->isComplete() && - mPlaylist->meta()->findInt32( - "target-duration", &targetDuration)) { - int64_t seekTimeSecs = (mSeekTimeUs + 500000ll) / 1000000ll; - int64_t index = seekTimeSecs / targetDuration; - - if (index >= 0 && index < mPlaylist->size()) { + if (mPlaylist->isComplete()) { + size_t index = 0; + int64_t segmentStartUs = 0; + while (index < mPlaylist->size()) { + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt( + index, NULL /* uri */, &itemMeta)); + + int64_t itemDurationUs; + CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); + + if (mSeekTimeUs < segmentStartUs + itemDurationUs) { + break; + } + + segmentStartUs += itemDurationUs; + ++index; + } + + if (index < mPlaylist->size()) { int32_t newSeqNumber = firstSeqNumberInPlaylist + index; if (newSeqNumber != mSeqNumber) { @@ -652,6 +676,10 @@ status_t LiveSession::decryptBuffer( ? HTTPBase::kFlagIncognito : 0); + if (mUIDValid) { + keySource->setUID(mUID); + } + status_t err = keySource->connect(keyURI.c_str()); if (err == OK) { diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp index 765f795..123fbf8 100644 --- a/media/libstagefright/httplive/M3UParser.cpp +++ b/media/libstagefright/httplive/M3UParser.cpp @@ -64,14 +64,21 @@ size_t M3UParser::size() { } bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) { - uri->clear(); - if (meta) { *meta = NULL; } + if (uri) { + uri->clear(); + } + + if (meta) { + *meta = NULL; + } if (index >= mItems.size()) { return false; } - *uri = mItems.itemAt(index).mURI; + if (uri) { + *uri = mItems.itemAt(index).mURI; + } if (meta) { *meta = mItems.itemAt(index).mMeta; diff --git a/media/libstagefright/include/ARTSPController.h b/media/libstagefright/include/ARTSPController.h index ce7ffe5..2bd5be6 100644 --- a/media/libstagefright/include/ARTSPController.h +++ b/media/libstagefright/include/ARTSPController.h @@ -30,6 +30,8 @@ struct MyHandler; struct ARTSPController : public MediaExtractor { ARTSPController(const sp<ALooper> &looper); + void setUID(uid_t uid); + status_t connect(const char *url); void disconnect(); @@ -80,6 +82,9 @@ private: sp<MyHandler> mHandler; sp<AHandlerReflector<ARTSPController> > mReflector; + bool mUIDValid; + uid_t mUID; + void (*mSeekDoneCb)(void *); void *mSeekDoneCookie; int64_t mLastSeekCompletedTimeUs; diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index f6df380..e069b4d 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -62,6 +62,7 @@ struct AwesomePlayer { ~AwesomePlayer(); void setListener(const wp<MediaPlayerBase> &listener); + void setUID(uid_t uid); status_t setDataSource( const char *uri, @@ -150,6 +151,8 @@ private: TimedEventQueue mQueue; bool mQueueStarted; wp<MediaPlayerBase> mListener; + bool mUIDValid; + uid_t mUID; sp<Surface> mSurface; sp<ANativeWindow> mNativeWindow; diff --git a/media/libstagefright/include/HTTPBase.h b/media/libstagefright/include/HTTPBase.h index 3a7fbb6..2e25dd9 100644 --- a/media/libstagefright/include/HTTPBase.h +++ b/media/libstagefright/include/HTTPBase.h @@ -48,13 +48,15 @@ struct HTTPBase : public DataSource { virtual status_t setBandwidthStatCollectFreq(int32_t freqMs); + void setUID(uid_t uid); + bool getUID(uid_t *uid) const; + static sp<HTTPBase> Create(uint32_t flags = 0); protected: void addBandwidthMeasurement(size_t numBytes, int64_t delayUs); private: - struct BandwidthEntry { int64_t mDelayUs; size_t mNumBytes; @@ -76,6 +78,8 @@ private: int32_t mPrevEstimatedBandWidthKbps; int32_t mBandWidthCollectFreqMs; + bool mUIDValid; + uid_t mUID; DISALLOW_EVIL_CONSTRUCTORS(HTTPBase); }; diff --git a/media/libstagefright/include/HTTPStream.h b/media/libstagefright/include/HTTPStream.h index 09e6a5f..88ba9d6 100644 --- a/media/libstagefright/include/HTTPStream.h +++ b/media/libstagefright/include/HTTPStream.h @@ -32,6 +32,8 @@ public: HTTPStream(); ~HTTPStream(); + void setUID(uid_t uid); + status_t connect(const char *server, int port = -1, bool https = false); status_t disconnect(); @@ -58,6 +60,8 @@ public: // _excluding_ the termianting CRLF. status_t receive_line(char *line, size_t size); + static void RegisterSocketUser(int s, uid_t uid); + private: enum State { READY, @@ -67,6 +71,10 @@ private: State mState; Mutex mLock; + + bool mUIDValid; + uid_t mUID; + int mSocket; KeyedVector<AString, AString> mHeaders; diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h index 99abe64..188ef5e 100644 --- a/media/libstagefright/include/LiveSession.h +++ b/media/libstagefright/include/LiveSession.h @@ -35,7 +35,7 @@ struct LiveSession : public AHandler { // Don't log any URLs. kFlagIncognito = 1, }; - LiveSession(uint32_t flags = 0); + LiveSession(uint32_t flags = 0, bool uidValid = false, uid_t uid = 0); sp<DataSource> getDataSource(); @@ -77,6 +77,8 @@ private: }; uint32_t mFlags; + bool mUIDValid; + uid_t mUID; sp<LiveDataSource> mDataSource; diff --git a/media/libstagefright/include/WVMExtractor.h b/media/libstagefright/include/WVMExtractor.h index 0817bab..deecd25 100644 --- a/media/libstagefright/include/WVMExtractor.h +++ b/media/libstagefright/include/WVMExtractor.h @@ -18,7 +18,6 @@ #define WVM_EXTRACTOR_H_ -#include <media/stagefright/DataSource.h> #include <media/stagefright/MediaExtractor.h> #include <utils/Errors.h> @@ -68,10 +67,6 @@ private: WVMExtractor(const WVMExtractor &); WVMExtractor &operator=(const WVMExtractor &); - - static Mutex sMutex; - static uint32_t sActiveExtractors; - static void *sVendorLibHandle; }; } // namespace android diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp index c4e0cdc..072d6b2 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.cpp +++ b/media/libstagefright/rtsp/ARTSPConnection.cpp @@ -34,13 +34,17 @@ #include <openssl/md5.h> #include <sys/socket.h> +#include "HTTPStream.h" + namespace android { // static const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll; -ARTSPConnection::ARTSPConnection() - : mState(DISCONNECTED), +ARTSPConnection::ARTSPConnection(bool uidValid, uid_t uid) + : mUIDValid(uidValid), + mUID(uid), + mState(DISCONNECTED), mAuthType(NONE), mSocket(-1), mConnectionID(0), @@ -246,6 +250,10 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) { mSocket = socket(AF_INET, SOCK_STREAM, 0); + if (mUIDValid) { + HTTPStream::RegisterSocketUser(mSocket, mUID); + } + MakeSocketBlocking(mSocket, false); struct sockaddr_in remote; diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h index ac2e3ae..5cb84fd 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.h +++ b/media/libstagefright/rtsp/ARTSPConnection.h @@ -33,7 +33,7 @@ struct ARTSPResponse : public RefBase { }; struct ARTSPConnection : public AHandler { - ARTSPConnection(); + ARTSPConnection(bool uidValid = false, uid_t uid = 0); void connect(const char *url, const sp<AMessage> &reply); void disconnect(const sp<AMessage> &reply); @@ -74,6 +74,8 @@ private: static const int64_t kSelectTimeoutUs; + bool mUIDValid; + uid_t mUID; State mState; AString mUser, mPass; AuthType mAuthType; diff --git a/media/libstagefright/rtsp/ARTSPController.cpp b/media/libstagefright/rtsp/ARTSPController.cpp index 1328d2e..2ebae7e 100644 --- a/media/libstagefright/rtsp/ARTSPController.cpp +++ b/media/libstagefright/rtsp/ARTSPController.cpp @@ -28,6 +28,7 @@ namespace android { ARTSPController::ARTSPController(const sp<ALooper> &looper) : mState(DISCONNECTED), mLooper(looper), + mUIDValid(false), mSeekDoneCb(NULL), mSeekDoneCookie(NULL), mLastSeekCompletedTimeUs(-1) { @@ -40,6 +41,11 @@ ARTSPController::~ARTSPController() { mLooper->unregisterHandler(mReflector->id()); } +void ARTSPController::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + status_t ARTSPController::connect(const char *url) { Mutex::Autolock autoLock(mLock); @@ -49,7 +55,7 @@ status_t ARTSPController::connect(const char *url) { sp<AMessage> msg = new AMessage(kWhatConnectDone, mReflector->id()); - mHandler = new MyHandler(url, mLooper); + mHandler = new MyHandler(url, mLooper, mUIDValid, mUID); mState = CONNECTING; diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp index fd0505e..f03f7a2 100644 --- a/media/libstagefright/rtsp/ASessionDescription.cpp +++ b/media/libstagefright/rtsp/ASessionDescription.cpp @@ -301,9 +301,6 @@ void ASessionDescription::ParseFormatDesc( // static bool ASessionDescription::parseNTPRange( const char *s, float *npt1, float *npt2) { - *npt1 = 0.0f; - *npt2 = 0.0f; - if (s[0] == '-') { return false; // no start time available. } diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h index f89f8e2..3188959 100644 --- a/media/libstagefright/rtsp/MyHandler.h +++ b/media/libstagefright/rtsp/MyHandler.h @@ -40,6 +40,8 @@ #include <sys/socket.h> #include <netdb.h> +#include "HTTPStream.h" + // If no access units are received within 5 secs, assume that the rtp // stream has ended and signal end of stream. static int64_t kAccessUnitTimeoutUs = 5000000ll; @@ -92,10 +94,14 @@ static bool GetAttribute(const char *s, const char *key, AString *value) { } struct MyHandler : public AHandler { - MyHandler(const char *url, const sp<ALooper> &looper) - : mLooper(looper), + MyHandler( + const char *url, const sp<ALooper> &looper, + bool uidValid = false, uid_t uid = 0) + : mUIDValid(uidValid), + mUID(uid), + mLooper(looper), mNetLooper(new ALooper), - mConn(new ARTSPConnection), + mConn(new ARTSPConnection(mUIDValid, mUID)), mRTPConn(new ARTPConnection), mOriginalSessionURL(url), mSessionURL(url), @@ -995,12 +1001,10 @@ struct MyHandler : public AHandler { AString val; CHECK(GetAttribute(range.c_str(), "npt", &val)); - bool seekable = true; - float npt1, npt2; if (!ASessionDescription::parseNTPRange(val.c_str(), &npt1, &npt2)) { // This is a live stream and therefore not seekable. - seekable = false; + return; } i = response->mHeaders.indexOfKey("rtp-info"); @@ -1046,7 +1050,7 @@ struct MyHandler : public AHandler { ++n; } - mSeekable = seekable; + mSeekable = true; } sp<APacketSource> getPacketSource(size_t index) { @@ -1080,6 +1084,8 @@ private: List<sp<ABuffer> > mPackets; }; + bool mUIDValid; + uid_t mUID; sp<ALooper> mLooper; sp<ALooper> mNetLooper; sp<ARTSPConnection> mConn; @@ -1174,6 +1180,11 @@ private: ARTPConnection::MakePortPair( &info->mRTPSocket, &info->mRTCPSocket, &rtpPort); + if (mUIDValid) { + HTTPStream::RegisterSocketUser(info->mRTPSocket, mUID); + HTTPStream::RegisterSocketUser(info->mRTCPSocket, mUID); + } + request.append("Transport: RTP/AVP/UDP;unicast;client_port="); request.append(rtpPort); request.append("-"); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6d8eab6..f42cbbf 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -25,6 +25,11 @@ android:exported="true" /> + <!-- started from PhoneWindowManager + TODO: Should have an android:permission attribute --> + <service android:name=".screenshot.TakeScreenshotService" + android:exported="false" /> + <activity android:name=".usb.UsbPreferenceActivity" android:theme="@*android:style/Theme.Holo.Dialog.Alert" android:excludeFromRecents="true"> diff --git a/packages/SystemUI/res/drawable-hdpi/global_screenshot_background.9.png b/packages/SystemUI/res/drawable-hdpi/global_screenshot_background.9.png Binary files differnew file mode 100644 index 0000000..e14111d --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/global_screenshot_background.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png b/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png Binary files differnew file mode 100644 index 0000000..e14111d --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml new file mode 100644 index 0000000..6cb8799 --- /dev/null +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView android:id="@+id/global_screenshot_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#FF000000" + android:visibility="gone" /> + <FrameLayout + android:id="@+id/global_screenshot_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/global_screenshot_background" + android:visibility="gone"> + <ImageView android:id="@+id/global_screenshot" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:adjustViewBounds="true" /> + </FrameLayout> + <ImageView android:id="@+id/global_screenshot_flash" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#FFFFFFFF" + android:visibility="gone" /> +</FrameLayout> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 7a4ac5d..5298f2e 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -36,10 +36,6 @@ <!-- Whether or not we show the number in the bar. --> <bool name="config_statusBarShowNumber">true</bool> - <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be - autodetected from the Configuration. --> - <bool name="config_showNavigationBar">false</bool> - <!-- How many icons may be shown at once in the system bar. Includes any slots that may be reused for things like IME control. --> <integer name="config_maxNotificationIcons">5</integer> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 86e0cd0..70f9b75 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -165,4 +165,9 @@ <string name="use_ptp_button_title">Mount as a camera (PTP)</string> <!-- Label for the installer CD image option in UsbPreferenceActivity. [CHAR LIMIT=50] --> <string name="installer_cd_button_title">Install Android File Transfer application for Mac</string> + + <!-- toast message displayed when a screenshot is saved to the Gallery. --> + <string name="screenshot_saving_toast">Screenshot saved to Gallery</string> + <!-- toast message displayed when we fail to take a screenshot. --> + <string name="screenshot_failed_toast">Could not save screenshot</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index b8dc63d..408436a 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -496,7 +496,7 @@ public class RecentsPanelView extends RelativeLayout // the task. final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); - am.removeTask(ad.taskId, 0); + am.removeTask(ad.taskId, ActivityManager.REMOVE_TASK_KILL_PROCESS); } public void handleLongPress(View selectedView) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java new file mode 100644 index 0000000..83a5578 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PixelFormat; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Environment; +import android.os.ServiceManager; +import android.provider.MediaStore; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.IWindowManager; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.systemui.R; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.Thread; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * POD used in the AsyncTask which saves an image in the background. + */ +class SaveImageInBackgroundData { + Context context; + Bitmap image; + int result; +} + +/** + * An AsyncTask that saves an image to the media store in the background. + */ +class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void, + SaveImageInBackgroundData> { + private static final String TAG = "SaveImageInBackgroundTask"; + private static final String SCREENSHOTS_DIR_NAME = "Screenshots"; + private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/Screenshot_%s-%d.png"; + + @Override + protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) { + if (params.length != 1) return null; + + Context context = params[0].context; + Bitmap image = params[0].image; + + try{ + long currentTime = System.currentTimeMillis(); + String date = new SimpleDateFormat("MM-dd-yy-kk-mm-ss").format(new Date(currentTime)); + String imageDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES).getAbsolutePath(); + String imageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, + imageDir, SCREENSHOTS_DIR_NAME, + date, currentTime % 1000); + + // Save the screenshot to the MediaStore + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.ImageColumns.DATA, imageFilePath); + values.put(MediaStore.Images.ImageColumns.TITLE, "Screenshot"); + values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, "Screenshot"); + values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, currentTime); + values.put(MediaStore.Images.ImageColumns.DATE_ADDED, currentTime); + values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, currentTime); + values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png"); + Uri uri = context.getContentResolver().insert( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + + OutputStream out = context.getContentResolver().openOutputStream(uri); + image.compress(Bitmap.CompressFormat.PNG, 100, out); + out.flush(); + out.close(); + + params[0].result = 0; + }catch(IOException e){ + params[0].result = 1; + } + + return params[0]; + }; + + @Override + protected void onPostExecute(SaveImageInBackgroundData params) { + if (params.result > 0) { + // Show a message that we've failed to save the image to disk + Toast.makeText(params.context, R.string.screenshot_failed_toast, + Toast.LENGTH_SHORT).show(); + } else { + // Show a message that we've saved the screenshot to disk + Toast.makeText(params.context, R.string.screenshot_saving_toast, + Toast.LENGTH_SHORT).show(); + } + }; +} + +/** + * TODO: + * - Performance when over gl surfaces? Ie. Gallery + * - what do we say in the Toast? Which icon do we get if the user uses another + * type of gallery? + */ +class GlobalScreenshot { + private static final String TAG = "GlobalScreenshot"; + private static final int SCREENSHOT_FADE_IN_DURATION = 900; + private static final int SCREENSHOT_FADE_OUT_DELAY = 1000; + private static final int SCREENSHOT_FADE_OUT_DURATION = 450; + private static final int TOAST_FADE_IN_DURATION = 500; + private static final int TOAST_FADE_OUT_DELAY = 1000; + private static final int TOAST_FADE_OUT_DURATION = 500; + private static final float BACKGROUND_ALPHA = 0.65f; + private static final float SCREENSHOT_SCALE = 0.85f; + private static final float SCREENSHOT_MIN_SCALE = 0.7f; + private static final float SCREENSHOT_ROTATION = -6.75f; // -12.5f; + + private Context mContext; + private LayoutInflater mLayoutInflater; + private IWindowManager mIWindowManager; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mWindowLayoutParams; + private Display mDisplay; + private DisplayMetrics mDisplayMetrics; + private Matrix mDisplayMatrix; + + private Bitmap mScreenBitmap; + private View mScreenshotLayout; + private ImageView mBackgroundView; + private FrameLayout mScreenshotContainerView; + private ImageView mScreenshotView; + + private AnimatorSet mScreenshotAnimation; + + // General use cubic interpolator + final TimeInterpolator mCubicInterpolator = new TimeInterpolator() { + public float getInterpolation(float t) { + return t*t*t; + } + }; + // The interpolator used to control the background alpha at the start of the animation + final TimeInterpolator mBackgroundViewAlphaInterpolator = new TimeInterpolator() { + public float getInterpolation(float t) { + float tStep = 0.35f; + if (t < tStep) { + return t * (1f / tStep); + } else { + return 1f; + } + } + }; + + /** + * @param context everything needs a context :( + */ + public GlobalScreenshot(Context context) { + mContext = context; + mLayoutInflater = (LayoutInflater) + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + // Inflate the screenshot layout + mDisplayMetrics = new DisplayMetrics(); + mDisplayMatrix = new Matrix(); + mScreenshotLayout = mLayoutInflater.inflate(R.layout.global_screenshot, null); + mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background); + mScreenshotContainerView = (FrameLayout) mScreenshotLayout.findViewById(R.id.global_screenshot_container); + mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot); + mScreenshotLayout.setFocusable(true); + mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + // Intercept and ignore all touch events + return true; + } + }); + + // Setup the window that we are going to use + mIWindowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService(Context.WINDOW_SERVICE)); + mWindowLayoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, + WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED_SYSTEM + | WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, + PixelFormat.TRANSLUCENT); + mWindowLayoutParams.token = new Binder(); + mWindowLayoutParams.setTitle("ScreenshotAnimation"); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mDisplay = mWindowManager.getDefaultDisplay(); + } + + /** + * Creates a new worker thread and saves the screenshot to the media store. + */ + private void saveScreenshotInWorkerThread() { + SaveImageInBackgroundData data = new SaveImageInBackgroundData(); + data.context = mContext; + data.image = mScreenBitmap; + new SaveImageInBackgroundTask().execute(data); + } + + /** + * @return the current display rotation in degrees + */ + private float getDegreesForRotation(int value) { + switch (value) { + case Surface.ROTATION_90: + return 90f; + case Surface.ROTATION_180: + return 180f; + case Surface.ROTATION_270: + return 270f; + } + return 0f; + } + + /** + * Takes a screenshot of the current display and shows an animation. + */ + void takeScreenshot() { + // We need to orient the screenshot correctly (and the Surface api seems to take screenshots + // only in the natural orientation of the device :!) + mDisplay.getRealMetrics(mDisplayMetrics); + float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; + float degrees = getDegreesForRotation(mDisplay.getRotation()); + boolean requiresRotation = (degrees > 0); + if (requiresRotation) { + // Get the dimensions of the device in its native orientation + mDisplayMatrix.reset(); + mDisplayMatrix.preRotate(-degrees); + mDisplayMatrix.mapPoints(dims); + dims[0] = Math.abs(dims[0]); + dims[1] = Math.abs(dims[1]); + } + mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]); + if (requiresRotation) { + // Rotate the screenshot to the current orientation + Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(ss); + c.translate(ss.getWidth() / 2, ss.getHeight() / 2); + c.rotate(360f - degrees); + c.translate(-dims[0] / 2, -dims[1] / 2); + c.drawBitmap(mScreenBitmap, 0, 0, null); + mScreenBitmap = ss; + } + + // If we couldn't take the screenshot, notify the user + if (mScreenBitmap == null) { + Toast.makeText(mContext, R.string.screenshot_failed_toast, + Toast.LENGTH_SHORT).show(); + return; + } + + // Start the post-screenshot animation + startAnimation(); + } + + + /** + * Starts the animation after taking the screenshot + */ + private void startAnimation() { + // Add the view for the animation + mScreenshotView.setImageBitmap(mScreenBitmap); + mScreenshotLayout.requestFocus(); + + // Setup the animation with the screenshot just taken + if (mScreenshotAnimation != null) { + mScreenshotAnimation.end(); + } + + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + ValueAnimator screenshotFadeInAnim = createScreenshotFadeInAnimation(); + ValueAnimator screenshotFadeOutAnim = createScreenshotFadeOutAnimation(); + mScreenshotAnimation = new AnimatorSet(); + mScreenshotAnimation.play(screenshotFadeInAnim).before(screenshotFadeOutAnim); + mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Save the screenshot once we have a bit of time now + saveScreenshotInWorkerThread(); + + mWindowManager.removeView(mScreenshotLayout); + } + }); + mScreenshotAnimation.start(); + } + private ValueAnimator createScreenshotFadeInAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setInterpolator(mCubicInterpolator); + anim.setDuration(SCREENSHOT_FADE_IN_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mBackgroundView.setVisibility(View.VISIBLE); + mScreenshotContainerView.setVisibility(View.VISIBLE); + } + }); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = ((Float) animation.getAnimatedValue()).floatValue(); + mBackgroundView.setAlpha(mBackgroundViewAlphaInterpolator.getInterpolation(t) * + BACKGROUND_ALPHA); + float scaleT = SCREENSHOT_SCALE + (1f - t) * SCREENSHOT_SCALE; + mScreenshotContainerView.setAlpha(t*t*t*t); + mScreenshotContainerView.setScaleX(scaleT); + mScreenshotContainerView.setScaleY(scaleT); + mScreenshotContainerView.setRotation(t * SCREENSHOT_ROTATION); + } + }); + return anim; + } + private ValueAnimator createScreenshotFadeOutAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f); + anim.setInterpolator(mCubicInterpolator); + anim.setStartDelay(SCREENSHOT_FADE_OUT_DELAY); + anim.setDuration(SCREENSHOT_FADE_OUT_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBackgroundView.setVisibility(View.GONE); + mScreenshotContainerView.setVisibility(View.GONE); + } + }); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = ((Float) animation.getAnimatedValue()).floatValue(); + float scaleT = SCREENSHOT_MIN_SCALE + + t*(SCREENSHOT_SCALE - SCREENSHOT_MIN_SCALE); + mScreenshotContainerView.setAlpha(t); + mScreenshotContainerView.setScaleX(scaleT); + mScreenshotContainerView.setScaleY(scaleT); + mBackgroundView.setAlpha(t * t * BACKGROUND_ALPHA); + } + }); + return anim; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java new file mode 100644 index 0000000..35eaedf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import android.app.Service; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +import com.android.systemui.R; + +public class TakeScreenshotService extends Service { + private static final String TAG = "TakeScreenshotService"; + + private static GlobalScreenshot mScreenshot; + + @Override + public IBinder onBind(Intent intent) { + if (mScreenshot == null) { + mScreenshot = new GlobalScreenshot(this); + } + mScreenshot.takeScreenshot(); + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index d8474db..4c7b0dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -246,7 +246,7 @@ public class PhoneStatusBar extends StatusBar { mIntruderAlertView.setClickable(true); try { - boolean showNav = res.getBoolean(R.bool.config_showNavigationBar); + boolean showNav = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); if (showNav) { mNavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); diff --git a/packages/VpnDialogs/Android.mk b/packages/VpnDialogs/Android.mk index 89f010a..ac84125 100644 --- a/packages/VpnDialogs/Android.mk +++ b/packages/VpnDialogs/Android.mk @@ -20,6 +20,8 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional +LOCAL_CERTIFICATE := platform + LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := VpnDialogs diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml index 4e6784c..c0b0a08 100644 --- a/packages/VpnDialogs/AndroidManifest.xml +++ b/packages/VpnDialogs/AndroidManifest.xml @@ -1,5 +1,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.vpndialogs"> + package="com.android.vpndialogs" + android:sharedUserId="android.uid.system"> <application android:label="VpnDialogs"> <activity android:name=".ConfirmDialog" diff --git a/packages/VpnDialogs/res/layout/confirm.xml b/packages/VpnDialogs/res/layout/confirm.xml index 249b6e6..5ab6ee2 100644 --- a/packages/VpnDialogs/res/layout/confirm.xml +++ b/packages/VpnDialogs/res/layout/confirm.xml @@ -17,7 +17,8 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:padding="5mm"> <ImageView android:id="@+id/icon" android:layout_width="@android:dimen/app_icon_size" @@ -32,7 +33,7 @@ android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:layout_below="@id/icon" - android:padding="2mm" + android:padding="3mm" android:text="@string/warning" android:textSize="18sp"/> @@ -53,9 +54,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/warning" android:text="@string/accept" - - - android:textSize="18sp" + android:textSize="20sp" android:checked="false"/> </RelativeLayout> diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index 8186e26..df6d36b 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -29,6 +29,7 @@ <string name="accept">I trust this application.</string> + <string name="legacy_title">VPN is connected</string> <string name="configure">Configure</string> <string name="disconnect">Disconnect</string> diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index c54e719..c7b4a5f 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -36,7 +36,7 @@ public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedC DialogInterface.OnClickListener, DialogInterface.OnDismissListener { private static final String TAG = "VpnConfirm"; - private String mPackageName; + private String mPackage; private IConnectivityManager mService; @@ -47,19 +47,19 @@ public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedC protected void onResume() { super.onResume(); try { - mPackageName = getCallingPackage(); + mPackage = getCallingPackage(); mService = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); - if (mPackageName.equals(mService.prepareVpn(null))) { + if (mService.prepareVpn(mPackage, null)) { setResult(RESULT_OK); finish(); return; } PackageManager pm = getPackageManager(); - ApplicationInfo app = pm.getApplicationInfo(mPackageName, 0); + ApplicationInfo app = pm.getApplicationInfo(mPackage, 0); View view = View.inflate(this, R.layout.confirm, null); ((ImageView) view.findViewById(R.id.icon)).setImageDrawable(app.loadIcon(pm)); @@ -103,8 +103,7 @@ public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedC @Override public void onClick(DialogInterface dialog, int which) { try { - if (which == AlertDialog.BUTTON_POSITIVE && - mPackageName.equals(mService.prepareVpn(mPackageName))) { + if (which == AlertDialog.BUTTON_POSITIVE && mService.prepareVpn(null, mPackage)) { setResult(RESULT_OK); } } catch (Exception e) { diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java index ba3f344..21e916b 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java @@ -64,26 +64,36 @@ public class ManageDialog extends Activity implements Handler.Callback, mService = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); - PackageManager pm = getPackageManager(); - ApplicationInfo app = pm.getApplicationInfo(mConfig.packageName, 0); - View view = View.inflate(this, R.layout.manage, null); - if (mConfig.sessionName != null) { - ((TextView) view.findViewById(R.id.session)).setText(mConfig.sessionName); + if (mConfig.session != null) { + ((TextView) view.findViewById(R.id.session)).setText(mConfig.session); } mDuration = (TextView) view.findViewById(R.id.duration); mDataTransmitted = (TextView) view.findViewById(R.id.data_transmitted); mDataReceived = (TextView) view.findViewById(R.id.data_received); - mDialog = new AlertDialog.Builder(this) - .setIcon(app.loadIcon(pm)) - .setTitle(app.loadLabel(pm)) - .setView(view) - .setNeutralButton(R.string.disconnect, this) - .setNegativeButton(android.R.string.cancel, this) - .create(); + if (mConfig.packagz.equals(VpnConfig.LEGACY_VPN)) { + mDialog = new AlertDialog.Builder(this) + .setIcon(android.R.drawable.ic_dialog_info) + .setTitle(R.string.legacy_title) + .setView(view) + .setNeutralButton(R.string.disconnect, this) + .setNegativeButton(android.R.string.cancel, this) + .create(); + } else { + PackageManager pm = getPackageManager(); + ApplicationInfo app = pm.getApplicationInfo(mConfig.packagz, 0); + + mDialog = new AlertDialog.Builder(this) + .setIcon(app.loadIcon(pm)) + .setTitle(app.loadLabel(pm)) + .setView(view) + .setNeutralButton(R.string.disconnect, this) + .setNegativeButton(android.R.string.cancel, this) + .create(); + } - if (mConfig.configureActivity != null) { + if (mConfig.configureIntent != null) { mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getText(R.string.configure), this); } @@ -113,11 +123,9 @@ public class ManageDialog extends Activity implements Handler.Callback, public void onClick(DialogInterface dialog, int which) { try { if (which == AlertDialog.BUTTON_POSITIVE) { - Intent intent = new Intent(); - intent.setClassName(mConfig.packageName, mConfig.configureActivity); - startActivity(intent); + mConfig.configureIntent.send(); } else if (which == AlertDialog.BUTTON_NEUTRAL) { - mService.prepareVpn(""); + mService.prepareVpn(mConfig.packagz, VpnConfig.LEGACY_VPN); } } catch (Exception e) { Log.e(TAG, "onClick", e); @@ -161,7 +169,7 @@ public class ManageDialog extends Activity implements Handler.Callback, try { // See dev_seq_printf_stats() in net/core/dev.c. in = new DataInputStream(new FileInputStream("/proc/net/dev")); - String prefix = mConfig.interfaceName + ':'; + String prefix = mConfig.interfaze + ':'; while (true) { String line = in.readLine().trim(); diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java index 8b7a61e..1c4084c 100644 --- a/policy/src/com/android/internal/policy/impl/LockScreen.java +++ b/policy/src/com/android/internal/policy/impl/LockScreen.java @@ -64,7 +64,6 @@ class LockScreen extends LinearLayout implements KeyguardScreen, private KeyguardUpdateMonitor mUpdateMonitor; private KeyguardScreenCallback mCallback; - private SlidingTab mSlidingTab; private TextView mScreenLocked; private TextView mEmergencyCallText; private Button mEmergencyCallButton; @@ -89,11 +88,9 @@ class LockScreen extends LinearLayout implements KeyguardScreen, private boolean mEnableMenuKeyInLockScreen; private StatusView mStatusView; - private WaveView mEnergyWave; - private SlidingTabMethods mSlidingTabMethods; - private WaveViewMethods mWaveViewMethods; - private MultiWaveView mMultiWaveView; - private MultiWaveViewMethods mMultiWaveViewMethods; + private UnlockWidgetCommonMethods mUnlockWidgetMethods; + private View mUnlockWidget; + /** * The status of this lock screen. @@ -151,9 +148,28 @@ class LockScreen extends LinearLayout implements KeyguardScreen, } } - class SlidingTabMethods implements SlidingTab.OnTriggerListener { + private interface UnlockWidgetCommonMethods { + // Update resources based on phone state + public void updateResources(); + + // Get the view associated with this widget + public View getView(); - private void updateRightTabResources() { + // Reset the view + public void reset(boolean animate); + + // Animate the widget if it supports ping() + public void ping(); + } + + class SlidingTabMethods implements SlidingTab.OnTriggerListener, UnlockWidgetCommonMethods { + private final SlidingTab mSlidingTab; + + SlidingTabMethods(SlidingTab slidingTab) { + mSlidingTab = slidingTab; + } + + public void updateResources() { boolean vibe = mSilentMode && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE); @@ -175,7 +191,6 @@ class LockScreen extends LinearLayout implements KeyguardScreen, mCallback.goToUnlockScreen(); } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) { toggleRingMode(); - updateRightTabResources(); doSilenceRingToast(); mCallback.pokeWakelock(); } @@ -195,12 +210,29 @@ class LockScreen extends LinearLayout implements KeyguardScreen, mCallback.pokeWakelock(); } } + + public View getView() { + return mSlidingTab; + } + + public void reset(boolean animate) { + mSlidingTab.reset(animate); + } + + public void ping() { + } } private static final int WAIT_FOR_ANIMATION_TIMEOUT = 0; private static final int STAY_ON_WHILE_GRABBED_TIMEOUT = 30000; - class WaveViewMethods implements WaveView.OnTriggerListener { + class WaveViewMethods implements WaveView.OnTriggerListener, UnlockWidgetCommonMethods { + + private final WaveView mWaveView; + + WaveViewMethods(WaveView waveView) { + mWaveView = waveView; + } /** {@inheritDoc} */ public void onTrigger(View v, int whichHandle) { if (whichHandle == WaveView.OnTriggerListener.CENTER_HANDLE) { @@ -210,8 +242,6 @@ class LockScreen extends LinearLayout implements KeyguardScreen, /** {@inheritDoc} */ public void onGrabbedStateChange(View v, int grabbedState) { - if (DBG) Log.v(TAG, "*** LockScreen accel is " - + (mEnergyWave.isHardwareAccelerated() ? "on":"off")); // Don't poke the wake lock when returning to a state where the handle is // not grabbed since that can happen when the system (instead of the user) // cancels the grab. @@ -219,30 +249,51 @@ class LockScreen extends LinearLayout implements KeyguardScreen, mCallback.pokeWakelock(STAY_ON_WHILE_GRABBED_TIMEOUT); } } + + public void updateResources() { + } + + public View getView() { + return mWaveView; + } + public void reset(boolean animate) { + mWaveView.reset(); + } + public void ping() { + } } - class MultiWaveViewMethods implements MultiWaveView.OnTriggerListener { + class MultiWaveViewMethods implements MultiWaveView.OnTriggerListener, + UnlockWidgetCommonMethods { + + private final MultiWaveView mMultiWaveView; + + MultiWaveViewMethods(MultiWaveView multiWaveView) { + mMultiWaveView = multiWaveView; + } + + public void updateResources() { + mMultiWaveView.setTargetResources(mSilentMode ? R.array.lockscreen_targets_when_silent + : R.array.lockscreen_targets_when_soundon); + } + public void onGrabbed(View v, int handle) { } + public void onReleased(View v, int handle) { } + public void onTrigger(View v, int target) { if (target == 0) { // TODO: Use resources to determine which handle was used mCallback.goToUnlockScreen(); } else if (target == 2) { toggleRingMode(); - updateResources(); doSilenceRingToast(); + mUnlockWidgetMethods.updateResources(); mCallback.pokeWakelock(); } - - } - - private void updateResources() { - mMultiWaveView.setTargetResources(mSilentMode ? R.array.lockscreen_targets_when_silent - : R.array.lockscreen_targets_when_soundon); } public void onGrabbedStateChange(View v, int handle) { @@ -253,6 +304,18 @@ class LockScreen extends LinearLayout implements KeyguardScreen, mCallback.pokeWakelock(); } } + + public View getView() { + return mMultiWaveView; + } + + public void reset(boolean animate) { + mMultiWaveView.reset(animate); + } + + public void ping() { + mMultiWaveView.ping(); + } } private void requestUnlockScreen() { @@ -371,32 +434,39 @@ class LockScreen extends LinearLayout implements KeyguardScreen, mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); mSilentMode = isSilentMode(); - View unlockWidget = findViewById(R.id.unlock_widget); - if (unlockWidget instanceof SlidingTab) { - mSlidingTab = (SlidingTab) unlockWidget; - mSlidingTab.setHoldAfterTrigger(true, false); - mSlidingTab.setLeftHintText(R.string.lockscreen_unlock_label); - mSlidingTab.setLeftTabResources( + mUnlockWidget = findViewById(R.id.unlock_widget); + if (mUnlockWidget instanceof SlidingTab) { + SlidingTab slidingTabView = (SlidingTab) mUnlockWidget; + slidingTabView.setHoldAfterTrigger(true, false); + slidingTabView.setLeftHintText(R.string.lockscreen_unlock_label); + slidingTabView.setLeftTabResources( R.drawable.ic_jog_dial_unlock, R.drawable.jog_tab_target_green, R.drawable.jog_tab_bar_left_unlock, R.drawable.jog_tab_left_unlock); - mSlidingTabMethods = new SlidingTabMethods(); - mSlidingTab.setOnTriggerListener(mSlidingTabMethods); - mSlidingTabMethods.updateRightTabResources(); - } else if (unlockWidget instanceof WaveView) { - mEnergyWave = (WaveView) unlockWidget; - mWaveViewMethods = new WaveViewMethods(); - mEnergyWave.setOnTriggerListener(mWaveViewMethods); - } else if (unlockWidget instanceof MultiWaveView) { - mMultiWaveView = (MultiWaveView) unlockWidget; - mMultiWaveViewMethods = new MultiWaveViewMethods(); - mMultiWaveViewMethods.updateResources(); // update silence/ring resources - mMultiWaveView.setOnTriggerListener(mMultiWaveViewMethods); + SlidingTabMethods slidingTabMethods = new SlidingTabMethods(slidingTabView); + slidingTabView.setOnTriggerListener(slidingTabMethods); + mUnlockWidgetMethods = slidingTabMethods; + } else if (mUnlockWidget instanceof WaveView) { + WaveView waveView = (WaveView) mUnlockWidget; + WaveViewMethods waveViewMethods = new WaveViewMethods(waveView); + waveView.setOnTriggerListener(waveViewMethods); + mUnlockWidgetMethods = waveViewMethods; + } else if (mUnlockWidget instanceof MultiWaveView) { + MultiWaveView multiWaveView = (MultiWaveView) mUnlockWidget; + MultiWaveViewMethods multiWaveViewMethods = new MultiWaveViewMethods(multiWaveView); + multiWaveView.setOnTriggerListener(multiWaveViewMethods); + mUnlockWidgetMethods = multiWaveViewMethods; } else { - throw new IllegalStateException("Unrecognized unlock widget: " + unlockWidget); + throw new IllegalStateException("Unrecognized unlock widget: " + mUnlockWidget); } + // Update widget with initial ring state + mUnlockWidgetMethods.updateResources(); + + if (DBG) Log.v(TAG, "*** LockScreen accel is " + + (mUnlockWidget.isHardwareAccelerated() ? "on":"off")); + resetStatusInfo(updateMonitor); } @@ -540,16 +610,14 @@ class LockScreen extends LinearLayout implements KeyguardScreen, * Enables unlocking of this screen. Typically just shows the unlock widget. */ private void enableUnlock() { - if (mEnergyWave != null) mEnergyWave.setVisibility(View.VISIBLE); - if (mSlidingTab != null) mSlidingTab.setVisibility(View.VISIBLE); + mUnlockWidgetMethods.getView().setVisibility(View.VISIBLE); } /** * Disable unlocking of this screen. Typically just hides the unlock widget. */ private void disableUnlock() { - if (mEnergyWave != null) mEnergyWave.setVisibility(View.GONE); - if (mSlidingTab != null) mSlidingTab.setVisibility(View.GONE); + mUnlockWidgetMethods.getView().setVisibility(View.GONE); } /** @@ -728,20 +796,13 @@ class LockScreen extends LinearLayout implements KeyguardScreen, /** {@inheritDoc} */ public void onPause() { - if (mEnergyWave != null) { - mEnergyWave.reset(); - } - if (mMultiWaveView != null) { - mMultiWaveView.reset(false); - } + mUnlockWidgetMethods.reset(false); } /** {@inheritDoc} */ public void onResume() { resetStatusInfo(mUpdateMonitor); - if (mMultiWaveView != null) { - mMultiWaveView.ping(); - } + mUnlockWidgetMethods.ping(); } /** {@inheritDoc} */ @@ -757,7 +818,7 @@ class LockScreen extends LinearLayout implements KeyguardScreen, boolean silent = AudioManager.RINGER_MODE_NORMAL != state; if (silent != mSilentMode) { mSilentMode = silent; - if (mSlidingTabMethods != null) mSlidingTabMethods.updateRightTabResources(); + mUnlockWidgetMethods.updateResources(); } } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index b52e7e1..ad6cebb 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -28,6 +28,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; @@ -372,6 +373,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // What we do when the user long presses on home private int mLongPressOnHomeBehavior = -1; + // Screenshot trigger states + private boolean mVolumeDownTriggered; + private boolean mPowerDownTriggered; + ShortcutManager mShortcutManager; PowerManager.WakeLock mBroadcastWakeLock; @@ -2339,6 +2344,26 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private void takeScreenshot() { + mHandler.post(new Runnable() { + @Override + public void run() { + ComponentName cn = new ComponentName("com.android.systemui", + "com.android.systemui.screenshot.TakeScreenshotService"); + Intent intent = new Intent(); + intent.setComponent(cn); + ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) {} + @Override + public void onServiceDisconnected(ComponentName name) {} + }; + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + mContext.unbindService(conn); + } + }); + } + /** {@inheritDoc} */ @Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { @@ -2398,6 +2423,24 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_DOWN: + if (down) { + // If the power key down was already triggered, take the screenshot + if (mPowerDownTriggered) { + // Dismiss the power-key longpress + mHandler.removeCallbacks(mPowerLongPress); + mPowerKeyHandled = true; + + // Take the screenshot + takeScreenshot(); + + // Prevent the event from being passed through to the current activity + result &= ~ACTION_PASS_TO_USER; + break; + } + mVolumeDownTriggered = true; + } else { + mVolumeDownTriggered = false; + } case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_MUTE: { if (down) { @@ -2478,6 +2521,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_POWER: { result &= ~ACTION_PASS_TO_USER; if (down) { + // If the volume down key has been triggered, then just take the screenshot + if (mVolumeDownTriggered) { + // Take the screenshot + takeScreenshot(); + mPowerKeyHandled = true; + + // Prevent the event from being passed through to the current activity + break; + } + mPowerDownTriggered = true; + + ITelephony telephonyService = getTelephonyService(); boolean hungUp = false; if (telephonyService != null) { @@ -2499,6 +2554,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } interceptPowerKeyDown(!isScreenOn || hungUp); } else { + mPowerDownTriggered = false; if (interceptPowerKeyUp(canceled)) { result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP; } diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp index 95b8a57..ca2540b 100644 --- a/services/input/EventHub.cpp +++ b/services/input/EventHub.cpp @@ -212,8 +212,8 @@ status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, struct input_absinfo info; if(ioctl(device->fd, EVIOCGABS(axis), &info)) { - LOGW("Error reading absolute controller %d for device %s fd %d\n", - axis, device->identifier.name.string(), device->fd); + LOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", + axis, device->identifier.name.string(), device->fd, errno); return -errno; } @@ -335,6 +335,33 @@ int32_t EventHub::getSwitchStateLocked(Device* device, int32_t sw) const { return AKEY_STATE_UNKNOWN; } +status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const { + if (axis >= 0 && axis <= ABS_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device != NULL) { + return getAbsoluteAxisValueLocked(device, axis, outValue); + } + } + *outValue = 0; + return -1; +} + +status_t EventHub::getAbsoluteAxisValueLocked(Device* device, int32_t axis, + int32_t* outValue) const { + struct input_absinfo info; + + if(ioctl(device->fd, EVIOCGABS(axis), &info)) { + LOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", + axis, device->identifier.name.string(), device->fd, errno); + return -errno; + } + + *outValue = info.value; + return OK; +} + bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { AutoMutex _l(mLock); diff --git a/services/input/EventHub.h b/services/input/EventHub.h index 0a34e45..695dfdf 100644 --- a/services/input/EventHub.h +++ b/services/input/EventHub.h @@ -186,6 +186,8 @@ public: virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0; virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0; virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0; + virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const = 0; /* * Examine key input devices for specific framework keycode support @@ -237,6 +239,7 @@ public: virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const; virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const; virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const; + virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const; virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; @@ -305,6 +308,7 @@ private: int32_t getScanCodeStateLocked(Device* device, int32_t scanCode) const; int32_t getKeyCodeStateLocked(Device* device, int32_t keyCode) const; int32_t getSwitchStateLocked(Device* device, int32_t sw) const; + int32_t getAbsoluteAxisValueLocked(Device* device, int32_t axis, int32_t* outValue) const; bool markSupportedKeyCodesLocked(Device* device, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index 85ce38a..10b9083 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -1239,8 +1239,9 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, const InputWindow* newHoverWindow = NULL; bool isSplit = mTouchState.split; - bool switchedDevice = mTouchState.deviceId != entry->deviceId - || mTouchState.source != entry->source; + bool switchedDevice = mTouchState.deviceId >= 0 + && (mTouchState.deviceId != entry->deviceId + || mTouchState.source != entry->source); bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT); diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp index 82c3af3..49cb864 100644 --- a/services/input/InputReader.cpp +++ b/services/input/InputReader.cpp @@ -768,10 +768,6 @@ void InputReader::dump(String8& dump) { dump.append(mConfig.excludedDeviceNames.itemAt(i).string()); } dump.append("]\n"); - dump.appendFormat(INDENT2 "FilterTouchEvents: %s\n", - toString(mConfig.filterTouchEvents)); - dump.appendFormat(INDENT2 "FilterJumpyTouchEvents: %s\n", - toString(mConfig.filterJumpyTouchEvents)); dump.appendFormat(INDENT2 "VirtualKeyQuietTime: %0.1fms\n", mConfig.virtualKeyQuietTime * 0.000001f); @@ -1955,13 +1951,6 @@ void TouchInputMapper::initializeLocked() { mLastTouch.clear(); mDownTime = 0; - for (uint32_t i = 0; i < MAX_POINTERS; i++) { - mAveragingTouchFilter.historyStart[i] = 0; - mAveragingTouchFilter.historyEnd[i] = 0; - } - - mJumpyTouchFilter.jumpyPointsDropped = 0; - mLocked.currentVirtualKey.down = false; mLocked.orientedRanges.havePressure = false; @@ -2028,10 +2017,6 @@ void TouchInputMapper::configure(const InputReaderConfiguration* config, uint32_ } void TouchInputMapper::configureParameters() { - mParameters.useBadTouchFilter = mConfig.filterTouchEvents; - mParameters.useAveragingTouchFilter = mConfig.filterTouchEvents; - mParameters.useJumpyTouchFilter = mConfig.filterJumpyTouchEvents; - // Use the pointer presentation mode for devices that do not support distinct // multitouch. The spot-based presentation relies on being able to accurately // locate two or more fingers on the touch pad. @@ -2122,13 +2107,6 @@ void TouchInputMapper::dumpParameters(String8& dump) { mParameters.associatedDisplayId); dump.appendFormat(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); - - dump.appendFormat(INDENT4 "UseBadTouchFilter: %s\n", - toString(mParameters.useBadTouchFilter)); - dump.appendFormat(INDENT4 "UseAveragingTouchFilter: %s\n", - toString(mParameters.useAveragingTouchFilter)); - dump.appendFormat(INDENT4 "UseJumpyTouchFilter: %s\n", - toString(mParameters.useJumpyTouchFilter)); } void TouchInputMapper::configureRawAxes() { @@ -2985,33 +2963,10 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { #endif // Preprocess pointer data. - if (mParameters.useBadTouchFilter) { - if (applyBadTouchFilter()) { - havePointerIds = false; - } - } - - if (mParameters.useJumpyTouchFilter) { - if (applyJumpyTouchFilter()) { - havePointerIds = false; - } - } - if (!havePointerIds) { calculatePointerIds(); } - TouchData temp; - TouchData* savedTouch; - if (mParameters.useAveragingTouchFilter) { - temp.copyFrom(mCurrentTouch); - savedTouch = & temp; - - applyAveragingTouchFilter(); - } else { - savedTouch = & mCurrentTouch; - } - uint32_t policyFlags = 0; if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) { if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) { @@ -3058,9 +3013,9 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { // Keep the button state so we can track edge-triggered button state changes. if (touchResult == DROP_STROKE) { mLastTouch.clear(); - mLastTouch.buttonState = savedTouch->buttonState; + mLastTouch.buttonState = mCurrentTouch.buttonState; } else { - mLastTouch.copyFrom(*savedTouch); + mLastTouch.copyFrom(mCurrentTouch); } } @@ -4826,359 +4781,6 @@ void TouchInputMapper::calculatePointerIds() { } } -/* Special hack for devices that have bad screen data: if one of the - * points has moved more than a screen height from the last position, - * then drop it. */ -bool TouchInputMapper::applyBadTouchFilter() { - uint32_t pointerCount = mCurrentTouch.pointerCount; - - // Nothing to do if there are no points. - if (pointerCount == 0) { - return false; - } - - // Don't do anything if a finger is going down or up. We run - // here before assigning pointer IDs, so there isn't a good - // way to do per-finger matching. - if (pointerCount != mLastTouch.pointerCount) { - return false; - } - - // We consider a single movement across more than a 7/16 of - // the long size of the screen to be bad. This was a magic value - // determined by looking at the maximum distance it is feasible - // to actually move in one sample. - int32_t maxDeltaY = (mRawAxes.y.maxValue - mRawAxes.y.minValue + 1) * 7 / 16; - - // XXX The original code in InputDevice.java included commented out - // code for testing the X axis. Note that when we drop a point - // we don't actually restore the old X either. Strange. - // The old code also tries to track when bad points were previously - // detected but it turns out that due to the placement of a "break" - // at the end of the loop, we never set mDroppedBadPoint to true - // so it is effectively dead code. - // Need to figure out if the old code is busted or just overcomplicated - // but working as intended. - - // Look through all new points and see if any are farther than - // acceptable from all previous points. - for (uint32_t i = pointerCount; i-- > 0; ) { - int32_t y = mCurrentTouch.pointers[i].y; - int32_t closestY = INT_MAX; - int32_t closestDeltaY = 0; - -#if DEBUG_HACKS - LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y); -#endif - - for (uint32_t j = pointerCount; j-- > 0; ) { - int32_t lastY = mLastTouch.pointers[j].y; - int32_t deltaY = abs(y - lastY); - -#if DEBUG_HACKS - LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d", - j, lastY, deltaY); -#endif - - if (deltaY < maxDeltaY) { - goto SkipSufficientlyClosePoint; - } - if (deltaY < closestDeltaY) { - closestDeltaY = deltaY; - closestY = lastY; - } - } - - // Must not have found a close enough match. -#if DEBUG_HACKS - LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d", - i, y, closestY, closestDeltaY, maxDeltaY); -#endif - - mCurrentTouch.pointers[i].y = closestY; - return true; // XXX original code only corrects one point - - SkipSufficientlyClosePoint: ; - } - - // No change. - return false; -} - -/* Special hack for devices that have bad screen data: drop points where - * the coordinate value for one axis has jumped to the other pointer's location. - */ -bool TouchInputMapper::applyJumpyTouchFilter() { - uint32_t pointerCount = mCurrentTouch.pointerCount; - if (mLastTouch.pointerCount != pointerCount) { -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Different pointer count %d -> %d", - mLastTouch.pointerCount, pointerCount); - for (uint32_t i = 0; i < pointerCount; i++) { - LOGD(" Pointer %d (%d, %d)", i, - mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y); - } -#endif - - if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) { - if (mLastTouch.pointerCount == 1 && pointerCount == 2) { - // Just drop the first few events going from 1 to 2 pointers. - // They're bad often enough that they're not worth considering. - mCurrentTouch.pointerCount = 1; - mJumpyTouchFilter.jumpyPointsDropped += 1; - -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Pointer 2 dropped"); -#endif - return true; - } else if (mLastTouch.pointerCount == 2 && pointerCount == 1) { - // The event when we go from 2 -> 1 tends to be messed up too - mCurrentTouch.pointerCount = 2; - mCurrentTouch.pointers[0] = mLastTouch.pointers[0]; - mCurrentTouch.pointers[1] = mLastTouch.pointers[1]; - mJumpyTouchFilter.jumpyPointsDropped += 1; - -#if DEBUG_HACKS - for (int32_t i = 0; i < 2; i++) { - LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i, - mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y); - } -#endif - return true; - } - } - // Reset jumpy points dropped on other transitions or if limit exceeded. - mJumpyTouchFilter.jumpyPointsDropped = 0; - -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Transition - drop limit reset"); -#endif - return false; - } - - // We have the same number of pointers as last time. - // A 'jumpy' point is one where the coordinate value for one axis - // has jumped to the other pointer's location. No need to do anything - // else if we only have one pointer. - if (pointerCount < 2) { - return false; - } - - if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) { - int jumpyEpsilon = (mRawAxes.y.maxValue - mRawAxes.y.minValue + 1) / JUMPY_EPSILON_DIVISOR; - - // We only replace the single worst jumpy point as characterized by pointer distance - // in a single axis. - int32_t badPointerIndex = -1; - int32_t badPointerReplacementIndex = -1; - int32_t badPointerDistance = INT_MIN; // distance to be corrected - - for (uint32_t i = pointerCount; i-- > 0; ) { - int32_t x = mCurrentTouch.pointers[i].x; - int32_t y = mCurrentTouch.pointers[i].y; - -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y); -#endif - - // Check if a touch point is too close to another's coordinates - bool dropX = false, dropY = false; - for (uint32_t j = 0; j < pointerCount; j++) { - if (i == j) { - continue; - } - - if (abs(x - mCurrentTouch.pointers[j].x) <= jumpyEpsilon) { - dropX = true; - break; - } - - if (abs(y - mCurrentTouch.pointers[j].y) <= jumpyEpsilon) { - dropY = true; - break; - } - } - if (! dropX && ! dropY) { - continue; // not jumpy - } - - // Find a replacement candidate by comparing with older points on the - // complementary (non-jumpy) axis. - int32_t distance = INT_MIN; // distance to be corrected - int32_t replacementIndex = -1; - - if (dropX) { - // X looks too close. Find an older replacement point with a close Y. - int32_t smallestDeltaY = INT_MAX; - for (uint32_t j = 0; j < pointerCount; j++) { - int32_t deltaY = abs(y - mLastTouch.pointers[j].y); - if (deltaY < smallestDeltaY) { - smallestDeltaY = deltaY; - replacementIndex = j; - } - } - distance = abs(x - mLastTouch.pointers[replacementIndex].x); - } else { - // Y looks too close. Find an older replacement point with a close X. - int32_t smallestDeltaX = INT_MAX; - for (uint32_t j = 0; j < pointerCount; j++) { - int32_t deltaX = abs(x - mLastTouch.pointers[j].x); - if (deltaX < smallestDeltaX) { - smallestDeltaX = deltaX; - replacementIndex = j; - } - } - distance = abs(y - mLastTouch.pointers[replacementIndex].y); - } - - // If replacing this pointer would correct a worse error than the previous ones - // considered, then use this replacement instead. - if (distance > badPointerDistance) { - badPointerIndex = i; - badPointerReplacementIndex = replacementIndex; - badPointerDistance = distance; - } - } - - // Correct the jumpy pointer if one was found. - if (badPointerIndex >= 0) { -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)", - badPointerIndex, - mLastTouch.pointers[badPointerReplacementIndex].x, - mLastTouch.pointers[badPointerReplacementIndex].y); -#endif - - mCurrentTouch.pointers[badPointerIndex].x = - mLastTouch.pointers[badPointerReplacementIndex].x; - mCurrentTouch.pointers[badPointerIndex].y = - mLastTouch.pointers[badPointerReplacementIndex].y; - mJumpyTouchFilter.jumpyPointsDropped += 1; - return true; - } - } - - mJumpyTouchFilter.jumpyPointsDropped = 0; - return false; -} - -/* Special hack for devices that have bad screen data: aggregate and - * compute averages of the coordinate data, to reduce the amount of - * jitter seen by applications. */ -void TouchInputMapper::applyAveragingTouchFilter() { - for (uint32_t currentIndex = 0; currentIndex < mCurrentTouch.pointerCount; currentIndex++) { - uint32_t id = mCurrentTouch.pointers[currentIndex].id; - int32_t x = mCurrentTouch.pointers[currentIndex].x; - int32_t y = mCurrentTouch.pointers[currentIndex].y; - int32_t pressure; - switch (mCalibration.pressureSource) { - case Calibration::PRESSURE_SOURCE_PRESSURE: - pressure = mCurrentTouch.pointers[currentIndex].pressure; - break; - case Calibration::PRESSURE_SOURCE_TOUCH: - pressure = mCurrentTouch.pointers[currentIndex].touchMajor; - break; - default: - pressure = 1; - break; - } - - if (mLastTouch.idBits.hasBit(id)) { - // Pointer was down before and is still down now. - // Compute average over history trace. - uint32_t start = mAveragingTouchFilter.historyStart[id]; - uint32_t end = mAveragingTouchFilter.historyEnd[id]; - - int64_t deltaX = x - mAveragingTouchFilter.historyData[end].pointers[id].x; - int64_t deltaY = y - mAveragingTouchFilter.historyData[end].pointers[id].y; - uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); - -#if DEBUG_HACKS - LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld", - id, distance); -#endif - - if (distance < AVERAGING_DISTANCE_LIMIT) { - // Increment end index in preparation for recording new historical data. - end += 1; - if (end > AVERAGING_HISTORY_SIZE) { - end = 0; - } - - // If the end index has looped back to the start index then we have filled - // the historical trace up to the desired size so we drop the historical - // data at the start of the trace. - if (end == start) { - start += 1; - if (start > AVERAGING_HISTORY_SIZE) { - start = 0; - } - } - - // Add the raw data to the historical trace. - mAveragingTouchFilter.historyStart[id] = start; - mAveragingTouchFilter.historyEnd[id] = end; - mAveragingTouchFilter.historyData[end].pointers[id].x = x; - mAveragingTouchFilter.historyData[end].pointers[id].y = y; - mAveragingTouchFilter.historyData[end].pointers[id].pressure = pressure; - - // Average over all historical positions in the trace by total pressure. - int32_t averagedX = 0; - int32_t averagedY = 0; - int32_t totalPressure = 0; - for (;;) { - int32_t historicalX = mAveragingTouchFilter.historyData[start].pointers[id].x; - int32_t historicalY = mAveragingTouchFilter.historyData[start].pointers[id].y; - int32_t historicalPressure = mAveragingTouchFilter.historyData[start] - .pointers[id].pressure; - - averagedX += historicalX * historicalPressure; - averagedY += historicalY * historicalPressure; - totalPressure += historicalPressure; - - if (start == end) { - break; - } - - start += 1; - if (start > AVERAGING_HISTORY_SIZE) { - start = 0; - } - } - - if (totalPressure != 0) { - averagedX /= totalPressure; - averagedY /= totalPressure; - -#if DEBUG_HACKS - LOGD("AveragingTouchFilter: Pointer id %d - " - "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure, - averagedX, averagedY); -#endif - - mCurrentTouch.pointers[currentIndex].x = averagedX; - mCurrentTouch.pointers[currentIndex].y = averagedY; - } - } else { -#if DEBUG_HACKS - LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id); -#endif - } - } else { -#if DEBUG_HACKS - LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id); -#endif - } - - // Reset pointer history. - mAveragingTouchFilter.historyStart[id] = 0; - mAveragingTouchFilter.historyEnd[id] = 0; - mAveragingTouchFilter.historyData[0].pointers[id].x = x; - mAveragingTouchFilter.historyData[0].pointers[id].y = y; - mAveragingTouchFilter.historyData[0].pointers[id].pressure = pressure; - } -} - int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { { // acquire lock AutoMutex _l(mLock); @@ -5394,7 +4996,6 @@ void SingleTouchInputMapper::configureRawAxes() { MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) : TouchInputMapper(device), mSlotCount(0), mUsingSlotsProtocol(false) { - clearState(); } MultiTouchInputMapper::~MultiTouchInputMapper() { @@ -5404,6 +5005,25 @@ void MultiTouchInputMapper::clearState() { mAccumulator.clearSlots(mSlotCount); mAccumulator.clearButtons(); mButtonState = 0; + mPointerIdBits.clear(); + + if (mUsingSlotsProtocol) { + // Query the driver for the current slot index and use it as the initial slot + // before we start reading events from the device. It is possible that the + // current slot index will not be the same as it was when the first event was + // written into the evdev buffer, which means the input mapper could start + // out of sync with the initial state of the events in the evdev buffer. + // In the extremely unlikely case that this happens, the data from + // two slots will be confused until the next ABS_MT_SLOT event is received. + // This can cause the touch point to "jump", but at least there will be + // no stuck touches. + status_t status = getEventHub()->getAbsoluteAxisValue(getDeviceId(), ABS_MT_SLOT, + &mAccumulator.currentSlot); + if (status) { + LOGW("Could not retrieve current multitouch slot index. status=%d", status); + mAccumulator.currentSlot = -1; + } + } } void MultiTouchInputMapper::reset() { @@ -5610,28 +5230,32 @@ void MultiTouchInputMapper::sync(nsecs_t when) { // Assign pointer id using tracking id if available. if (havePointerIds) { - int32_t id; - if (mUsingSlotsProtocol) { - id = inIndex; - } else if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) { - id = inSlot.absMTTrackingId; - } else { - id = -1; - } + int32_t id = -1; + if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) { + int32_t trackingId = inSlot.absMTTrackingId; + + for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty(); ) { + uint32_t n = idBits.firstMarkedBit(); + idBits.clearBit(n); + + if (mPointerTrackingIdMap[n] == trackingId) { + id = n; + } + } - if (id >= 0 && id <= MAX_POINTER_ID) { + if (id < 0 && !mPointerIdBits.isFull()) { + id = mPointerIdBits.firstUnmarkedBit(); + mPointerIdBits.markBit(id); + mPointerTrackingIdMap[id] = trackingId; + } + } + if (id < 0) { + havePointerIds = false; + mCurrentTouch.idBits.clear(); + } else { outPointer.id = id; mCurrentTouch.idToIndex[id] = outCount; mCurrentTouch.idBits.markBit(id); - } else { - if (id >= 0) { -#if DEBUG_POINTERS - LOGD("Pointers: Ignoring driver provided slot index or tracking id %d because " - "it is larger than the maximum supported pointer id %d", - id, MAX_POINTER_ID); -#endif - } - havePointerIds = false; } } @@ -5643,6 +5267,8 @@ void MultiTouchInputMapper::sync(nsecs_t when) { mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp; mCurrentTouch.buttonState = mButtonState; + mPointerIdBits = mCurrentTouch.idBits; + syncTouch(when, havePointerIds); if (!mUsingSlotsProtocol) { @@ -5682,6 +5308,8 @@ void MultiTouchInputMapper::configureRawAxes() { } mAccumulator.allocateSlots(mSlotCount); + + clearState(); } diff --git a/services/input/InputReader.h b/services/input/InputReader.h index 288ff4e..69fa6b4 100644 --- a/services/input/InputReader.h +++ b/services/input/InputReader.h @@ -57,14 +57,6 @@ struct InputReaderConfiguration { CHANGE_MUST_REOPEN = 1 << 31, }; - // Determines whether to turn on some hacks we have to improve the touch interaction with a - // certain device whose screen currently is not all that good. - bool filterTouchEvents; - - // Determines whether to turn on some hacks to improve touch interaction with another device - // where touch coordinate data can get corrupted. - bool filterJumpyTouchEvents; - // Gets the amount of time to disable virtual keys after the screen is touched // in order to filter out accidental virtual key presses due to swiping gestures // or taps near the edge of the display. May be 0 to disable the feature. @@ -146,8 +138,6 @@ struct InputReaderConfiguration { float pointerGestureZoomSpeedRatio; InputReaderConfiguration() : - filterTouchEvents(false), - filterJumpyTouchEvents(false), virtualKeyQuietTime(0), pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, 3.0f), wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f), @@ -812,10 +802,6 @@ protected: int32_t associatedDisplayId; bool orientationAware; - bool useBadTouchFilter; - bool useJumpyTouchFilter; - bool useAveragingTouchFilter; - enum GestureMode { GESTURE_MODE_POINTER, GESTURE_MODE_SPOTS, @@ -1042,38 +1028,6 @@ protected: void syncTouch(nsecs_t when, bool havePointerIds); private: - /* Maximum number of historical samples to average. */ - static const uint32_t AVERAGING_HISTORY_SIZE = 5; - - /* Slop distance for jumpy pointer detection. - * The vertical range of the screen divided by this is our epsilon value. */ - static const uint32_t JUMPY_EPSILON_DIVISOR = 212; - - /* Number of jumpy points to drop for touchscreens that need it. */ - static const uint32_t JUMPY_TRANSITION_DROPS = 3; - static const uint32_t JUMPY_DROP_LIMIT = 3; - - /* Maximum squared distance for averaging. - * If moving farther than this, turn of averaging to avoid lag in response. */ - static const uint64_t AVERAGING_DISTANCE_LIMIT = 75 * 75; - - struct AveragingTouchFilterState { - // Individual history tracks are stored by pointer id - uint32_t historyStart[MAX_POINTERS]; - uint32_t historyEnd[MAX_POINTERS]; - struct { - struct { - int32_t x; - int32_t y; - int32_t pressure; - } pointers[MAX_POINTERS]; - } historyData[AVERAGING_HISTORY_SIZE]; - } mAveragingTouchFilter; - - struct JumpyTouchFilterState { - uint32_t jumpyPointsDropped; - } mJumpyTouchFilter; - struct PointerDistanceHeapElement { uint32_t currentPointerIndex : 8; uint32_t lastPointerIndex : 8; @@ -1251,9 +1205,6 @@ private: bool isPointInsideSurfaceLocked(int32_t x, int32_t y); const VirtualKey* findVirtualKeyHitLocked(int32_t x, int32_t y); - bool applyBadTouchFilter(); - bool applyJumpyTouchFilter(); - void applyAveragingTouchFilter(); void calculatePointerIds(); }; @@ -1401,6 +1352,10 @@ private: int32_t mButtonState; + // Specifies the pointer id bits that are in use, and their associated tracking id. + BitSet32 mPointerIdBits; + int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1]; + void clearState(); void sync(nsecs_t when); diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp index e349248..67067de 100644 --- a/services/input/tests/InputReader_test.cpp +++ b/services/input/tests/InputReader_test.cpp @@ -144,14 +144,6 @@ public: mDisplayInfos.add(displayId, info); } - void setFilterTouchEvents(bool enabled) { - mConfig.filterTouchEvents = enabled; - } - - void setFilterJumpyTouchEvents(bool enabled) { - mConfig.filterJumpyTouchEvents = enabled; - } - virtual nsecs_t getVirtualKeyQuietTime() { return 0; } @@ -429,6 +421,7 @@ class FakeEventHub : public EventHubInterface { KeyedVector<int32_t, int32_t> keyCodeStates; KeyedVector<int32_t, int32_t> scanCodeStates; KeyedVector<int32_t, int32_t> switchStates; + KeyedVector<int32_t, int32_t> absoluteAxisValue; KeyedVector<int32_t, KeyInfo> keys; KeyedVector<int32_t, bool> leds; Vector<VirtualKeyDefinition> virtualKeys; @@ -514,6 +507,11 @@ public: device->switchStates.replaceValueFor(switchCode, state); } + void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { + Device* device = getDevice(deviceId); + device->absoluteAxisValue.replaceValueFor(axis, value); + } + void addKey(int32_t deviceId, int32_t scanCode, int32_t keyCode, uint32_t flags) { Device* device = getDevice(deviceId); KeyInfo info; @@ -677,6 +675,20 @@ private: return AKEY_STATE_UNKNOWN; } + virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->absoluteAxisValue.indexOfKey(axis); + if (index >= 0) { + *outValue = device->absoluteAxisValue.valueAt(index); + return OK; + } + } + *outValue = 0; + return -1; + } + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { bool result = false; @@ -3516,7 +3528,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); @@ -3525,9 +3537,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); - ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_EQ(2, motionArgs.pointerProperties[1].id); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); @@ -3547,9 +3559,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); - ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_EQ(2, motionArgs.pointerProperties[1].id); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); @@ -3567,9 +3579,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); - ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_EQ(2, motionArgs.pointerProperties[1].id); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0)); @@ -3579,7 +3591,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(2, motionArgs.pointerProperties[0].id); + ASSERT_EQ(1, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); @@ -3594,7 +3606,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(2, motionArgs.pointerProperties[0].id); + ASSERT_EQ(1, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); @@ -3610,17 +3622,17 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); - ASSERT_EQ(2, motionArgs.pointerProperties[0].id); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_EQ(3, motionArgs.pointerProperties[1].id); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); // Second finger up. x3 += 30; y3 -= 20; @@ -3630,22 +3642,22 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); - ASSERT_EQ(2, motionArgs.pointerProperties[0].id); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_EQ(3, motionArgs.pointerProperties[1].id); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(3, motionArgs.pointerProperties[0].id); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); @@ -3657,7 +3669,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(3, motionArgs.pointerProperties[0].id); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0)); @@ -3708,7 +3720,7 @@ TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { FakeInputDispatcher::NotifyMotionArgs args; ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(id, args.pointerProperties[0].id); + ASSERT_EQ(0, args.pointerProperties[0].id); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, orientation)); } diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 8fb6274..afc04bb 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -44,7 +44,6 @@ import android.net.NetworkUtils; import android.net.Proxy; import android.net.ProxyProperties; import android.net.RouteInfo; -import android.net.vpn.VpnManager; import android.net.wifi.WifiStateTracker; import android.os.Binder; import android.os.FileUtils; @@ -496,11 +495,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mSettingsObserver.observe(mContext); loadGlobalProxy(); - - VpnManager.startVpnService(context); } - /** * Sets the preferred network. * @param preference the new preference @@ -2489,8 +2485,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @hide */ @Override - public String prepareVpn(String packageName) { - return mVpn.prepare(packageName); + public boolean prepareVpn(String oldPackage, String newPackage) { + return mVpn.prepare(oldPackage, newPackage); } /** @@ -2505,6 +2501,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mVpn.establish(config); } + /** + * Handle a legacy VPN request. + * @hide + */ + @Override + public void doLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { + mVpn.doLegacyVpn(config, racoon, mtpd); + } + private String getDefaultInterface() { if (ConnectivityManager.isNetworkTypeValid(mActiveDefaultNetwork)) { NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork]; @@ -2533,7 +2538,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private VpnCallback() { } - public synchronized void override(String[] dnsServers) { + public synchronized void override(List<String> dnsServers, List<String> searchDomains) { // TODO: override DNS servers and http proxy. } diff --git a/services/java/com/android/server/DnsPinger.java b/services/java/com/android/server/DnsPinger.java index a7324d9..4e33938 100644 --- a/services/java/com/android/server/DnsPinger.java +++ b/services/java/com/android/server/DnsPinger.java @@ -16,7 +16,6 @@ package com.android.server; -import android.content.ContentResolver; import android.content.Context; import android.net.ConnectivityManager; import android.net.LinkProperties; @@ -28,6 +27,7 @@ import android.util.Slog; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; +import java.net.NetworkInterface; import java.net.SocketTimeoutException; import java.util.Collection; import java.util.Random; @@ -49,7 +49,7 @@ public final class DnsPinger { private static final boolean V = true; /** Number of bytes for the query */ - private static final int DNS_QUERY_BASE_SIZE = 33; + private static final int DNS_QUERY_BASE_SIZE = 32; /** The DNS port */ private static final int DNS_PORT = 53; @@ -84,12 +84,7 @@ public final class DnsPinger { * dns set. Should not be null. */ public InetAddress getDns() { - if (mConnectivityManager == null) { - mConnectivityManager = (ConnectivityManager) mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - } - - LinkProperties curLinkProps = mConnectivityManager.getLinkProperties(mConnectionType); + LinkProperties curLinkProps = getCurrentLinkProperties(); if (curLinkProps == null) { Slog.e(TAG, "getCurLinkProperties:: LP for type" + mConnectionType + " is null!"); return mDefaultDns; @@ -104,6 +99,15 @@ public final class DnsPinger { return dnses.iterator().next(); } + private LinkProperties getCurrentLinkProperties() { + if (mConnectivityManager == null) { + mConnectivityManager = (ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + } + + return mConnectivityManager.getLinkProperties(mConnectionType); + } + private InetAddress getDefaultDns() { String dns = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.DEFAULT_DNS_SERVER); @@ -130,8 +134,15 @@ public final class DnsPinger { // Set some socket properties socket.setSoTimeout(timeout); - byte[] buf = new byte[DNS_QUERY_BASE_SIZE]; - fillQuery(buf); + // Try to bind but continue ping if bind fails + try { + socket.setNetworkInterface(NetworkInterface.getByName( + getCurrentLinkProperties().getInterfaceName())); + } catch (Exception e) { + Slog.d(TAG,"pingDns::Error binding to socket", e); + } + + byte[] buf = constructQuery(); // Send the DNS query @@ -164,48 +175,47 @@ public final class DnsPinger { } - private static void fillQuery(byte[] buf) { - - /* - * See RFC2929 (though the bit tables in there are misleading for us. - * For example, the recursion desired bit is the 0th bit for us, but - * looking there it would appear as the 7th bit of the byte - */ - - // Make sure it's all zeroed out - for (int i = 0; i < buf.length; i++) - buf[i] = 0; - - // Form a query for www.android.com + /** + * @return google.com DNS query packet + */ + private static byte[] constructQuery() { + byte[] buf = new byte[DNS_QUERY_BASE_SIZE]; // [0-1] bytes are an ID, generate random ID for this query buf[0] = (byte) sRandom.nextInt(256); buf[1] = (byte) sRandom.nextInt(256); // [2-3] bytes are for flags. - buf[2] = 1; // Recursion desired + buf[2] = 0x01; // Recursion desired - // [4-5] bytes are for the query count - buf[5] = 1; // One query + // [4-5] bytes are for number of queries (QCOUNT) + buf[5] = 0x01; // [6-7] [8-9] [10-11] are all counts of other fields we don't use // [12-15] for www writeString(buf, 12, "www"); - // [16-23] for android - writeString(buf, 16, "android"); + // [16-22] for google + writeString(buf, 16, "google"); + + // [23-26] for com + writeString(buf, 23, "com"); + + // [27] is a null byte terminator byte for the url - // [24-27] for com - writeString(buf, 24, "com"); + // [28-29] bytes are for QTYPE, set to 1 = A (host address) + buf[29] = 0x01; - // [29-30] bytes are for QTYPE, set to 1 - buf[30] = 1; + // [30-31] bytes are for QCLASS, set to 1 = IN (internet) + buf[31] = 0x01; - // [31-32] bytes are for QCLASS, set to 1 - buf[32] = 1; + return buf; } + /** + * Writes the string's length and its contents to the buffer + */ private static void writeString(byte[] buf, int startPos, String string) { int pos = startPos; diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index 1d3e3ac..b3d7220 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -41,7 +41,7 @@ import android.content.IntentFilter; /** * {@hide} */ -public class IntentResolver<F extends IntentFilter, R extends Object> { +public abstract class IntentResolver<F extends IntentFilter, R extends Object> { final private static String TAG = "IntentResolver"; final private static boolean DEBUG = false; final private static boolean localLOGV = DEBUG || false; @@ -333,14 +333,19 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { return false; } - protected String packageForFilter(F filter) { - return null; - } + /** + * Return the package that owns this filter. This must be implemented to + * provide correct filtering of Intents that have specified a package name + * they are to be delivered to. + */ + protected abstract String packageForFilter(F filter); + @SuppressWarnings("unchecked") protected R newResult(F filter, int match) { return (R)filter; } + @SuppressWarnings("unchecked") protected void sortResults(List<R> results) { Collections.sort(results, mResolvePrioritySorter); } @@ -502,6 +507,7 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { String resolvedType, String scheme, List<F> src, List<R> dest) { final String action = intent.getAction(); final Uri data = intent.getData(); + final String packageName = intent.getPackage(); final boolean excludingStopped = intent.isExcludingStopped(); @@ -520,6 +526,14 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { continue; } + // Is delivery being limited to filters owned by a particular package? + if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (debug) { + Slog.v(TAG, " Filter is not from package " + packageName + "; skipping"); + } + continue; + } + // Do we already have this one? if (!allowFilterResult(filter, dest)) { if (debug) { @@ -561,6 +575,7 @@ public class IntentResolver<F extends IntentFilter, R extends Object> { } // Sorts a List of IntentFilter objects into descending priority order. + @SuppressWarnings("rawtypes") private static final Comparator mResolvePrioritySorter = new Comparator() { public int compare(Object o1, Object o2) { final int q1 = ((IntentFilter) o1).getPriority(); diff --git a/services/java/com/android/server/LoadAverageService.java b/services/java/com/android/server/LoadAverageService.java index b6baadb..da9fc99 100644 --- a/services/java/com/android/server/LoadAverageService.java +++ b/services/java/com/android/server/LoadAverageService.java @@ -278,14 +278,14 @@ public class LoadAverageService extends Service { PixelFormat.TRANSLUCENT); params.gravity = Gravity.RIGHT | Gravity.TOP; params.setTitle("Load Average"); - WindowManagerImpl wm = (WindowManagerImpl)getSystemService(WINDOW_SERVICE); + WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE); wm.addView(mView, params); } @Override public void onDestroy() { super.onDestroy(); - ((WindowManagerImpl)getSystemService(WINDOW_SERVICE)).removeView(mView); + ((WindowManager)getSystemService(WINDOW_SERVICE)).removeView(mView); mView = null; } diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 656ec4d..56afe7f 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -195,6 +195,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run final Object mKey; final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>(); int mPendingBroadcasts; + String requiredPermissions; Receiver(ILocationListener listener) { mListener = listener; @@ -284,7 +285,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler); + mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler, + requiredPermissions); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -319,7 +321,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler); + mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler, + requiredPermissions); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -358,7 +361,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler); + mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler, + requiredPermissions); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -572,22 +576,30 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return Settings.Secure.isLocationProviderEnabled(resolver, provider); } - private void checkPermissionsSafe(String provider) { - if ((LocationManager.GPS_PROVIDER.equals(provider) - || LocationManager.PASSIVE_PROVIDER.equals(provider)) - && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED)) { - throw new SecurityException("Provider " + provider - + " requires ACCESS_FINE_LOCATION permission"); + private String checkPermissionsSafe(String provider, String lastPermission) { + if (LocationManager.GPS_PROVIDER.equals(provider) + || LocationManager.PASSIVE_PROVIDER.equals(provider)) { + if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Provider " + provider + + " requires ACCESS_FINE_LOCATION permission"); + } + return ACCESS_FINE_LOCATION; } - if (LocationManager.NETWORK_PROVIDER.equals(provider) - && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) - && (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) - != PackageManager.PERMISSION_GRANTED)) { - throw new SecurityException("Provider " + provider - + " requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission"); + + // Assume any other provider requires the coarse or fine permission. + if (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + return ACCESS_FINE_LOCATION.equals(lastPermission) + ? lastPermission : ACCESS_COARSE_LOCATION; } + if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + return ACCESS_FINE_LOCATION; + } + + throw new SecurityException("Provider " + provider + + " requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission"); } private boolean isAllowedProviderSafe(String provider) { @@ -1099,8 +1111,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + void validatePendingIntent(PendingIntent intent) { + if (intent.isTargetedToPackage()) { + return; + } + Slog.i(TAG, "Given Intent does not require a specific package: " + + intent); + // XXX we should really throw a security exception, if the caller's + // targetSdkVersion is high enough. + //throw new SecurityException("Given Intent does not require a specific package: " + // + intent); + } + public void requestLocationUpdatesPI(String provider, Criteria criteria, long minTime, float minDistance, boolean singleShot, PendingIntent intent) { + validatePendingIntent(intent); if (criteria != null) { // FIXME - should we consider using multiple providers simultaneously // rather than only the best one? @@ -1132,7 +1157,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run throw new IllegalArgumentException("provider=" + provider); } - checkPermissionsSafe(provider); + receiver.requiredPermissions = checkPermissionsSafe(provider, + receiver.requiredPermissions); // so wakelock calls will succeed final int callingUid = Binder.getCallingUid(); @@ -1300,7 +1326,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } // first check for permission to the provider - checkPermissionsSafe(provider); + checkPermissionsSafe(provider, null); // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) != PackageManager.PERMISSION_GRANTED)) { @@ -1432,7 +1458,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (this) { // synchronize to ensure incrementPendingBroadcasts() // is called before decrementPendingBroadcasts() - intent.send(mContext, 0, enteredIntent, this, mLocationHandler); + intent.send(mContext, 0, enteredIntent, this, mLocationHandler, + ACCESS_FINE_LOCATION); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcasts(); @@ -1457,7 +1484,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (this) { // synchronize to ensure incrementPendingBroadcasts() // is called before decrementPendingBroadcasts() - intent.send(mContext, 0, exitedIntent, this, mLocationHandler); + intent.send(mContext, 0, exitedIntent, this, mLocationHandler, + ACCESS_FINE_LOCATION); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcasts(); @@ -1526,6 +1554,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run public void addProximityAlert(double latitude, double longitude, float radius, long expiration, PendingIntent intent) { + validatePendingIntent(intent); try { synchronized (mLock) { addProximityAlertLocked(latitude, longitude, radius, expiration, intent); @@ -1626,7 +1655,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return null; } - checkPermissionsSafe(provider); + checkPermissionsSafe(provider, null); Bundle b = new Bundle(); b.putBoolean("network", p.requiresNetwork()); @@ -1668,7 +1697,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } private boolean _isProviderEnabledLocked(String provider) { - checkPermissionsSafe(provider); + checkPermissionsSafe(provider, null); LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { @@ -1694,7 +1723,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } private Location _getLastKnownLocationLocked(String provider) { - checkPermissionsSafe(provider); + checkPermissionsSafe(provider, null); LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index adc6570..1c150f8 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -16,10 +16,11 @@ package com.android.server; +import static android.Manifest.permission.MANAGE_NETWORK_POLICY; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; -import static android.Manifest.permission.MANAGE_NETWORK_POLICY; +import static android.provider.Settings.Secure.NETSTATS_ENABLED; import android.content.Context; import android.content.pm.PackageManager; @@ -35,6 +36,7 @@ import android.os.Binder; import android.os.INetworkManagementService; import android.os.SystemClock; import android.os.SystemProperties; +import android.provider.Settings; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; @@ -123,6 +125,8 @@ class NetworkManagementService extends INetworkManagementService.Stub { /** Set of UIDs with active reject rules. */ private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray(); + private boolean mBandwidthControlEnabled; + /** * Constructs a new NetworkManagementService instance * @@ -161,6 +165,29 @@ class NetworkManagementService extends INetworkManagementService.Stub { return new NetworkManagementService(context, procRoot); } + public void systemReady() { + + // only enable bandwidth control when support exists, and requested by + // system setting. + // TODO: eventually migrate to be always enabled + final boolean hasKernelSupport = new File("/proc/net/xt_qtaguid/ctrl").exists(); + final boolean shouldEnable = + Settings.Secure.getInt(mContext.getContentResolver(), NETSTATS_ENABLED, 0) != 0; + + mBandwidthControlEnabled = false; + if (hasKernelSupport && shouldEnable) { + Slog.d(TAG, "enabling bandwidth control"); + try { + mConnector.doCommand("bandwidth enable"); + mBandwidthControlEnabled = true; + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "problem enabling bandwidth controls", e); + } + } else { + Slog.d(TAG, "not enabling bandwidth control"); + } + } + public void registerObserver(INetworkManagementEventObserver obs) { Slog.d(TAG, "Registering observer"); mObservers.add(obs); @@ -919,7 +946,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - if (mProcStatsNetfilter.exists()) { + if (mBandwidthControlEnabled) { return getNetworkStatsDetailNetfilter(UID_ALL); } else { return getNetworkStatsDetailUidstat(UID_ALL); @@ -930,6 +957,10 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void setInterfaceQuota(String iface, long quota) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + // silently discard when control disabled + // TODO: eventually migrate to be always enabled + if (!mBandwidthControlEnabled) return; + synchronized (mInterfaceQuota) { if (mInterfaceQuota.contains(iface)) { // TODO: eventually consider throwing @@ -953,6 +984,10 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void removeInterfaceQuota(String iface) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + // silently discard when control disabled + // TODO: eventually migrate to be always enabled + if (!mBandwidthControlEnabled) return; + synchronized (mInterfaceQuota) { if (!mInterfaceQuota.contains(iface)) { // TODO: eventually consider throwing @@ -976,6 +1011,10 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + // silently discard when control disabled + // TODO: eventually migrate to be always enabled + if (!mBandwidthControlEnabled) return; + synchronized (mUidRejectOnQuota) { final boolean oldRejectOnQuota = mUidRejectOnQuota.get(uid, false); if (oldRejectOnQuota == rejectOnQuotaInterfaces) { @@ -1011,7 +1050,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); } - if (mProcStatsNetfilter.exists()) { + if (mBandwidthControlEnabled) { return getNetworkStatsDetailNetfilter(uid); } else { return getNetworkStatsDetailUidstat(uid); @@ -1151,12 +1190,6 @@ class NetworkManagementService extends INetworkManagementService.Stub { return getInterfaceThrottle(iface, false); } - @Override - public void setBandwidthControlEnabled(boolean enabled) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - mConnector.doCommand(String.format("bandwidth %s", (enabled ? "enable" : "disable"))); - } - /** * Split given line into {@link ArrayList}. */ diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index dbfd145..8c7e279 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -523,6 +523,7 @@ class ServerThread extends Thread { // These are needed to propagate to the runnable below. final Context contextF = context; final BatteryService batteryF = battery; + final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; final NetworkPolicyManagerService networkPolicyF = networkPolicy; final ConnectivityService connectivityF = connectivity; @@ -550,6 +551,7 @@ class ServerThread extends Thread { startSystemUi(contextF); if (batteryF != null) batteryF.systemReady(); + if (networkManagementF != null) networkManagementF.systemReady(); if (networkStatsF != null) networkStatsF.systemReady(); if (networkPolicyF != null) networkPolicyF.systemReady(); if (connectivityF != null) connectivityF.systemReady(); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 4ec71c1..48b0b66 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -622,6 +622,11 @@ public final class ActivityManagerService extends ActivityManagerNative } return true; } + + @Override + protected String packageForFilter(BroadcastFilter filter) { + return filter.packageName; + } }; /** @@ -1825,6 +1830,8 @@ public final class ActivityManagerService extends ActivityManagerNative // We already have the app running, or are waiting for it to // come up (we have a pid but not yet its thread), so keep it. if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app); + // If this is a new package in the process, add the package to the list + app.addPackage(info.packageName); return app; } else { // An application record is attached to a previous process, @@ -2278,7 +2285,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - return pir.sendInner(0, fillInIntent, resolvedType, + return pir.sendInner(0, fillInIntent, resolvedType, null, null, resultTo, resultWho, requestCode, flagsMask, flagsValues); } @@ -4162,6 +4169,27 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + public boolean isIntentSenderTargetedToPackage(IIntentSender pendingResult) { + if (!(pendingResult instanceof PendingIntentRecord)) { + return false; + } + try { + PendingIntentRecord res = (PendingIntentRecord)pendingResult; + if (res.key.allIntents == null) { + return false; + } + for (int i=0; i<res.key.allIntents.length; i++) { + Intent intent = res.key.allIntents[i]; + if (intent.getPackage() != null && intent.getComponent() != null) { + return false; + } + } + return true; + } catch (ClassCastException e) { + } + return false; + } + public void setProcessLimit(int max) { enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT, "setProcessLimit()"); @@ -5143,7 +5171,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private void removeTaskProcessesLocked(ActivityRecord root) { + private void cleanUpRemovedTaskLocked(ActivityRecord root, boolean killProcesses) { TaskRecord tr = root.task; Intent baseIntent = new Intent( tr.intent != null ? tr.intent : tr.affinityIntent); @@ -5166,6 +5194,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceRecord sr = services.get(i); if (sr.startRequested) { if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) { + Slog.i(TAG, "Stopping service " + sr.shortName + ": remove task"); stopServiceLocked(sr); } else { sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, @@ -5177,26 +5206,28 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // Find any running processes associated with this app. - ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); - SparseArray<ProcessRecord> appProcs - = mProcessNames.getMap().get(component.getPackageName()); - if (appProcs != null) { - for (int i=0; i<appProcs.size(); i++) { - procs.add(appProcs.valueAt(i)); - } - } - - // Kill the running processes. - for (int i=0; i<procs.size(); i++) { - ProcessRecord pr = procs.get(i); - if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { - Slog.i(TAG, "Killing " + pr + ": remove task"); - EventLog.writeEvent(EventLogTags.AM_KILL, pr.pid, - pr.processName, pr.setAdj, "remove task"); - Process.killProcessQuiet(pr.pid); - } else { - pr.waitingToKill = "remove task"; + if (killProcesses) { + // Find any running processes associated with this app. + ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); + SparseArray<ProcessRecord> appProcs + = mProcessNames.getMap().get(component.getPackageName()); + if (appProcs != null) { + for (int i=0; i<appProcs.size(); i++) { + procs.add(appProcs.valueAt(i)); + } + } + + // Kill the running processes. + for (int i=0; i<procs.size(); i++) { + ProcessRecord pr = procs.get(i); + if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { + Slog.i(TAG, "Killing " + pr.toShortString() + ": remove task"); + EventLog.writeEvent(EventLogTags.AM_KILL, pr.pid, + pr.processName, pr.setAdj, "remove task"); + Process.killProcessQuiet(pr.pid); + } else { + pr.waitingToKill = "remove task"; + } } } } @@ -5210,11 +5241,8 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityRecord r = mMainStack.removeTaskActivitiesLocked(taskId, -1); if (r != null) { mRecentTasks.remove(r.task); - - if ((flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0) { - removeTaskProcessesLocked(r); - } - + cleanUpRemovedTaskLocked(r, + (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0); return true; } } finally { @@ -9895,6 +9923,7 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord app = getProcessRecordLocked(appName, r.appInfo.uid); if (app != null && app.thread != null) { try { + app.addPackage(r.appInfo.packageName); realStartServiceLocked(r, app); return true; } catch (RemoteException e) { @@ -10945,7 +10974,7 @@ public final class ActivityManagerService extends ActivityManagerNative mBroadcastsScheduled = true; } - public Intent registerReceiver(IApplicationThread caller, + public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String permission) { synchronized(this) { ProcessRecord callerApp = null; @@ -10957,6 +10986,13 @@ public final class ActivityManagerService extends ActivityManagerNative + " (pid=" + Binder.getCallingPid() + ") when registering receiver " + receiver); } + if (callerApp.info.uid != Process.SYSTEM_UID && + !callerApp.pkgList.contains(callerPackage)) { + throw new SecurityException("Given caller package " + callerPackage + + " is not running in process " + callerApp); + } + } else { + callerPackage = null; } List allSticky = null; @@ -11001,7 +11037,7 @@ public final class ActivityManagerService extends ActivityManagerNative } mRegisteredReceivers.put(receiver.asBinder(), rl); } - BroadcastFilter bf = new BroadcastFilter(filter, rl, permission); + BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, permission); rl.add(bf); if (!bf.debugCheck()) { Slog.w(TAG, "==> For Dynamic broadast"); @@ -12155,6 +12191,7 @@ public final class ActivityManagerService extends ActivityManagerNative info.activityInfo.applicationInfo.uid); if (app != null && app.thread != null) { try { + app.addPackage(info.activityInfo.packageName); processCurBroadcastLocked(r, app); return; } catch (RemoteException e) { @@ -13132,7 +13169,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " to " + app.curSchedGroup); if (app.waitingToKill != null && app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { - Slog.i(TAG, "Killing " + app + ": " + app.waitingToKill); + Slog.i(TAG, "Killing " + app.toShortString() + ": " + app.waitingToKill); EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, app.processName, app.setAdj, app.waitingToKill); Process.killProcessQuiet(app.pid); diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index b94ee58..b1da69f 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -652,6 +652,7 @@ final class ActivityStack { if (app != null && app.thread != null) { try { + app.addPackage(r.info.packageName); realStartActivityLocked(r, app, andResume, checkConfig); return; } catch (RemoteException e) { diff --git a/services/java/com/android/server/am/BroadcastFilter.java b/services/java/com/android/server/am/BroadcastFilter.java index 2e784d3..b49bc22 100644 --- a/services/java/com/android/server/am/BroadcastFilter.java +++ b/services/java/com/android/server/am/BroadcastFilter.java @@ -25,12 +25,14 @@ import java.io.PrintWriter; class BroadcastFilter extends IntentFilter { // Back-pointer to the list this filter is in. final ReceiverList receiverList; + final String packageName; final String requiredPermission; BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList, - String _requiredPermission) { + String _packageName, String _requiredPermission) { super(_filter); receiverList = _receiverList; + packageName = _packageName; requiredPermission = _requiredPermission; } diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index ee6e420..8ed0cc1 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -177,13 +177,13 @@ class PendingIntentRecord extends IIntentSender.Stub { } public int send(int code, Intent intent, String resolvedType, - IIntentReceiver finishedReceiver) { + IIntentReceiver finishedReceiver, String requiredPermission) { return sendInner(code, intent, resolvedType, finishedReceiver, - null, null, 0, 0, 0); + requiredPermission, null, null, 0, 0, 0); } int sendInner(int code, Intent intent, String resolvedType, - IIntentReceiver finishedReceiver, + IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues) { synchronized(owner) { @@ -246,8 +246,8 @@ class PendingIntentRecord extends IIntentSender.Stub { // that the broadcast be delivered synchronously owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType, - finishedReceiver, code, null, null, null, - (finishedReceiver != null), false); + finishedReceiver, code, null, null, + requiredPermission, (finishedReceiver != null), false); sendFinish = false; } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index f5efda9..bb3ce28 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -40,9 +40,9 @@ import com.android.internal.R; import com.android.internal.net.VpnConfig; import com.android.server.ConnectivityService.VpnCallback; -import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charsets; +import java.util.Arrays; /** * @hide @@ -55,9 +55,8 @@ public class Vpn extends INetworkManagementEventObserver.Stub { private final Context mContext; private final VpnCallback mCallback; - private String mPackageName; - private String mInterfaceName; - + private String mPackage = VpnConfig.LEGACY_VPN; + private String mInterface; private LegacyVpnRunner mLegacyVpnRunner; public Vpn(Context context, VpnCallback callback) { @@ -66,74 +65,98 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } /** - * Prepare for a VPN application. + * Protect a socket from routing changes by binding it to the given + * interface. The socket IS closed by this method. * - * @param packageName The package name of the new VPN application. - * @return The name of the current prepared package. + * @param socket The socket to be bound. + * @param name The name of the interface. */ - public synchronized String prepare(String packageName) { - // Return the current prepared package if the new one is null. - if (packageName == null) { - return mPackageName; + public void protect(ParcelFileDescriptor socket, String interfaze) { + try { + mContext.enforceCallingPermission(VPN, "protect"); + jniProtect(socket.getFd(), interfaze); + } finally { + try { + socket.close(); + } catch (Exception e) { + // ignore + } } + } - // Check the permission of the caller. - PackageManager pm = mContext.getPackageManager(); - VpnConfig.enforceCallingPackage(pm.getNameForUid(Binder.getCallingUid())); + /** + * Prepare for a VPN application. This method is designed to solve + * race conditions. It first compares the current prepared package + * with {@code oldPackage}. If they are the same, the prepared + * package is revoked and replaced with {@code newPackage}. If + * {@code oldPackage} is {@code null}, the comparison is omitted. + * If {@code newPackage} is the same package or {@code null}, the + * revocation is omitted. This method returns {@code true} if the + * operation is succeeded. + * + * Legacy VPN is handled specially since it is not a real package. + * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and + * it can be revoked by itself. + * + * @param oldPackage The package name of the old VPN application. + * @param newPackage The package name of the new VPN application. + * @return true if the operation is succeeded. + */ + public synchronized boolean prepare(String oldPackage, String newPackage) { + // Return false if the package does not match. + if (oldPackage != null && !oldPackage.equals(mPackage)) { + return false; + } + + // Return true if we do not need to revoke. + if (newPackage == null || + (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) { + return true; + } + + // Only system user can revoke a package. + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Unauthorized Caller"); + } // Check the permission of the given package. - if (packageName.isEmpty()) { - packageName = null; - } else if (pm.checkPermission(VPN, packageName) != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException(packageName + " does not have " + VPN); + PackageManager pm = mContext.getPackageManager(); + if (!newPackage.equals(VpnConfig.LEGACY_VPN) && + pm.checkPermission(VPN, newPackage) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException(newPackage + " does not have " + VPN); } // Reset the interface and hide the notification. - if (mInterfaceName != null) { - nativeReset(mInterfaceName); + if (mInterface != null) { + jniReset(mInterface); mCallback.restore(); hideNotification(); - mInterfaceName = null; + mInterface = null; } - // Notify the package being revoked. - if (mPackageName != null) { + // Send out the broadcast or stop LegacyVpnRunner. + if (!mPackage.equals(VpnConfig.LEGACY_VPN)) { Intent intent = new Intent(VpnConfig.ACTION_VPN_REVOKED); - intent.setPackage(mPackageName); + intent.setPackage(mPackage); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcast(intent); + } else if (mLegacyVpnRunner != null) { + mLegacyVpnRunner.exit(); + mLegacyVpnRunner = null; } - Log.i(TAG, "Switched from " + mPackageName + " to " + packageName); - mPackageName = packageName; - return mPackageName; - } - - /** - * Protect a socket from routing changes by binding it to the given - * interface. The socket IS closed by this method. - * - * @param socket The socket to be bound. - * @param name The name of the interface. - */ - public void protect(ParcelFileDescriptor socket, String name) { - try { - mContext.enforceCallingPermission(VPN, "protect"); - nativeProtect(socket.getFd(), name); - } finally { - try { - socket.close(); - } catch (Exception e) { - // ignore - } - } + Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); + mPackage = newPackage; + return true; } /** - * Configure a TUN interface and return its file descriptor. + * Establish a VPN network and return the file descriptor of the VPN + * interface. This methods returns {@code null} if the application is + * revoked or not prepared. * - * @param configuration The parameters to configure the interface. - * @return The file descriptor of the interface. + * @param config The parameters to configure the network. + * @return The file descriptor of the VPN interface. */ public synchronized ParcelFileDescriptor establish(VpnConfig config) { // Check the permission of the caller. @@ -143,7 +166,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { PackageManager pm = mContext.getPackageManager(); ApplicationInfo app = null; try { - app = pm.getApplicationInfo(mPackageName, 0); + app = pm.getApplicationInfo(mPackage, 0); } catch (Exception e) { return null; } @@ -151,87 +174,88 @@ public class Vpn extends INetworkManagementEventObserver.Stub { return null; } - // Create and configure the interface. - ParcelFileDescriptor descriptor = ParcelFileDescriptor.adoptFd( - nativeEstablish(config.mtu, config.addresses, config.routes)); + // Load the label. + String label = app.loadLabel(pm).toString(); + + // Load the icon and convert it into a bitmap. + Drawable icon = app.loadIcon(pm); + Bitmap bitmap = null; + if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { + int width = mContext.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + int height = mContext.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_height); + icon.setBounds(0, 0, width, height); + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + icon.draw(new Canvas(bitmap)); + } - // Replace the interface and abort if it fails. + // Configure the interface. Abort if any of these steps fails. + ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd( + jniConfigure(config.mtu, config.addresses, config.routes)); try { - String interfaceName = nativeGetName(descriptor.getFd()); - - if (mInterfaceName != null && !mInterfaceName.equals(interfaceName)) { - nativeReset(mInterfaceName); + String interfaze = jniGetName(tun.getFd()); + if (mInterface != null && !mInterface.equals(interfaze)) { + jniReset(mInterface); } - mInterfaceName = interfaceName; + mInterface = interfaze; } catch (RuntimeException e) { try { - descriptor.close(); + tun.close(); } catch (Exception ex) { // ignore } throw e; } - String dnsServers = (config.dnsServers == null) ? "" : config.dnsServers.trim(); - mCallback.override(dnsServers.isEmpty() ? null : dnsServers.split(" ")); + // Override DNS servers and search domains. + mCallback.override(config.dnsServers, config.searchDomains); - config.packageName = mPackageName; - config.interfaceName = mInterfaceName; - showNotification(pm, app, config); - return descriptor; + // Fill more values. + config.packagz = mPackage; + config.interfaze = mInterface; + + // Show the notification! + showNotification(config, label, bitmap); + return tun; } // INetworkManagementEventObserver.Stub - public void interfaceStatusChanged(String name, boolean up) { + public void interfaceStatusChanged(String interfaze, boolean up) { } // INetworkManagementEventObserver.Stub - public void interfaceLinkStateChanged(String name, boolean up) { + public void interfaceLinkStateChanged(String interfaze, boolean up) { } // INetworkManagementEventObserver.Stub - public void interfaceAdded(String name) { + public void interfaceAdded(String interfaze) { } // INetworkManagementEventObserver.Stub - public synchronized void interfaceRemoved(String name) { - if (name.equals(mInterfaceName) && nativeCheck(name) == 0) { - hideNotification(); - mInterfaceName = null; + public synchronized void interfaceRemoved(String interfaze) { + if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { mCallback.restore(); + hideNotification(); + mInterface = null; } } - private void showNotification(PackageManager pm, ApplicationInfo app, VpnConfig config) { + private void showNotification(VpnConfig config, String label, Bitmap icon) { NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); if (nm != null) { - // Load the icon and convert it into a bitmap. - Drawable icon = app.loadIcon(pm); - Bitmap bitmap = null; - if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { - int width = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - int height = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_height); - icon.setBounds(0, 0, width, height); - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - icon.draw(new Canvas(bitmap)); - } + String title = (label == null) ? mContext.getString(R.string.vpn_title) : + mContext.getString(R.string.vpn_title_long, label); + String text = (config.session == null) ? mContext.getString(R.string.vpn_text) : + mContext.getString(R.string.vpn_text_long, config.session); - // Load the label. - String label = app.loadLabel(pm).toString(); - - // Build the notification. - String text = (config.sessionName == null) ? mContext.getString(R.string.vpn_text) : - mContext.getString(R.string.vpn_text_long, config.sessionName); long identity = Binder.clearCallingIdentity(); Notification notification = new Notification.Builder(mContext) .setSmallIcon(R.drawable.vpn_connected) - .setLargeIcon(bitmap) - .setTicker(mContext.getString(R.string.vpn_ticker, label)) - .setContentTitle(mContext.getString(R.string.vpn_title, label)) + .setLargeIcon(icon) + .setContentTitle(title) .setContentText(text) .setContentIntent(VpnConfig.getIntentForNotification(mContext, config)) .setDefaults(Notification.DEFAULT_ALL) @@ -253,34 +277,27 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } } - private native int nativeEstablish(int mtu, String addresses, String routes); - private native String nativeGetName(int fd); - private native void nativeReset(String name); - private native int nativeCheck(String name); - private native void nativeProtect(int fd, String name); + private native int jniConfigure(int mtu, String addresses, String routes); + private native String jniGetName(int tun); + private native void jniReset(String interfaze); + private native int jniCheck(String interfaze); + private native void jniProtect(int socket, String interfaze); /** - * Handle legacy VPN requests. This method stops the services and restart - * them if their arguments are not null. Heavy things are offloaded to - * another thread, so callers will not be blocked too long. + * Handle a legacy VPN request. This method stops the daemons and restart + * them if arguments are not null. Heavy things are offloaded to another + * thread, so callers will not be blocked for a long time. * + * @param config The parameters to configure the network. * @param raoocn The arguments to be passed to racoon. * @param mtpd The arguments to be passed to mtpd. */ - public synchronized void doLegacyVpn(String[] racoon, String[] mtpd) { - // Currently only system user is allowed. - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Unauthorized Caller"); - } - - // If the previous runner is still alive, interrupt it. - if (mLegacyVpnRunner != null && mLegacyVpnRunner.isAlive()) { - mLegacyVpnRunner.interrupt(); - } + public synchronized void doLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { + // Prepare for the new request. This also checks the caller. + prepare(null, VpnConfig.LEGACY_VPN); // Start a new runner and we are done! - mLegacyVpnRunner = new LegacyVpnRunner( - new String[] {"racoon", "mtpd"}, racoon, mtpd); + mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd); mLegacyVpnRunner.start(); } @@ -293,17 +310,28 @@ public class Vpn extends INetworkManagementEventObserver.Stub { */ private class LegacyVpnRunner extends Thread { private static final String TAG = "LegacyVpnRunner"; - private static final String NONE = "--"; - private final String[] mServices; + private final VpnConfig mConfig; + private final String[] mDaemons; private final String[][] mArguments; private long mTimer = -1; - public LegacyVpnRunner(String[] services, String[]... arguments) { + public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) { super(TAG); - mServices = services; - mArguments = arguments; + mConfig = config; + mDaemons = new String[] {"racoon", "mtpd"}; + mArguments = new String[][] {racoon, mtpd}; + + mConfig.packagz = VpnConfig.LEGACY_VPN; + } + + public void exit() { + // We assume that everything is reset after the daemons die. + for (String daemon : mDaemons) { + SystemProperties.set("ctl.stop", daemon); + } + interrupt(); } @Override @@ -311,9 +339,9 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Wait for the previous thread since it has been interrupted. Log.v(TAG, "wait"); synchronized (TAG) { - Log.v(TAG, "run"); + Log.v(TAG, "begin"); execute(); - Log.v(TAG, "exit"); + Log.v(TAG, "end"); } } @@ -325,7 +353,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } else if (now - mTimer <= 30000) { Thread.sleep(yield ? 200 : 1); } else { - throw new InterruptedException("timeout"); + throw new InterruptedException("time is up"); } } @@ -335,14 +363,14 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Initialize the timer. checkpoint(false); - // First stop the services. - for (String service : mServices) { - SystemProperties.set("ctl.stop", service); + // First stop the daemons. + for (String daemon : mDaemons) { + SystemProperties.set("ctl.stop", daemon); } - // Wait for the services to stop. - for (String service : mServices) { - String key = "init.svc." + service; + // Wait for the daemons to stop. + for (String daemon : mDaemons) { + String key = "init.svc." + daemon; while (!"stopped".equals(SystemProperties.get(key))) { checkpoint(true); } @@ -356,7 +384,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { checkpoint(true); } - // Check if we need to restart some services. + // Check if we need to restart any of the daemons. boolean restart = false; for (String[] arguments : mArguments) { restart = restart || (arguments != null); @@ -365,19 +393,19 @@ public class Vpn extends INetworkManagementEventObserver.Stub { return; } - // Start the service with arguments. - for (int i = 0; i < mServices.length; ++i) { + // Start the daemon with arguments. + for (int i = 0; i < mDaemons.length; ++i) { String[] arguments = mArguments[i]; if (arguments == null) { continue; } - // Start the service. - String service = mServices[i]; - SystemProperties.set("ctl.start", service); + // Start the daemon. + String daemon = mDaemons[i]; + SystemProperties.set("ctl.start", daemon); - // Wait for the service to start. - String key = "init.svc." + service; + // Wait for the daemon to start. + String key = "init.svc." + daemon; while (!"running".equals(SystemProperties.get(key))) { checkpoint(true); } @@ -385,7 +413,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Create the control socket. LocalSocket socket = new LocalSocket(); LocalSocketAddress address = new LocalSocketAddress( - service, LocalSocketAddress.Namespace.RESERVED); + daemon, LocalSocketAddress.Namespace.RESERVED); // Wait for the socket to connect. while (true) { @@ -400,22 +428,22 @@ public class Vpn extends INetworkManagementEventObserver.Stub { socket.setSoTimeout(500); // Send over the arguments. - OutputStream output = socket.getOutputStream(); + OutputStream out = socket.getOutputStream(); for (String argument : arguments) { byte[] bytes = argument.getBytes(Charsets.UTF_8); if (bytes.length >= 0xFFFF) { - throw new IllegalArgumentException("argument too large"); + throw new IllegalArgumentException("argument is too large"); } - output.write(bytes.length >> 8); - output.write(bytes.length); - output.write(bytes); + out.write(bytes.length >> 8); + out.write(bytes.length); + out.write(bytes); checkpoint(false); } // Send End-Of-Arguments. - output.write(0xFF); - output.write(0xFF); - output.flush(); + out.write(0xFF); + out.write(0xFF); + out.flush(); socket.close(); } @@ -426,26 +454,49 @@ public class Vpn extends INetworkManagementEventObserver.Stub { while (NONE.equals(SystemProperties.get("vpn.dns")) || NONE.equals(SystemProperties.get("vpn.via"))) { - // Check if a running service is dead. - for (int i = 0; i < mServices.length; ++i) { - String service = mServices[i]; + // Check if a running daemon is dead. + for (int i = 0; i < mDaemons.length; ++i) { + String daemon = mDaemons[i]; if (mArguments[i] != null && !"running".equals( - SystemProperties.get("init.svc." + service))) { - throw new IllegalArgumentException(service + " is dead"); + SystemProperties.get("init.svc." + daemon))) { + throw new IllegalArgumentException(daemon + " is dead"); } } checkpoint(true); } - // Great! Now we are connected! - Log.i(TAG, "connected!"); - // TODO: + // Now we are connected. Get the interface. + mConfig.interfaze = SystemProperties.get("vpn.via"); - } catch (Exception e) { - Log.i(TAG, e.getMessage()); - for (String service : mServices) { - SystemProperties.set("ctl.stop", service); + // Get the DNS servers if they are not set in config. + if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { + String dnsServers = SystemProperties.get("vpn.dns").trim(); + if (!dnsServers.isEmpty()) { + mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); + } } + + // TODO: support search domains from ISAKMP mode config. + + // The final step must be synchronized. + synchronized (Vpn.this) { + // Check if the thread is interrupted while we are waiting. + checkpoint(false); + + // Check if the interface is gone while we are waiting. + if (jniCheck(mConfig.interfaze) == 0) { + throw new IllegalStateException(mConfig.interfaze + " is gone"); + } + + // Now INetworkManagementEventObserver is watching our back. + mInterface = mConfig.interfaze; + mCallback.override(mConfig.dnsServers, mConfig.searchDomains); + showNotification(mConfig, null, null); + } + Log.i(TAG, "Connected!"); + } catch (Exception e) { + Log.i(TAG, "Abort because " + e.getMessage()); + exit(); } } } diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index 1f2ec2c..2a17cbe 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -1044,8 +1044,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // TODO: only dispatch when rules actually change - // record rule locally to dispatch to new listeners - mUidRules.put(uid, uidRules); + if (uidRules == RULE_ALLOW_ALL) { + mUidRules.delete(uid); + } else { + mUidRules.put(uid, uidRules); + } final boolean rejectMetered = (uidRules & RULE_REJECT_METERED) != 0; setUidNetworkRules(uid, rejectMetered); diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 7610a11..b4bd176 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -134,7 +134,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * Settings that can be changed externally. */ public interface NetworkStatsSettings { - public boolean getEnabled(); public long getPollInterval(); public long getPersistThreshold(); public long getNetworkBucketDuration(); @@ -207,20 +206,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } public void systemReady() { - if (mSettings.getEnabled()) { - try { - // enable low-level bandwidth stats and control - // TODO: consider shipping with this enabled by default - mNetworkManager.setBandwidthControlEnabled(true); - } catch (RemoteException e) { - Slog.e(TAG, "problem talking to netd while enabling bandwidth controls", e); - } catch (NativeDaemonConnectorException ndce) { - Slog.e(TAG, "problem enabling bandwidth controls", ndce); - } - } else { - Slog.w(TAG, "detailed network stats disabled"); - } - synchronized (mStatsLock) { // read historical network stats from disk, since policy service // might need them right away. we delay loading detailed UID stats @@ -389,6 +374,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } + @Override + public void forceUpdate() { + mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + + synchronized (mStatsLock) { + performPollLocked(true, false); + } + } + /** * Receiver that watches for {@link IConnectivityManager} to claim network * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()} @@ -905,6 +899,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { argSet.add(arg); } + final boolean fullHistory = argSet.contains("full"); + synchronized (mStatsLock) { // TODO: remove this testing code, since it corrupts stats if (argSet.contains("generate")) { @@ -930,7 +926,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { for (NetworkIdentitySet ident : mNetworkStats.keySet()) { final NetworkStatsHistory history = mNetworkStats.get(ident); pw.print(" ident="); pw.println(ident.toString()); - history.dump(" ", pw); + history.dump(" ", pw, fullHistory); } if (argSet.contains("detail")) { @@ -950,7 +946,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStatsHistory history = uidStats.valueAt(i); pw.print(" UID="); pw.print(uid); pw.print(" tag="); pw.println(tag); - history.dump(" ", pw); + history.dump(" ", pw, fullHistory); } } } @@ -1058,15 +1054,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return Settings.Secure.getLong(mResolver, name, def); } - public boolean getEnabled() { - if (!new File("/proc/net/xt_qtaguid/ctrl").exists()) { - Slog.w(TAG, "kernel does not support bandwidth control"); - return false; - } - // TODO: once things stabilize, enable by default. - // For now: ./vendor/google/tools/override-gservices secure:netstats_enabled=1 - return Settings.Secure.getInt(mResolver, NETSTATS_ENABLED, 0) != 0; - } public long getPollInterval() { return getSecureLong(NETSTATS_POLL_INTERVAL, 15 * MINUTE_IN_MILLIS); } diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 22e2dde..ea5d26b 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -157,7 +157,6 @@ public class PackageManagerService extends IPackageManager.Stub { private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; - private static final int KEYCHAIN_UID = Process.KEYCHAIN_UID; static final int FIRST_APPLICATION_UID = Process.FIRST_APPLICATION_UID; static final int MAX_APPLICATION_UIDS = 1000; @@ -761,10 +760,6 @@ public class PackageManagerService extends IPackageManager.Stub { MULTIPLE_APPLICATION_UIDS ? NFC_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.keychain", - MULTIPLE_APPLICATION_UIDS - ? KEYCHAIN_UID : FIRST_APPLICATION_UID, - ApplicationInfo.FLAG_SYSTEM); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index 918f1b6..13a76b3 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -45,7 +45,6 @@ import android.os.storage.StorageVolume; import android.os.SystemProperties; import android.os.UEventObserver; import android.provider.Settings; -import android.util.Log; import android.util.Slog; import java.io.File; @@ -62,7 +61,7 @@ import java.util.List; public class UsbDeviceManager { private static final String TAG = UsbDeviceManager.class.getSimpleName(); - private static final boolean LOG = false; + private static final boolean DEBUG = false; private static final String USB_STATE_MATCH = "DEVPATH=/devices/virtual/android_usb/android0"; @@ -93,18 +92,9 @@ public class UsbDeviceManager { private final UsbSettingsManager mSettingsManager; private NotificationManager mNotificationManager; private final boolean mHasUsbAccessory; - - // for USB connected notification - private boolean mUsbNotificationShown; private boolean mUseUsbNotification; - private Notification mUsbNotification; - - // for adb connected notification - private boolean mAdbNotificationShown; - private Notification mAdbNotification; private boolean mAdbEnabled; - private class AdbSettingsObserver extends ContentObserver { public AdbSettingsObserver() { super(null); @@ -117,115 +107,20 @@ public class UsbDeviceManager { } } - private void updateUsbNotification(boolean connected) { - if (mNotificationManager == null || !mUseUsbNotification) return; - if (connected) { - if (!mUsbNotificationShown) { - Resources r = mContext.getResources(); - CharSequence title = r.getText( - com.android.internal.R.string.usb_preferences_notification_title); - CharSequence message = r.getText( - com.android.internal.R.string.usb_preferece_notification_message); - - if (mUsbNotification == null) { - mUsbNotification = new Notification(); - mUsbNotification.icon = com.android.internal.R.drawable.stat_sys_data_usb; - mUsbNotification.when = 0; - mUsbNotification.flags = Notification.FLAG_ONGOING_EVENT; - mUsbNotification.tickerText = title; - mUsbNotification.defaults = 0; // please be quiet - mUsbNotification.sound = null; - mUsbNotification.vibrate = null; - } - - Intent intent = new Intent(); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - intent.setClassName("com.android.systemui", - "com.android.systemui.usb.UsbPreferenceActivity"); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, - intent, 0); - - mUsbNotification.setLatestEventInfo(mContext, title, message, pi); - - mUsbNotificationShown = true; - mNotificationManager.notify( - com.android.internal.R.string.usb_preferences_notification_title, - mUsbNotification); - } - - } else if (mUsbNotificationShown) { - mUsbNotificationShown = false; - mNotificationManager.cancel( - com.android.internal.R.string.usb_preferences_notification_title); - } - } - - private void updateAdbNotification(boolean adbEnabled) { - if (mNotificationManager == null) return; - if (adbEnabled) { - if ("0".equals(SystemProperties.get("persist.adb.notify"))) return; - - if (!mAdbNotificationShown) { - Resources r = mContext.getResources(); - CharSequence title = r.getText( - com.android.internal.R.string.adb_active_notification_title); - CharSequence message = r.getText( - com.android.internal.R.string.adb_active_notification_message); - - if (mAdbNotification == null) { - mAdbNotification = new Notification(); - mAdbNotification.icon = com.android.internal.R.drawable.stat_sys_adb; - mAdbNotification.when = 0; - mAdbNotification.flags = Notification.FLAG_ONGOING_EVENT; - mAdbNotification.tickerText = title; - mAdbNotification.defaults = 0; // please be quiet - mAdbNotification.sound = null; - mAdbNotification.vibrate = null; - } - - Intent intent = new Intent( - Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - // Note: we are hard-coding the component because this is - // an important security UI that we don't want anyone - // intercepting. - intent.setComponent(new ComponentName("com.android.settings", - "com.android.settings.DevelopmentSettings")); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, - intent, 0); - - mAdbNotification.setLatestEventInfo(mContext, title, message, pi); - - mAdbNotificationShown = true; - mNotificationManager.notify( - com.android.internal.R.string.adb_active_notification_title, - mAdbNotification); - } - } else if (mAdbNotificationShown) { - mAdbNotificationShown = false; - mNotificationManager.cancel( - com.android.internal.R.string.adb_active_notification_title); - } - } - /* * Listens for uevent messages from the kernel to monitor the USB state */ private final UEventObserver mUEventObserver = new UEventObserver() { @Override public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "USB UEVENT: " + event.toString()); - } + if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString()); String state = event.get("USB_STATE"); String accessory = event.get("ACCESSORY"); if (state != null) { mHandler.updateState(state); } else if ("START".equals(accessory)) { - Slog.d(TAG, "got accessory start"); + if (DEBUG) Slog.d(TAG, "got accessory start"); setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false); } } @@ -319,16 +214,32 @@ public class UsbDeviceManager { private String mDefaultFunctions; private UsbAccessory mCurrentAccessory; private boolean mDeferAccessoryAttached; + private int mUsbNotificationId; + private boolean mAdbNotificationShown; + + private static final int NOTIFICATION_NONE = 0; + private static final int NOTIFICATION_MTP = 1; + private static final int NOTIFICATION_PTP = 2; + private static final int NOTIFICATION_INSTALLER = 3; + private static final int NOTIFICATION_ADB = 4; public UsbHandler() { - // Read initial USB state try { + // sanity check the sys.usb.config system property + // this may be necessary if we crashed while switching USB configurations + String config = SystemProperties.get("sys.usb.config", "none"); + if (config.equals("none")) { + String persistConfig = SystemProperties.get("persist.sys.usb.config", "none"); + Slog.w(TAG, "resetting config to persistent property: " + persistConfig); + SystemProperties.set("sys.usb.config", persistConfig); + } + + // Read initial USB state mCurrentFunctions = FileUtils.readTextFile( new File(FUNCTIONS_PATH), 0, null).trim(); mDefaultFunctions = mCurrentFunctions; String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim(); updateState(state); - mAdbEnabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB); // Upgrade step for previous versions that used persist.service.adb.enable @@ -414,12 +325,12 @@ public class UsbDeviceManager { } catch (InterruptedException e) { } } - Log.e(TAG, "waitForState(" + state + ") FAILED"); + Slog.e(TAG, "waitForState(" + state + ") FAILED"); return false; } private boolean setUsbConfig(String config) { - Log.d(TAG, "setUsbConfig(" + config + ")"); + if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")"); // set the new configuration SystemProperties.set("sys.usb.config", config); return waitForState(config); @@ -428,7 +339,7 @@ public class UsbDeviceManager { private void doSetCurrentFunctions(String functions) { if (!mCurrentFunctions.equals(functions)) { if (!setUsbConfig("none") || !setUsbConfig(functions)) { - Log.e(TAG, "Failed to switch USB configuration to " + functions); + Slog.e(TAG, "Failed to switch USB configuration to " + functions); // revert to previous configuration if we fail setUsbConfig(mCurrentFunctions); } else { @@ -438,6 +349,7 @@ public class UsbDeviceManager { } private void setAdbEnabled(boolean enable) { + if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable); if (enable != mAdbEnabled) { mAdbEnabled = enable; String functions; @@ -449,7 +361,7 @@ public class UsbDeviceManager { functions = removeFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB); } setCurrentFunction(functions, true); - updateAdbNotification(mAdbEnabled && mConnected); + updateAdbNotification(); } } @@ -469,7 +381,7 @@ public class UsbDeviceManager { String[] strings = nativeGetAccessoryStrings(); if (strings != null) { mCurrentAccessory = new UsbAccessory(strings); - Log.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); + Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); // defer accessoryAttached if system is not ready if (mSystemReady) { mSettingsManager.accessoryAttached(mCurrentAccessory); @@ -477,12 +389,12 @@ public class UsbDeviceManager { mDeferAccessoryAttached = true; } } else { - Log.e(TAG, "nativeGetAccessoryStrings failed"); + Slog.e(TAG, "nativeGetAccessoryStrings failed"); } } else if (!mConnected) { // make sure accessory mode is off // and restore default functions - Log.d(TAG, "exited USB accessory mode"); + Slog.d(TAG, "exited USB accessory mode"); setEnabledFunctions(mDefaultFunctions); if (mCurrentAccessory != null) { @@ -517,8 +429,8 @@ public class UsbDeviceManager { case MSG_UPDATE_STATE: mConnected = (msg.arg1 == 1); mConfigured = (msg.arg2 == 1); - updateUsbNotification(mConnected); - updateAdbNotification(mAdbEnabled && mConnected); + updateUsbNotification(); + updateAdbNotification(); if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ACCESSORY)) { updateCurrentAccessory(); @@ -562,8 +474,8 @@ public class UsbDeviceManager { } break; case MSG_SYSTEM_READY: - updateUsbNotification(mConnected); - updateAdbNotification(mAdbEnabled && mConnected); + updateUsbNotification(); + updateAdbNotification(); updateUsbState(); if (mCurrentAccessory != null && mDeferAccessoryAttached) { mSettingsManager.accessoryAttached(mCurrentAccessory); @@ -576,6 +488,105 @@ public class UsbDeviceManager { return mCurrentAccessory; } + private void updateUsbNotification() { + if (mNotificationManager == null || !mUseUsbNotification) return; + if (mConnected) { + Resources r = mContext.getResources(); + CharSequence title = null; + int id = NOTIFICATION_NONE; + if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)) { + title = r.getText( + com.android.internal.R.string.usb_mtp_notification_title); + id = NOTIFICATION_MTP; + } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP)) { + title = r.getText( + com.android.internal.R.string.usb_ptp_notification_title); + id = NOTIFICATION_PTP; + } else if (containsFunction(mCurrentFunctions, + UsbManager.USB_FUNCTION_MASS_STORAGE)) { + title = r.getText( + com.android.internal.R.string.usb_cd_installer_notification_title); + id = NOTIFICATION_INSTALLER; + } else { + Slog.e(TAG, "No known USB function in updateUsbNotification"); + } + if (id != mUsbNotificationId) { + // clear notification if title needs changing + if (mUsbNotificationId != NOTIFICATION_NONE) { + mNotificationManager.cancel(mUsbNotificationId); + mUsbNotificationId = NOTIFICATION_NONE; + } + } + if (mUsbNotificationId == NOTIFICATION_NONE) { + CharSequence message = r.getText( + com.android.internal.R.string.usb_notification_message); + + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_sys_data_usb; + notification.when = 0; + notification.flags = Notification.FLAG_ONGOING_EVENT; + notification.tickerText = title; + notification.defaults = 0; // please be quiet + notification.sound = null; + notification.vibrate = null; + + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + intent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbPreferenceActivity"); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, + intent, 0); + notification.setLatestEventInfo(mContext, title, message, pi); + mNotificationManager.notify(id, notification); + mUsbNotificationId = id; + } + + } else if (mUsbNotificationId != NOTIFICATION_NONE) { + mNotificationManager.cancel(mUsbNotificationId); + mUsbNotificationId = NOTIFICATION_NONE; + } + } + + private void updateAdbNotification() { + if (mNotificationManager == null) return; + if (mAdbEnabled && mConnected) { + if ("0".equals(SystemProperties.get("persist.adb.notify"))) return; + + if (!mAdbNotificationShown) { + Resources r = mContext.getResources(); + CharSequence title = r.getText( + com.android.internal.R.string.adb_active_notification_title); + CharSequence message = r.getText( + com.android.internal.R.string.adb_active_notification_message); + + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_sys_adb; + notification.when = 0; + notification.flags = Notification.FLAG_ONGOING_EVENT; + notification.tickerText = title; + notification.defaults = 0; // please be quiet + notification.sound = null; + notification.vibrate = null; + + Intent intent = new Intent( + Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + intent.setComponent(new ComponentName("com.android.settings", + "com.android.settings.DevelopmentSettings")); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, + intent, 0); + notification.setLatestEventInfo(mContext, title, message, pi); + mAdbNotificationShown = true; + mNotificationManager.notify(NOTIFICATION_ADB, notification); + } + } else if (mAdbNotificationShown) { + mAdbNotificationShown = false; + mNotificationManager.cancel(NOTIFICATION_ADB); + } + } + public void dump(FileDescriptor fd, PrintWriter pw) { pw.println(" USB Device State:"); pw.println(" Current Functions: " + mCurrentFunctions); @@ -608,6 +619,7 @@ public class UsbDeviceManager { } public void setCurrentFunction(String function, boolean makeDefault) { + if (DEBUG) Slog.d(TAG, "setCurrentFunction(" + function + ") default: " + makeDefault); mHandler.sendMessage(MSG_SET_CURRENT_FUNCTION, function, makeDefault); } diff --git a/services/java/com/android/server/usb/UsbHostManager.java b/services/java/com/android/server/usb/UsbHostManager.java index 923b049..0a0ff59 100644 --- a/services/java/com/android/server/usb/UsbHostManager.java +++ b/services/java/com/android/server/usb/UsbHostManager.java @@ -37,7 +37,6 @@ import android.os.Parcelable; import android.os.ParcelFileDescriptor; import android.os.UEventObserver; import android.provider.Settings; -import android.util.Log; import android.util.Slog; import java.io.File; @@ -112,7 +111,7 @@ public class UsbHostManager { synchronized (mLock) { if (mDevices.get(deviceName) != null) { - Log.w(TAG, "device already on mDevices list: " + deviceName); + Slog.w(TAG, "device already on mDevices list: " + deviceName); return; } @@ -148,7 +147,7 @@ public class UsbHostManager { } catch (Exception e) { // beware of index out of bound exceptions, which might happen if // a device does not set bNumEndpoints correctly - Log.e(TAG, "error parsing USB descriptors", e); + Slog.e(TAG, "error parsing USB descriptors", e); return; } diff --git a/services/java/com/android/server/usb/UsbSettingsManager.java b/services/java/com/android/server/usb/UsbSettingsManager.java index 9113677..0baafbb 100644 --- a/services/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/java/com/android/server/usb/UsbSettingsManager.java @@ -35,7 +35,7 @@ import android.hardware.usb.UsbManager; import android.os.Binder; import android.os.FileUtils; import android.os.Process; -import android.util.Log; +import android.util.Slog; import android.util.SparseBooleanArray; import android.util.Xml; @@ -62,6 +62,7 @@ import java.util.List; class UsbSettingsManager { private static final String TAG = "UsbSettingsManager"; + private static final boolean DEBUG = false; private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml"); private final Context mContext; @@ -410,9 +411,9 @@ class UsbSettingsManager { } } } catch (FileNotFoundException e) { - Log.w(TAG, "settings file not found"); + if (DEBUG) Slog.d(TAG, "settings file not found"); } catch (Exception e) { - Log.e(TAG, "error reading settings file, deleting to start fresh", e); + Slog.e(TAG, "error reading settings file, deleting to start fresh", e); sSettingsFile.delete(); } finally { if (stream != null) { @@ -428,7 +429,7 @@ class UsbSettingsManager { FileOutputStream fos = null; try { FileOutputStream fstr = new FileOutputStream(sSettingsFile); - Log.d(TAG, "writing settings to " + fstr); + if (DEBUG) Slog.d(TAG, "writing settings to " + fstr); BufferedOutputStream str = new BufferedOutputStream(fstr); FastXmlSerializer serializer = new FastXmlSerializer(); serializer.setOutput(str, "utf-8"); @@ -457,7 +458,7 @@ class UsbSettingsManager { FileUtils.sync(fstr); str.close(); } catch (Exception e) { - Log.e(TAG, "error writing settings file, deleting to start fresh", e); + Slog.e(TAG, "error writing settings file, deleting to start fresh", e); sSettingsFile.delete(); } } @@ -472,7 +473,7 @@ class UsbSettingsManager { try { parser = ai.loadXmlMetaData(mPackageManager, metaDataName); if (parser == null) { - Log.w(TAG, "no meta-data for " + info); + Slog.w(TAG, "no meta-data for " + info); return false; } @@ -494,7 +495,7 @@ class UsbSettingsManager { XmlUtils.nextElement(parser); } } catch (Exception e) { - Log.w(TAG, "Unable to load component info " + info.toString(), e); + Slog.w(TAG, "Unable to load component info " + info.toString(), e); } finally { if (parser != null) parser.close(); } @@ -553,7 +554,7 @@ class UsbSettingsManager { Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); intent.putExtra(UsbManager.EXTRA_DEVICE, device); - Log.d(TAG, "usbDeviceRemoved, sending " + intent); + if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent); mContext.sendBroadcast(intent); } @@ -604,7 +605,7 @@ class UsbSettingsManager { try { mContext.startActivity(dialogIntent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "unable to start UsbAccessoryUriActivity"); + Slog.e(TAG, "unable to start UsbAccessoryUriActivity"); } } } @@ -652,7 +653,7 @@ class UsbSettingsManager { defaultRI.activityInfo.name)); mContext.startActivity(intent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "startActivity failed", e); + Slog.e(TAG, "startActivity failed", e); } } else { Intent resolverIntent = new Intent(); @@ -679,7 +680,7 @@ class UsbSettingsManager { try { mContext.startActivity(resolverIntent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "unable to start activity " + resolverIntent); + Slog.e(TAG, "unable to start activity " + resolverIntent); } } } @@ -733,7 +734,7 @@ class UsbSettingsManager { XmlUtils.nextElement(parser); } } catch (Exception e) { - Log.w(TAG, "Unable to load component info " + aInfo.toString(), e); + Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e); } finally { if (parser != null) parser.close(); } @@ -751,7 +752,7 @@ class UsbSettingsManager { info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); } catch (NameNotFoundException e) { - Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e); + Slog.e(TAG, "handlePackageUpdate could not find package " + packageName, e); return; } @@ -831,7 +832,7 @@ class UsbSettingsManager { try { mContext.startActivity(intent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "unable to start UsbPermissionActivity"); + Slog.e(TAG, "unable to start UsbPermissionActivity"); } finally { Binder.restoreCallingIdentity(identity); } @@ -847,7 +848,7 @@ class UsbSettingsManager { try { pi.send(mContext, 0, intent); } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "requestPermission PendingIntent was cancelled"); + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); } return; } @@ -867,7 +868,7 @@ class UsbSettingsManager { try { pi.send(mContext, 0, intent); } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "requestPermission PendingIntent was cancelled"); + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); } return; } diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java index ee69311..65007f9 100644 --- a/services/java/com/android/server/wm/InputManager.java +++ b/services/java/com/android/server/wm/InputManager.java @@ -551,18 +551,6 @@ public class InputManager { android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED; } - - @SuppressWarnings("unused") - public boolean filterTouchEvents() { - return mContext.getResources().getBoolean( - com.android.internal.R.bool.config_filterTouchEvents); - } - - @SuppressWarnings("unused") - public boolean filterJumpyTouchEvents() { - return mContext.getResources().getBoolean( - com.android.internal.R.bool.config_filterJumpyTouchEvents); - } @SuppressWarnings("unused") public int getVirtualKeyQuietTimeMillis() { diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 8a46ab0..14a4109 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -69,8 +69,6 @@ static struct { jmethodID interceptKeyBeforeDispatching; jmethodID dispatchUnhandledKey; jmethodID checkInjectEventsPermission; - jmethodID filterTouchEvents; - jmethodID filterJumpyTouchEvents; jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; jmethodID getKeyRepeatTimeout; @@ -381,18 +379,6 @@ bool NativeInputManager::getDisplayInfo(int32_t displayId, void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) { JNIEnv* env = jniEnv(); - jboolean filterTouchEvents = env->CallBooleanMethod(mCallbacksObj, - gCallbacksClassInfo.filterTouchEvents); - if (!checkAndClearExceptionFromCallback(env, "filterTouchEvents")) { - outConfig->filterTouchEvents = filterTouchEvents; - } - - jboolean filterJumpyTouchEvents = env->CallBooleanMethod(mCallbacksObj, - gCallbacksClassInfo.filterJumpyTouchEvents); - if (!checkAndClearExceptionFromCallback(env, "filterJumpyTouchEvents")) { - outConfig->filterJumpyTouchEvents = filterJumpyTouchEvents; - } - jint virtualKeyQuietTime = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getVirtualKeyQuietTimeMillis); if (!checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) { @@ -1405,12 +1391,6 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, clazz, "checkInjectEventsPermission", "(II)Z"); - GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, clazz, - "filterTouchEvents", "()Z"); - - GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, clazz, - "filterJumpyTouchEvents", "()Z"); - GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyQuietTimeMillis, clazz, "getVirtualKeyQuietTimeMillis", "()I"); diff --git a/services/jni/com_android_server_connectivity_Vpn.cpp b/services/jni/com_android_server_connectivity_Vpn.cpp index ae7fbfe..5f920f1 100644 --- a/services/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/jni/com_android_server_connectivity_Vpn.cpp @@ -42,6 +42,9 @@ namespace android { +static int inet4 = -1; +static int inet6 = -1; + static inline in_addr_t *as_in_addr(sockaddr *sa) { return &((sockaddr_in *)sa)->sin_addr.s_addr; } @@ -51,11 +54,9 @@ static inline in_addr_t *as_in_addr(sockaddr *sa) { #define SYSTEM_ERROR -1 #define BAD_ARGUMENT -2 -static int create_interface(int mtu, char *name, int *index) +static int create_interface(char *name, int *index, int mtu) { - int tun = open("/dev/tun", O_RDWR); - int inet4 = socket(AF_INET, SOCK_DGRAM, 0); - int flags; + int tun = open("/dev/tun", O_RDWR | O_NONBLOCK); ifreq ifr4; memset(&ifr4, 0, sizeof(ifr4)); @@ -87,32 +88,20 @@ static int create_interface(int mtu, char *name, int *index) goto error; } - // Make it non-blocking. - flags = fcntl(tun, F_GETFL, 0); - if (flags == -1 || fcntl(tun, F_SETFL, flags | O_NONBLOCK)) { - LOGE("Cannot set non-blocking on %s: %s", ifr4.ifr_name, strerror(errno)); - goto error; - } - - strcpy(name, ifr4.ifr_name); + strncpy(name, ifr4.ifr_name, IFNAMSIZ); *index = ifr4.ifr_ifindex; - close(inet4); return tun; error: close(tun); - close(inet4); return SYSTEM_ERROR; } static int set_addresses(const char *name, int index, const char *addresses) { - int inet4 = socket(AF_INET, SOCK_DGRAM, 0); - int inet6 = socket(AF_INET6, SOCK_DGRAM, 0); - ifreq ifr4; memset(&ifr4, 0, sizeof(ifr4)); - strcpy(ifr4.ifr_name, name); + strncpy(ifr4.ifr_name, name, IFNAMSIZ); ifr4.ifr_addr.sa_family = AF_INET; in6_ifreq ifr6; @@ -121,7 +110,6 @@ static int set_addresses(const char *name, int index, const char *addresses) char address[65]; int prefix; - int chars; int count = 0; @@ -164,7 +152,7 @@ static int set_addresses(const char *name, int index, const char *addresses) break; } } - LOGV("Address added on %s: %s/%d", name, address, prefix); + LOGD("Address added on %s: %s/%d", name, address, prefix); ++count; } @@ -177,16 +165,11 @@ static int set_addresses(const char *name, int index, const char *addresses) count = BAD_ARGUMENT; } - close(inet4); - close(inet6); return count; } static int set_routes(const char *name, int index, const char *routes) { - int inet4 = socket(AF_INET, SOCK_DGRAM, 0); - int inet6 = socket(AF_INET6, SOCK_DGRAM, 0); - rtentry rt4; memset(&rt4, 0, sizeof(rt4)); rt4.rt_dev = (char *)name; @@ -201,7 +184,6 @@ static int set_routes(const char *name, int index, const char *routes) char address[65]; int prefix; - int chars; int count = 0; @@ -211,32 +193,50 @@ static int set_routes(const char *name, int index, const char *routes) if (strchr(address, ':')) { // Add an IPv6 route. if (inet_pton(AF_INET6, address, &rt6.rtmsg_dst) != 1 || - prefix < 1 || prefix > 128) { + prefix < 0 || prefix > 128) { count = BAD_ARGUMENT; break; } - rt6.rtmsg_dst_len = prefix; + rt6.rtmsg_dst_len = prefix ? prefix : 1; if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) { count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; break; } + + if (!prefix) { + // Split the route instead of replacing the default route. + rt6.rtmsg_dst.s6_addr[0] ^= 0x80; + if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) { + count = SYSTEM_ERROR; + break; + } + } } else { // Add an IPv4 route. if (inet_pton(AF_INET, address, as_in_addr(&rt4.rt_dst)) != 1 || - prefix < 1 || prefix > 32) { + prefix < 0 || prefix > 32) { count = BAD_ARGUMENT; break; } - in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0; + in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0x80000000; *as_in_addr(&rt4.rt_genmask) = htonl(mask); if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; break; } + + if (!prefix) { + // Split the route instead of replacing the default route. + *as_in_addr(&rt4.rt_dst) ^= htonl(0x80000000); + if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { + count = SYSTEM_ERROR; + break; + } + } } - LOGV("Route added on %s: %s/%d", name, address, prefix); + LOGD("Route added on %s: %s/%d", name, address, prefix); ++count; } @@ -250,8 +250,6 @@ static int set_routes(const char *name, int index, const char *routes) count = BAD_ARGUMENT; } - close(inet4); - close(inet6); return count; } @@ -262,31 +260,25 @@ static int get_interface_name(char *name, int tun) LOGE("Cannot get interface name: %s", strerror(errno)); return SYSTEM_ERROR; } - strcpy(name, ifr4.ifr_name); + strncpy(name, ifr4.ifr_name, IFNAMSIZ); return 0; } static int reset_interface(const char *name) { - int inet4 = socket(AF_INET, SOCK_DGRAM, 0); - ifreq ifr4; - ifr4.ifr_flags = 0; strncpy(ifr4.ifr_name, name, IFNAMSIZ); + ifr4.ifr_flags = 0; if (ioctl(inet4, SIOCSIFFLAGS, &ifr4) && errno != ENODEV) { LOGE("Cannot reset %s: %s", name, strerror(errno)); - close(inet4); return SYSTEM_ERROR; } - close(inet4); return 0; } static int check_interface(const char *name) { - int inet4 = socket(AF_INET, SOCK_DGRAM, 0); - ifreq ifr4; strncpy(ifr4.ifr_name, name, IFNAMSIZ); ifr4.ifr_flags = 0; @@ -294,13 +286,12 @@ static int check_interface(const char *name) if (ioctl(inet4, SIOCGIFFLAGS, &ifr4) && errno != ENODEV) { LOGE("Cannot check %s: %s", name, strerror(errno)); } - close(inet4); return ifr4.ifr_flags; } -static int bind_to_interface(int fd, const char *name) +static int bind_to_interface(int socket, const char *name) { - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) { + if (setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) { LOGE("Cannot bind socket to %s: %s", name, strerror(errno)); return SYSTEM_ERROR; } @@ -318,23 +309,22 @@ static void throwException(JNIEnv *env, int error, const char *message) } } -static jint establish(JNIEnv *env, jobject thiz, +static jint configure(JNIEnv *env, jobject thiz, jint mtu, jstring jAddresses, jstring jRoutes) { char name[IFNAMSIZ]; int index; - int tun = create_interface(mtu, name, &index); + int tun = create_interface(name, &index, mtu); if (tun < 0) { throwException(env, tun, "Cannot create interface"); return -1; } - LOGD("%s is created", name); - const char *addresses; - const char *routes; + const char *addresses = NULL; + const char *routes = NULL; int count; - // Addresses are required. + // At least one address must be set. addresses = jAddresses ? env->GetStringUTFChars(jAddresses, NULL) : NULL; if (!addresses) { jniThrowNullPointerException(env, "address"); @@ -348,7 +338,7 @@ static jint establish(JNIEnv *env, jobject thiz, } LOGD("Configured %d address(es) on %s", count, name); - // Routes are optional. + // On the contrary, routes are optional. routes = jRoutes ? env->GetStringUTFChars(jRoutes, NULL) : NULL; if (routes) { count = set_routes(name, index, routes); @@ -368,10 +358,10 @@ error: return -1; } -static jstring getName(JNIEnv *env, jobject thiz, jint fd) +static jstring getName(JNIEnv *env, jobject thiz, jint tun) { char name[IFNAMSIZ]; - if (get_interface_name(name, fd) < 0) { + if (get_interface_name(name, tun) < 0) { throwException(env, SYSTEM_ERROR, "Cannot get interface name"); return NULL; } @@ -380,24 +370,20 @@ static jstring getName(JNIEnv *env, jobject thiz, jint fd) static void reset(JNIEnv *env, jobject thiz, jstring jName) { - const char *name = jName ? - env->GetStringUTFChars(jName, NULL) : NULL; + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; if (!name) { jniThrowNullPointerException(env, "name"); return; } if (reset_interface(name) < 0) { throwException(env, SYSTEM_ERROR, "Cannot reset interface"); - } else { - LOGD("%s is deactivated", name); } env->ReleaseStringUTFChars(jName, name); } static jint check(JNIEnv *env, jobject thiz, jstring jName) { - const char *name = jName ? - env->GetStringUTFChars(jName, NULL) : NULL; + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; if (!name) { jniThrowNullPointerException(env, "name"); return 0; @@ -407,15 +393,14 @@ static jint check(JNIEnv *env, jobject thiz, jstring jName) return flags; } -static void protect(JNIEnv *env, jobject thiz, jint fd, jstring jName) +static void protect(JNIEnv *env, jobject thiz, jint socket, jstring jName) { - const char *name = jName ? - env->GetStringUTFChars(jName, NULL) : NULL; + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; if (!name) { jniThrowNullPointerException(env, "name"); return; } - if (bind_to_interface(fd, name) < 0) { + if (bind_to_interface(socket, name) < 0) { throwException(env, SYSTEM_ERROR, "Cannot protect socket"); } env->ReleaseStringUTFChars(jName, name); @@ -424,15 +409,21 @@ static void protect(JNIEnv *env, jobject thiz, jint fd, jstring jName) //------------------------------------------------------------------------------ static JNINativeMethod gMethods[] = { - {"nativeEstablish", "(ILjava/lang/String;Ljava/lang/String;)I", (void *)establish}, - {"nativeGetName", "(I)Ljava/lang/String;", (void *)getName}, - {"nativeReset", "(Ljava/lang/String;)V", (void *)reset}, - {"nativeCheck", "(Ljava/lang/String;)I", (void *)check}, - {"nativeProtect", "(ILjava/lang/String;)V", (void *)protect}, + {"jniConfigure", "(ILjava/lang/String;Ljava/lang/String;)I", (void *)configure}, + {"jniGetName", "(I)Ljava/lang/String;", (void *)getName}, + {"jniReset", "(Ljava/lang/String;)V", (void *)reset}, + {"jniCheck", "(Ljava/lang/String;)I", (void *)check}, + {"jniProtect", "(ILjava/lang/String;)V", (void *)protect}, }; int register_android_server_connectivity_Vpn(JNIEnv *env) { + if (inet4 == -1) { + inet4 = socket(AF_INET, SOCK_DGRAM, 0); + } + if (inet6 == -1) { + inet6 = socket(AF_INET6, SOCK_DGRAM, 0); + } return jniRegisterNativeMethods(env, "com/android/server/connectivity/Vpn", gMethods, NELEM(gMethods)); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 1c57bc1..685613e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1799,10 +1799,10 @@ status_t SurfaceFlinger::electronBeamOffAnimationImplLocked() const GLfloat h = hw_h - (hw_h * v); const GLfloat x = (hw_w - w) * 0.5f; const GLfloat y = (hw_h - h) * 0.5f; - vtx[0] = x; vtx[1] = y; - vtx[2] = x; vtx[3] = y + h; - vtx[4] = x + w; vtx[5] = y + h; - vtx[6] = x + w; vtx[7] = y; + vtx[0] = x; vtx[1] = y + h; + vtx[2] = x; vtx[3] = y; + vtx[4] = x + w; vtx[5] = y; + vtx[6] = x + w; vtx[7] = y + h; } }; @@ -1817,10 +1817,10 @@ status_t SurfaceFlinger::electronBeamOffAnimationImplLocked() const GLfloat h = 1.0f; const GLfloat x = (hw_w - w) * 0.5f; const GLfloat y = (hw_h - h) * 0.5f; - vtx[0] = x; vtx[1] = y; - vtx[2] = x; vtx[3] = y + h; - vtx[4] = x + w; vtx[5] = y + h; - vtx[6] = x + w; vtx[7] = y; + vtx[0] = x; vtx[1] = y + h; + vtx[2] = x; vtx[3] = y; + vtx[4] = x + w; vtx[5] = y; + vtx[6] = x + w; vtx[7] = y + h; } }; diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java index 903f2b0..f2c28bb 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java @@ -610,9 +610,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { mAlarmManager.setInexactRepeating( eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), isA(PendingIntent.class)); expectLastCall().atLeastOnce(); - - mNetManager.setBandwidthControlEnabled(true); - expectLastCall().atLeastOnce(); } private void expectNetworkState(NetworkState... state) throws Exception { @@ -633,7 +630,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory) throws Exception { - expect(mSettings.getEnabled()).andReturn(true).anyTimes(); expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes(); expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes(); expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes(); diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java index dea67f3..ffabb7b 100644 --- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java +++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java @@ -94,9 +94,6 @@ public class PhoneNumberFormattingTextWatcher implements TextWatcher { */ public PhoneNumberFormattingTextWatcher(String countryCode) { if (countryCode == null) throw new IllegalArgumentException(); - // TODO: remove this once CountryDetector.detectCountry().getCountryIso() is fixed to always - // return uppercase. Tracked at b/4941319. - countryCode = countryCode.toUpperCase(Locale.ENGLISH); mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode); } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index fce7cdc..2f010e5 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -473,8 +473,8 @@ public class ServiceState implements Parcelable { + " EmergOnly=" + mIsEmergencyOnly); } - public void setStateOutOfService() { - mState = STATE_OUT_OF_SERVICE; + private void setNullState(int state) { + mState = state; mRoaming = false; mOperatorAlphaLong = null; mOperatorAlphaShort = null; @@ -491,23 +491,12 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = false; } - // TODO - can't this be combined with the above method? + public void setStateOutOfService() { + setNullState(STATE_OUT_OF_SERVICE); + } + public void setStateOff() { - mState = STATE_POWER_OFF; - mRoaming = false; - mOperatorAlphaLong = null; - mOperatorAlphaShort = null; - mOperatorNumeric = null; - mIsManualNetworkSelection = false; - mRadioTechnology = 0; - mCssIndicator = false; - mNetworkId = -1; - mSystemId = -1; - mCdmaRoamingIndicator = -1; - mCdmaDefaultRoamingIndicator = -1; - mCdmaEriIconIndex = -1; - mCdmaEriIconMode = -1; - mIsEmergencyOnly = false; + setNullState(STATE_POWER_OFF); } public void setState(int state) { diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java index ab93e2a..68e0045 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfo.java +++ b/telephony/java/com/android/internal/telephony/CallerInfo.java @@ -526,16 +526,6 @@ public class CallerInfo { + countryIso); } - // Temp workaround: The current libphonenumber library requires - // the countryIso to be uppercase (e.g. "US") but the - // detector.detectCountry().getCountryIso() call currently returns - // "us". Passing "us" to util.parse() will just result in a - // NumberParseException. - // So force the countryIso to uppercase for now. - // TODO: remove this once getCountryIso() is fixed to always - // return uppercase. - countryIso = countryIso.toUpperCase(); - PhoneNumber pn = null; try { if (VDBG) Log.v(TAG, "parsing '" + number diff --git a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java index 910905a..aa7568b 100644 --- a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java +++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java @@ -21,6 +21,7 @@ import android.net.LinkProperties; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.util.Log; @@ -55,8 +56,13 @@ public class DefaultPhoneNotifier implements PhoneNotifier { } public void notifyServiceState(Phone sender) { + ServiceState ss = sender.getServiceState(); + if (ss == null) { + ss = new ServiceState(); + ss.setStateOutOfService(); + } try { - mRegistry.notifyServiceState(sender.getServiceState()); + mRegistry.notifyServiceState(ss); } catch (RemoteException ex) { // system process is dead } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 5fc0bf9..a6b131a 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -556,6 +556,9 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { if (DBG) log("onDataConnectionAttached: start polling notify attached"); startNetStatPoll(); notifyDataConnection(Phone.REASON_DATA_ATTACHED); + } else { + // update APN availability so that APN can be enabled. + notifyDataAvailability(Phone.REASON_DATA_ATTACHED); } setupDataOnReadyApns(Phone.REASON_DATA_ATTACHED); @@ -785,13 +788,13 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { Message msg = obtainMessage(EVENT_DISCONNECT_DONE, apnContext); apnContext.getDataConnection().tearDown(apnContext.getReason(), msg); apnContext.setState(State.DISCONNECTING); - } else { - // apn is connected but no reference to dcac. - // Should not be happen, but reset the state in case. - apnContext.setState(State.IDLE); - mPhone.notifyDataConnection(apnContext.getReason(), - apnContext.getApnType()); } + } else { + // apn is connected but no reference to dcac. + // Should not be happen, but reset the state in case. + apnContext.setState(State.IDLE); + mPhone.notifyDataConnection(apnContext.getReason(), + apnContext.getApnType()); } } } else { @@ -1528,13 +1531,16 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { createAllApnList(); if (mRadioAvailable) { if (DBG) log("onRecordsLoaded: notifying data availability"); - notifyDataAvailability(null); + notifyDataAvailability(Phone.REASON_SIM_LOADED); } setupDataOnReadyApns(Phone.REASON_SIM_LOADED); } @Override protected void onSetDependencyMet(String apnType, boolean met) { + // don't allow users to tweak hipri to work around default dependency not met + if (Phone.APN_TYPE_HIPRI.equals(apnType)) return; + ApnContext apnContext = mApnContexts.get(apnType); if (apnContext == null) { loge("onSetDependencyMet: ApnContext not found in onSetDependencyMet(" + @@ -1542,6 +1548,11 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { return; } applyNewState(apnContext, apnContext.isEnabled(), met); + if (Phone.APN_TYPE_DEFAULT.equals(apnType)) { + // tie actions on default to similar actions on HIPRI regarding dependencyMet + apnContext = mApnContexts.get(Phone.APN_TYPE_HIPRI); + if (apnContext != null) applyNewState(apnContext, apnContext.isEnabled(), met); + } } private void applyNewState(ApnContext apnContext, boolean enabled, boolean met) { @@ -1627,11 +1638,17 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { @Override protected void onRoamingOff() { if (DBG) log("onRoamingOff"); + // Notify data availability so APN can be enabled. + notifyDataAvailability(Phone.REASON_ROAMING_OFF); + setupDataOnReadyApns(Phone.REASON_ROAMING_OFF); } @Override protected void onRoamingOn() { + // Notify data availability so APN can be enabled. + notifyDataAvailability(Phone.REASON_ROAMING_ON); + if (getDataOnRoamingEnabled()) { if (DBG) log("onRoamingOn: setup data on roaming"); setupDataOnReadyApns(Phone.REASON_ROAMING_ON); diff --git a/tests/GridLayoutTest/res/layout/grid3.xml b/tests/GridLayoutTest/res/layout/grid3.xml index ba911c2..536be7e 100644 --- a/tests/GridLayoutTest/res/layout/grid3.xml +++ b/tests/GridLayoutTest/res/layout/grid3.xml @@ -66,8 +66,8 @@ <Space android:layout_row="4" android:layout_column="2" - android:layout_rowWeight="1" - android:layout_columnWeight="1" + android:layout_heightSpec="canStretch" + android:layout_widthSpec="canStretch" /> <Button diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java index e010a00..cba98c2 100644 --- a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java +++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java @@ -97,8 +97,8 @@ public class Activity2 extends Activity { Space v = new Space(context); { LayoutParams lp = new LayoutParams(row5, col3); - lp.rowWeight = 1; - lp.columnWeight = 1; + lp.widthSpec = CAN_STRETCH; + lp.heightSpec = CAN_STRETCH; vg.addView(v, lp); } } diff --git a/tests/RenderScriptTests/PerfTest/AndroidManifest.xml b/tests/RenderScriptTests/PerfTest/AndroidManifest.xml index aafd176..cc60396 100644 --- a/tests/RenderScriptTests/PerfTest/AndroidManifest.xml +++ b/tests/RenderScriptTests/PerfTest/AndroidManifest.xml @@ -9,8 +9,7 @@ android:icon="@drawable/test_pattern"> <uses-library android:name="android.test.runner" /> <activity android:name="RsBench" - android:label="RsBenchmark" - android:theme="@android:style/Theme.Black.NoTitleBar"> + android:label="RsBenchmark"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/tests/RenderScriptTests/PerfTest/res/menu/loader_menu.xml b/tests/RenderScriptTests/PerfTest/res/menu/loader_menu.xml new file mode 100644 index 0000000..8234677 --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/res/menu/loader_menu.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* Copyright (C) 2011 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +--> + +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/benchmark_mode" + android:title="@string/benchmark_mode" /> + <item android:id="@+id/debug_mode" + android:title="@string/debug_mode" /> +</menu> diff --git a/tests/RenderScriptTests/PerfTest/res/values/strings.xml b/tests/RenderScriptTests/PerfTest/res/values/strings.xml new file mode 100644 index 0000000..627ac21 --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/res/values/strings.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* Copyright (C) 2011 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <skip /> + <string name="benchmark_mode">Benchmark Mode</string> + <string name="debug_mode">Debug Mode</string> +</resources> diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBench.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBench.java index d7393f8..b336a4d 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBench.java +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBench.java @@ -31,10 +31,14 @@ import android.provider.Settings.System; import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import android.view.MenuInflater; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.ListView; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.widget.Toast; import java.lang.Runtime; @@ -77,4 +81,37 @@ public class RsBench extends Activity { super.onPause(); mView.pause(); } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.loader_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.benchmark_mode: + mView.setBenchmarkMode(); + return true; + case R.id.debug_mode: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Pick a Test"); + builder.setItems(mView.getTestNames(), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Toast.makeText(getApplicationContext(), + "Switching to: " + mView.getTestNames()[item], + Toast.LENGTH_SHORT).show(); + mView.setDebugMode(item); + } + }); + builder.show(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java index c706286..c375be5 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java @@ -84,12 +84,6 @@ public class RsBenchRS { private Resources mRes; private RenderScriptGL mRS; - private Sampler mLinearClamp; - private Sampler mLinearWrap; - private Sampler mMipLinearWrap; - private Sampler mNearestClamp; - private Sampler mNearesWrap; - private ProgramStore mProgStoreBlendNoneDepth; private ProgramStore mProgStoreBlendNone; private ProgramStore mProgStoreBlendAlpha; @@ -115,10 +109,6 @@ public class RsBenchRS { private ScriptField_FragentShaderConstants3_s mFSConstPixel; - private ProgramRaster mCullBack; - private ProgramRaster mCullFront; - private ProgramRaster mCullNone; - private Allocation mTexTorus; private Allocation mTexOpaque; private Allocation mTexTransparent; @@ -143,6 +133,8 @@ public class RsBenchRS { private ScriptC_rsbench mScript; + private ScriptC_text_test mTextScript; + private ScriptC_torus_test mTorusScript; private final BitmapFactory.Options mOptionsARGB = new BitmapFactory.Options(); @@ -310,6 +302,7 @@ public class RsBenchRS { mProgStoreBlendAdd = BLEND_ADD_DEPTH_NONE(mRS); mScript.set_gProgStoreBlendNoneDepth(mProgStoreBlendNoneDepth); + mScript.set_gProgStoreBlendNone(mProgStoreBlendNone); mScript.set_gProgStoreBlendAlpha(mProgStoreBlendAlpha); mScript.set_gProgStoreBlendAdd(mProgStoreBlendAdd); @@ -330,22 +323,24 @@ public class RsBenchRS { texBuilder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); mProgFragmentTexture = texBuilder.create(); - mProgFragmentTexture.bindSampler(mLinearClamp, 0); + mProgFragmentTexture.bindSampler(Sampler.CLAMP_LINEAR(mRS), 0); ProgramFragmentFixedFunction.Builder colBuilder = new ProgramFragmentFixedFunction.Builder(mRS); colBuilder.setVaryingColor(false); mProgFragmentColor = colBuilder.create(); mScript.set_gProgFragmentColor(mProgFragmentColor); + mScript.set_gProgFragmentTexture(mProgFragmentTexture); + // For Galaxy live wallpaper drawing ProgramFragmentFixedFunction.Builder builder = new ProgramFragmentFixedFunction.Builder(mRS); builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, ProgramFragmentFixedFunction.Builder.Format.RGB, 0); ProgramFragment pfb = builder.create(); - pfb.bindSampler(mNearesWrap, 0); + pfb.bindSampler(Sampler.WRAP_NEAREST(mRS), 0); mScript.set_gPFBackground(pfb); builder = new ProgramFragmentFixedFunction.Builder(mRS); @@ -354,7 +349,7 @@ public class RsBenchRS { ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); builder.setVaryingColor(true); ProgramFragment pfs = builder.create(); - pfs.bindSampler(mMipLinearWrap, 0); + pfs.bindSampler(Sampler.WRAP_LINEAR_MIP_LINEAR(mRS), 0); mScript.set_gPFStars(pfs); } @@ -404,6 +399,7 @@ public class RsBenchRS { mScript.set_gProgVertex(mProgVertex); + // For galaxy live wallpaper mPvStarAlloc = new ScriptField_VpConsts(mRS, 1); mScript.bind_vpConstants(mPvStarAlloc); @@ -447,13 +443,11 @@ public class RsBenchRS { private void initCustomShaders() { mVSConst = new ScriptField_VertexShaderConstants_s(mRS, 1); mFSConst = new ScriptField_FragentShaderConstants_s(mRS, 1); - mScript.bind_gVSConstants(mVSConst); - mScript.bind_gFSConstants(mFSConst); + mVSConstPixel = new ScriptField_VertexShaderConstants3_s(mRS, 1); mFSConstPixel = new ScriptField_FragentShaderConstants3_s(mRS, 1); - mScript.bind_gVSConstPixel(mVSConstPixel); - mScript.bind_gFSConstPixel(mFSConstPixel); + // Initialize the shader builder ProgramVertex.Builder pvbCustom = new ProgramVertex.Builder(mRS); @@ -506,11 +500,7 @@ public class RsBenchRS { } mProgFragmentMultitex = pfbCustom.create(); - mScript.set_gProgVertexCustom(mProgVertexCustom); - mScript.set_gProgFragmentCustom(mProgFragmentCustom); - mScript.set_gProgVertexPixelLight(mProgVertexPixelLight); - mScript.set_gProgVertexPixelLightMove(mProgVertexPixelLightMove); - mScript.set_gProgFragmentPixelLight(mProgFragmentPixelLight); + mScript.set_gProgFragmentMultitex(mProgFragmentMultitex); } @@ -587,39 +577,22 @@ public class RsBenchRS { Log.e("rs", "could not load model"); } else { mTorus = (Mesh)entry.getObject(); - mScript.set_gTorusMesh(mTorus); } createParticlesMesh(); } private void initSamplers() { - Sampler.Builder bs = new Sampler.Builder(mRS); - bs.setMinification(Sampler.Value.LINEAR); - bs.setMagnification(Sampler.Value.LINEAR); - bs.setWrapS(Sampler.Value.WRAP); - bs.setWrapT(Sampler.Value.WRAP); - mLinearWrap = bs.create(); - - mLinearClamp = Sampler.CLAMP_LINEAR(mRS); - mNearestClamp = Sampler.CLAMP_NEAREST(mRS); - mMipLinearWrap = Sampler.WRAP_LINEAR_MIP_LINEAR(mRS); - mNearesWrap = Sampler.WRAP_NEAREST(mRS); - - mScript.set_gLinearClamp(mLinearClamp); - mScript.set_gLinearWrap(mLinearWrap); - mScript.set_gMipLinearWrap(mMipLinearWrap); - mScript.set_gNearestClamp(mNearestClamp); + mScript.set_gLinearClamp(Sampler.CLAMP_LINEAR(mRS)); + mScript.set_gLinearWrap(Sampler.WRAP_LINEAR(mRS)); + mScript.set_gMipLinearWrap(Sampler.WRAP_LINEAR_MIP_LINEAR(mRS)); + mScript.set_gNearestClamp(Sampler.CLAMP_NEAREST(mRS)); } private void initProgramRaster() { - mCullBack = ProgramRaster.CULL_BACK(mRS); - mCullFront = ProgramRaster.CULL_FRONT(mRS); - mCullNone = ProgramRaster.CULL_NONE(mRS); - - mScript.set_gCullBack(mCullBack); - mScript.set_gCullFront(mCullFront); - mScript.set_gCullNone(mCullNone); + mScript.set_gCullBack(ProgramRaster.CULL_BACK(mRS)); + mScript.set_gCullFront(ProgramRaster.CULL_FRONT(mRS)); + mScript.set_gCullNone(ProgramRaster.CULL_NONE(mRS)); } private int strlen(byte[] array) { @@ -645,9 +618,47 @@ public class RsBenchRS { } } + public void setDebugMode(int num) { + mScript.invoke_setDebugMode(num); + } + + public void setBenchmarkMode() { + mScript.invoke_setBenchmarkMode(); + } + + void initTextScript() { + mTextScript = new ScriptC_text_test(mRS, mRes, R.raw.text_test); + mTextScript.set_gFontSans(mFontSans); + mTextScript.set_gFontSerif(mFontSerif); + } + + void initTorusScript() { + mTorusScript = new ScriptC_torus_test(mRS, mRes, R.raw.torus_test); + mTorusScript.set_gCullFront(ProgramRaster.CULL_FRONT(mRS)); + mTorusScript.set_gCullBack(ProgramRaster.CULL_BACK(mRS)); + mTorusScript.set_gLinearClamp(Sampler.CLAMP_LINEAR(mRS)); + mTorusScript.set_gTorusMesh(mTorus); + mTorusScript.set_gTexTorus(mTexTorus); + mTorusScript.set_gProgVertexCustom(mProgVertexCustom); + mTorusScript.set_gProgFragmentCustom(mProgFragmentCustom); + mTorusScript.set_gProgVertexPixelLight(mProgVertexPixelLight); + mTorusScript.set_gProgVertexPixelLightMove(mProgVertexPixelLightMove); + mTorusScript.set_gProgFragmentPixelLight(mProgFragmentPixelLight); + mTorusScript.bind_gVSConstPixel(mVSConstPixel); + mTorusScript.bind_gFSConstPixel(mFSConstPixel); + mTorusScript.bind_gVSConstants(mVSConst); + mTorusScript.bind_gFSConstants(mFSConst); + mTorusScript.set_gProgVertex(mProgVertex); + mTorusScript.set_gProgFragmentTexture(mProgFragmentTexture); + mTorusScript.set_gProgFragmentColor(mProgFragmentColor); + mTorusScript.set_gProgStoreBlendNoneDepth(mProgStoreBlendNoneDepth); + } + private void initRS() { mScript = new ScriptC_rsbench(mRS, mRes, R.raw.rsbench); + + mRS.setMessageHandler(mRsMessage); mMaxModes = mScript.get_gMaxModes(); @@ -709,6 +720,13 @@ public class RsBenchRS { mSampleListViewAllocs.copyAll(); mScript.bind_gListViewText(mSampleListViewAllocs); + initTextScript(); + initTorusScript(); + + mScript.set_gFontScript(mTextScript); + mScript.set_gTorusScript(mTorusScript); + mScript.set_gDummyAlloc(Allocation.createSized(mRS, Element.I32(mRS), 1)); + mRS.bindRootScript(mScript); } } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchView.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchView.java index 2882b93..61aa3e1 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchView.java +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchView.java @@ -105,4 +105,16 @@ public class RsBenchView extends RSSurfaceView { public boolean testIsFinished() { return mRender.testIsFinished(); } + + void setBenchmarkMode() { + mRender.setBenchmarkMode(); + } + + void setDebugMode(int num) { + mRender.setDebugMode(num); + } + + String[] getTestNames() { + return mRender.mTestNames; + } } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs index bb81862..eaafe1d 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs @@ -18,6 +18,7 @@ #include "rs_graphics.rsh" #include "shader_def.rsh" +#include "subtest_def.rsh" /* Message sent from script to renderscript */ const int RS_MSG_TEST_DONE = 100; @@ -98,7 +99,6 @@ ListAllocs *gListViewText; rs_mesh g10by10Mesh; rs_mesh g100by100Mesh; rs_mesh gWbyHMesh; -rs_mesh gTorusMesh; rs_mesh gSingleMesh; rs_font gFontSans; @@ -115,25 +115,15 @@ rs_program_raster gCullBack; rs_program_raster gCullFront; rs_program_raster gCullNone; -// Custom vertex shader compunents -VertexShaderConstants *gVSConstants; -FragentShaderConstants *gFSConstants; -VertexShaderConstants3 *gVSConstPixel; -FragentShaderConstants3 *gFSConstPixel; // Export these out to easily set the inputs to shader VertexShaderInputs *gVSInputs; -// Custom shaders we use for lighting -rs_program_vertex gProgVertexCustom; -rs_program_fragment gProgFragmentCustom; -rs_program_vertex gProgVertexPixelLight; -rs_program_vertex gProgVertexPixelLightMove; -rs_program_fragment gProgFragmentPixelLight; + rs_program_fragment gProgFragmentMultitex; rs_allocation gRenderBufferColor; rs_allocation gRenderBufferDepth; -float gDt = 0; +static float gDt = 0; void init() { } @@ -141,16 +131,6 @@ void init() { static int gRenderSurfaceW; static int gRenderSurfaceH; -static const char *sampleText = "This is a sample of small text for performace"; -// Offsets for multiple layer of text -static int textOffsets[] = { 0, 0, -5, -5, 5, 5, -8, -8, 8, 8}; -static float textColors[] = {1.0f, 1.0f, 1.0f, 1.0f, - 0.5f, 0.7f, 0.5f, 1.0f, - 0.7f, 0.5f, 0.5f, 1.0f, - 0.5f, 0.5f, 0.7f, 1.0f, - 0.5f, 0.6f, 0.7f, 1.0f, -}; - /** * Methods to draw the galaxy live wall paper */ @@ -291,40 +271,16 @@ static void setupOffscreenTarget() { rsgBindDepthTarget(gRenderBufferDepth); } -static void displayFontSamples(int fillNum) { +rs_script gFontScript; +rs_script gTorusScript; +rs_allocation gDummyAlloc; - rs_font fonts[5]; - fonts[0] = gFontSans; - fonts[1] = gFontSerif; - fonts[2] = gFontSans; - fonts[3] = gFontSerif; - fonts[4] = gFontSans; - - uint width = gRenderSurfaceW; - uint height = gRenderSurfaceH; - int left = 0, right = 0, top = 0, bottom = 0; - rsgMeasureText(sampleText, &left, &right, &top, &bottom); - - int textHeight = top - bottom; - int textWidth = right - left; - int numVerticalLines = height / textHeight; - int yPos = top; - - int xOffset = 0, yOffset = 0; - for(int fillI = 0; fillI < fillNum; fillI ++) { - rsgBindFont(fonts[fillI]); - xOffset = textOffsets[fillI * 2]; - yOffset = textOffsets[fillI * 2 + 1]; - float *colPtr = textColors + fillI * 4; - rsgFontColor(colPtr[0], colPtr[1], colPtr[2], colPtr[3]); - for (int h = 0; h < 4; h ++) { - yPos = top + yOffset; - for (int v = 0; v < numVerticalLines; v ++) { - rsgDrawText(sampleText, xOffset + textWidth * h, yPos); - yPos += textHeight; - } - } - } +static void displayFontSamples(int fillNum) { + TestData testData; + testData.renderSurfaceW = gRenderSurfaceW; + testData.renderSurfaceH = gRenderSurfaceH; + testData.user = fillNum; + rsForEach(gFontScript, gDummyAlloc, gDummyAlloc, &testData); } static void bindProgramVertexOrtho() { @@ -555,212 +511,37 @@ static void displayLiveWallPaper(int wResolution, int hResolution) { drawMeshInPage(2.0f*gRenderSurfaceW, 0, wResolution, hResolution); } -static float gTorusRotation = 0; -static void updateModelMatrix(rs_matrix4x4 *matrix, void *buffer) { - if (buffer == 0) { - rsgProgramVertexLoadModelMatrix(matrix); - } else { - rsgAllocationSyncAll(rsGetAllocation(buffer)); - } -} - -static void drawToruses(int numMeshes, rs_matrix4x4 *matrix, void *buffer) { - - if (numMeshes == 1) { - rsMatrixLoadTranslate(matrix, 0.0f, 0.0f, -7.5f); - rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); - updateModelMatrix(matrix, buffer); - rsgDrawMesh(gTorusMesh); - return; - } - - if (numMeshes == 2) { - rsMatrixLoadTranslate(matrix, -1.6f, 0.0f, -7.5f); - rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); - updateModelMatrix(matrix, buffer); - rsgDrawMesh(gTorusMesh); - - rsMatrixLoadTranslate(matrix, 1.6f, 0.0f, -7.5f); - rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); - updateModelMatrix(matrix, buffer); - rsgDrawMesh(gTorusMesh); - return; - } - - float startX = -5.0f; - float startY = -1.5f; - float startZ = -15.0f; - float dist = 3.2f; - - for (int h = 0; h < 4; h ++) { - for (int v = 0; v < 2; v ++) { - // Position our model on the screen - rsMatrixLoadTranslate(matrix, startX + dist * h, startY + dist * v, startZ); - rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); - updateModelMatrix(matrix, buffer); - rsgDrawMesh(gTorusMesh); - } - } -} - // Quick hack to get some geometry numbers static void displaySimpleGeoSamples(bool useTexture, int numMeshes) { - rsgBindProgramVertex(gProgVertex); - rsgBindProgramRaster(gCullBack); - // Setup the projection matrix with 30 degree field of view - rs_matrix4x4 proj; - float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; - rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f); - rsgProgramVertexLoadProjectionMatrix(&proj); - - // Fragment shader with texture - rsgBindProgramStore(gProgStoreBlendNoneDepth); - if (useTexture) { - rsgBindProgramFragment(gProgFragmentTexture); - } else { - rsgBindProgramFragment(gProgFragmentColor); - rsgProgramFragmentConstantColor(gProgFragmentColor, 0.1, 0.7, 0.1, 1); - } - rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp); - rsgBindTexture(gProgFragmentTexture, 0, gTexTorus); - - // Apply a rotation to our mesh - gTorusRotation += 50.0f * gDt; - if (gTorusRotation > 360.0f) { - gTorusRotation -= 360.0f; - } - - rs_matrix4x4 matrix; - drawToruses(numMeshes, &matrix, 0); -} - -float gLight0Rotation = 0; -float gLight1Rotation = 0; - -static void setupCustomShaderLights() { - float4 light0Pos = {-5.0f, 5.0f, -10.0f, 1.0f}; - float4 light1Pos = {2.0f, 5.0f, 15.0f, 1.0f}; - float4 light0DiffCol = {0.9f, 0.7f, 0.7f, 1.0f}; - float4 light0SpecCol = {0.9f, 0.6f, 0.6f, 1.0f}; - float4 light1DiffCol = {0.5f, 0.5f, 0.9f, 1.0f}; - float4 light1SpecCol = {0.5f, 0.5f, 0.9f, 1.0f}; - - gLight0Rotation += 50.0f * gDt; - if (gLight0Rotation > 360.0f) { - gLight0Rotation -= 360.0f; - } - gLight1Rotation -= 50.0f * gDt; - if (gLight1Rotation > 360.0f) { - gLight1Rotation -= 360.0f; - } - - rs_matrix4x4 l0Mat; - rsMatrixLoadRotate(&l0Mat, gLight0Rotation, 1.0f, 0.0f, 0.0f); - light0Pos = rsMatrixMultiply(&l0Mat, light0Pos); - rs_matrix4x4 l1Mat; - rsMatrixLoadRotate(&l1Mat, gLight1Rotation, 0.0f, 0.0f, 1.0f); - light1Pos = rsMatrixMultiply(&l1Mat, light1Pos); - - // Set light 0 properties - gVSConstants->light0_Posision = light0Pos; - gVSConstants->light0_Diffuse = 1.0f; - gVSConstants->light0_Specular = 0.5f; - gVSConstants->light0_CosinePower = 10.0f; - // Set light 1 properties - gVSConstants->light1_Posision = light1Pos; - gVSConstants->light1_Diffuse = 1.0f; - gVSConstants->light1_Specular = 0.7f; - gVSConstants->light1_CosinePower = 25.0f; - rsgAllocationSyncAll(rsGetAllocation(gVSConstants)); - - // Update fragment shader constants - // Set light 0 colors - gFSConstants->light0_DiffuseColor = light0DiffCol; - gFSConstants->light0_SpecularColor = light0SpecCol; - // Set light 1 colors - gFSConstants->light1_DiffuseColor = light1DiffCol; - gFSConstants->light1_SpecularColor = light1SpecCol; - rsgAllocationSyncAll(rsGetAllocation(gFSConstants)); - - // Set light 0 properties for per pixel lighting - gFSConstPixel->light0_Posision = light0Pos; - gFSConstPixel->light0_Diffuse = 1.0f; - gFSConstPixel->light0_Specular = 0.5f; - gFSConstPixel->light0_CosinePower = 10.0f; - gFSConstPixel->light0_DiffuseColor = light0DiffCol; - gFSConstPixel->light0_SpecularColor = light0SpecCol; - // Set light 1 properties - gFSConstPixel->light1_Posision = light1Pos; - gFSConstPixel->light1_Diffuse = 1.0f; - gFSConstPixel->light1_Specular = 0.7f; - gFSConstPixel->light1_CosinePower = 25.0f; - gFSConstPixel->light1_DiffuseColor = light1DiffCol; - gFSConstPixel->light1_SpecularColor = light1SpecCol; - rsgAllocationSyncAll(rsGetAllocation(gFSConstPixel)); + TestData testData; + testData.renderSurfaceW = gRenderSurfaceW; + testData.renderSurfaceH = gRenderSurfaceH; + testData.dt = gDt; + testData.user = 0; + testData.user1 = useTexture ? 1 : 0; + testData.user2 = numMeshes; + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); } static void displayCustomShaderSamples(int numMeshes) { - - // Update vertex shader constants - // Load model matrix - // Apply a rotation to our mesh - gTorusRotation += 50.0f * gDt; - if (gTorusRotation > 360.0f) { - gTorusRotation -= 360.0f; - } - - // Setup the projection matrix - float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; - rsMatrixLoadPerspective(&gVSConstants->proj, 30.0f, aspect, 0.1f, 100.0f); - setupCustomShaderLights(); - - rsgBindProgramVertex(gProgVertexCustom); - - // Fragment shader with texture - rsgBindProgramStore(gProgStoreBlendNoneDepth); - rsgBindProgramFragment(gProgFragmentCustom); - rsgBindSampler(gProgFragmentCustom, 0, gLinearClamp); - rsgBindTexture(gProgFragmentCustom, 0, gTexTorus); - - // Use back face culling - rsgBindProgramRaster(gCullBack); - - drawToruses(numMeshes, &gVSConstants->model, gVSConstants); + TestData testData; + testData.renderSurfaceW = gRenderSurfaceW; + testData.renderSurfaceH = gRenderSurfaceH; + testData.dt = gDt; + testData.user = 1; + testData.user1 = numMeshes; + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); } static void displayPixelLightSamples(int numMeshes, bool heavyVertex) { - - // Update vertex shader constants - // Load model matrix - // Apply a rotation to our mesh - gTorusRotation += 30.0f * gDt; - if (gTorusRotation > 360.0f) { - gTorusRotation -= 360.0f; - } - - gVSConstPixel->time = rsUptimeMillis()*0.005; - - // Setup the projection matrix - float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; - rsMatrixLoadPerspective(&gVSConstPixel->proj, 30.0f, aspect, 0.1f, 100.0f); - setupCustomShaderLights(); - - if (heavyVertex) { - rsgBindProgramVertex(gProgVertexPixelLightMove); - } else { - rsgBindProgramVertex(gProgVertexPixelLight); - } - - // Fragment shader with texture - rsgBindProgramStore(gProgStoreBlendNoneDepth); - rsgBindProgramFragment(gProgFragmentPixelLight); - rsgBindSampler(gProgFragmentPixelLight, 0, gLinearClamp); - rsgBindTexture(gProgFragmentPixelLight, 0, gTexTorus); - - // Use back face culling - rsgBindProgramRaster(gCullBack); - - drawToruses(numMeshes, &gVSConstPixel->model, gVSConstPixel); + TestData testData; + testData.renderSurfaceW = gRenderSurfaceW; + testData.renderSurfaceH = gRenderSurfaceH; + testData.dt = gDt; + testData.user = 2; + testData.user1 = numMeshes; + testData.user2 = heavyVertex ? 1 : 0; + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); } static void displayMultitextureSample(bool blend, int quadCount) { @@ -862,6 +643,20 @@ static const char *testNames[] = { "UI test with live wallpaper", }; +static bool gIsDebugMode = false; +void setDebugMode(int testNumber) { + gIsDebugMode = true; + benchMode = testNumber; + rsgClearAllRenderTargets(); +} + +void setBenchmarkMode() { + gIsDebugMode = false; + benchMode = 0; + runningLoops = 0; +} + + void getTestName(int testIndex) { int bufferLen = rsAllocationGetDimX(rsGetAllocation(gStringBuffer)); if (testIndex >= gMaxModes) { @@ -932,7 +727,7 @@ static void runTest(int index) { displaySingletexFill(true, 10); break; case 18: - displayMultitextureSample(true, 8); + displayMultitextureSample(true, 10); break; case 19: displayPixelLightSamples(1, false); @@ -992,14 +787,7 @@ static void drawOffscreenResult(int posX, int posY, int width, int height) { startX + width, startY, 0, 1, 1); } -int root(void) { - gRenderSurfaceW = rsgGetWidth(); - gRenderSurfaceH = rsgGetHeight(); - rsgClearColor(0.2f, 0.2f, 0.2f, 1.0f); - rsgClearDepth(1.0f); - if(!checkInit()) { - return 1; - } +static void benchmark() { gDt = 1.0f / 60.0f; @@ -1045,8 +833,6 @@ int root(void) { benchMode ++; - gTorusRotation = 0; - if (benchMode == gMaxModes) { rsSendToClientBlocking(RS_MSG_RESULTS_READY, gResultBuffer, gMaxModes*sizeof(float)); benchMode = 0; @@ -1058,5 +844,30 @@ int root(void) { sendMsgFlag = true; } } + +} + +static void debug() { + gDt = rsGetDt(); + + rsgFinish(); + runTest(benchMode); +} + +int root(void) { + gRenderSurfaceW = rsgGetWidth(); + gRenderSurfaceH = rsgGetHeight(); + rsgClearColor(0.2f, 0.2f, 0.2f, 1.0f); + rsgClearDepth(1.0f); + if(!checkInit()) { + return 1; + } + + if (gIsDebugMode) { + debug(); + } else { + benchmark(); + } + return 1; } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/subtest_def.rsh b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/subtest_def.rsh new file mode 100644 index 0000000..b635373 --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/subtest_def.rsh @@ -0,0 +1,28 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma version(1) + +#pragma rs java_package_name(com.android.perftest) + +typedef struct TestData_s { + int renderSurfaceW; + int renderSurfaceH; + float dt; + int user; + int user1; + int user2; + int user3; +} TestData; + diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/text_test.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/text_test.rs new file mode 100644 index 0000000..0df6b35 --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/text_test.rs @@ -0,0 +1,82 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma version(1) + +#pragma rs java_package_name(com.android.perftest) + +#include "rs_graphics.rsh" +#include "subtest_def.rsh" + +rs_font gFontSans; +rs_font gFontSerif; + +void init() { +} + +static int gRenderSurfaceW = 1280; +static int gRenderSurfaceH = 720; + +static const char *sampleText = "This is a sample of small text for performace"; +// Offsets for multiple layer of text +static int textOffsets[] = { 0, 0, -5, -5, 5, 5, -8, -8, 8, 8}; +static float textColors[] = {1.0f, 1.0f, 1.0f, 1.0f, + 0.5f, 0.7f, 0.5f, 1.0f, + 0.7f, 0.5f, 0.5f, 1.0f, + 0.5f, 0.5f, 0.7f, 1.0f, + 0.5f, 0.6f, 0.7f, 1.0f, +}; + +static void displayFontSamples(int fillNum) { + + rs_font fonts[5]; + fonts[0] = gFontSans; + fonts[1] = gFontSerif; + fonts[2] = gFontSans; + fonts[3] = gFontSerif; + fonts[4] = gFontSans; + + uint width = gRenderSurfaceW; + uint height = gRenderSurfaceH; + int left = 0, right = 0, top = 0, bottom = 0; + rsgMeasureText(sampleText, &left, &right, &top, &bottom); + + int textHeight = top - bottom; + int textWidth = right - left; + int numVerticalLines = height / textHeight; + int yPos = top; + + int xOffset = 0, yOffset = 0; + for(int fillI = 0; fillI < fillNum; fillI ++) { + rsgBindFont(fonts[fillI]); + xOffset = textOffsets[fillI * 2]; + yOffset = textOffsets[fillI * 2 + 1]; + float *colPtr = textColors + fillI * 4; + rsgFontColor(colPtr[0], colPtr[1], colPtr[2], colPtr[3]); + for (int h = 0; h < 4; h ++) { + yPos = top + yOffset; + for (int v = 0; v < numVerticalLines; v ++) { + rsgDrawText(sampleText, xOffset + textWidth * h, yPos); + yPos += textHeight; + } + } + } +} + +void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) { + TestData *testData = (TestData*)usrData; + gRenderSurfaceW = testData->renderSurfaceW; + gRenderSurfaceH = testData->renderSurfaceH; + displayFontSamples(testData->user); +} diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/torus_test.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/torus_test.rs new file mode 100644 index 0000000..26d5680 --- /dev/null +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/torus_test.rs @@ -0,0 +1,283 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma version(1) + +#pragma rs java_package_name(com.android.perftest) + +#include "rs_graphics.rsh" +#include "subtest_def.rsh" +#include "shader_def.rsh" + +rs_program_vertex gProgVertex; +rs_program_fragment gProgFragmentColor; +rs_program_fragment gProgFragmentTexture; + +rs_program_store gProgStoreBlendNoneDepth; +rs_mesh gTorusMesh; + +rs_program_raster gCullBack; +rs_program_raster gCullFront; + +// Custom vertex shader compunents +VertexShaderConstants *gVSConstants; +FragentShaderConstants *gFSConstants; +VertexShaderConstants3 *gVSConstPixel; +FragentShaderConstants3 *gFSConstPixel; + +// Custom shaders we use for lighting +rs_program_vertex gProgVertexCustom; +rs_program_fragment gProgFragmentCustom; + +rs_sampler gLinearClamp; +rs_allocation gTexTorus; + +rs_program_vertex gProgVertexPixelLight; +rs_program_vertex gProgVertexPixelLightMove; +rs_program_fragment gProgFragmentPixelLight; + +static float gDt = 0.0f; + +static int gRenderSurfaceW; +static int gRenderSurfaceH; + + +static float gTorusRotation = 0; +static void updateModelMatrix(rs_matrix4x4 *matrix, void *buffer) { + if (buffer == 0) { + rsgProgramVertexLoadModelMatrix(matrix); + } else { + rsgAllocationSyncAll(rsGetAllocation(buffer)); + } +} + +static void drawToruses(int numMeshes, rs_matrix4x4 *matrix, void *buffer) { + + if (numMeshes == 1) { + rsMatrixLoadTranslate(matrix, 0.0f, 0.0f, -7.5f); + rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); + updateModelMatrix(matrix, buffer); + rsgDrawMesh(gTorusMesh); + return; + } + + if (numMeshes == 2) { + rsMatrixLoadTranslate(matrix, -1.6f, 0.0f, -7.5f); + rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); + updateModelMatrix(matrix, buffer); + rsgDrawMesh(gTorusMesh); + + rsMatrixLoadTranslate(matrix, 1.6f, 0.0f, -7.5f); + rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); + updateModelMatrix(matrix, buffer); + rsgDrawMesh(gTorusMesh); + return; + } + + float startX = -5.0f; + float startY = -1.5f; + float startZ = -15.0f; + float dist = 3.2f; + + for (int h = 0; h < 4; h ++) { + for (int v = 0; v < 2; v ++) { + // Position our model on the screen + rsMatrixLoadTranslate(matrix, startX + dist * h, startY + dist * v, startZ); + rsMatrixRotate(matrix, gTorusRotation, 1.0f, 0.0f, 0.0f); + updateModelMatrix(matrix, buffer); + rsgDrawMesh(gTorusMesh); + } + } +} + + +// Quick hack to get some geometry numbers +static void displaySimpleGeoSamples(bool useTexture, int numMeshes) { + rsgBindProgramVertex(gProgVertex); + rsgBindProgramRaster(gCullBack); + // Setup the projection matrix with 30 degree field of view + rs_matrix4x4 proj; + float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; + rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f); + rsgProgramVertexLoadProjectionMatrix(&proj); + + // Fragment shader with texture + rsgBindProgramStore(gProgStoreBlendNoneDepth); + if (useTexture) { + rsgBindProgramFragment(gProgFragmentTexture); + } else { + rsgBindProgramFragment(gProgFragmentColor); + rsgProgramFragmentConstantColor(gProgFragmentColor, 0.1, 0.7, 0.1, 1); + } + rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp); + rsgBindTexture(gProgFragmentTexture, 0, gTexTorus); + + // Apply a rotation to our mesh + gTorusRotation += 50.0f * gDt; + if (gTorusRotation > 360.0f) { + gTorusRotation -= 360.0f; + } + + rs_matrix4x4 matrix; + drawToruses(numMeshes, &matrix, 0); +} + +float gLight0Rotation = 0; +float gLight1Rotation = 0; + +static void setupCustomShaderLights() { + float4 light0Pos = {-5.0f, 5.0f, -10.0f, 1.0f}; + float4 light1Pos = {2.0f, 5.0f, 15.0f, 1.0f}; + float4 light0DiffCol = {0.9f, 0.7f, 0.7f, 1.0f}; + float4 light0SpecCol = {0.9f, 0.6f, 0.6f, 1.0f}; + float4 light1DiffCol = {0.5f, 0.5f, 0.9f, 1.0f}; + float4 light1SpecCol = {0.5f, 0.5f, 0.9f, 1.0f}; + + gLight0Rotation += 50.0f * gDt; + if (gLight0Rotation > 360.0f) { + gLight0Rotation -= 360.0f; + } + gLight1Rotation -= 50.0f * gDt; + if (gLight1Rotation > 360.0f) { + gLight1Rotation -= 360.0f; + } + + rs_matrix4x4 l0Mat; + rsMatrixLoadRotate(&l0Mat, gLight0Rotation, 1.0f, 0.0f, 0.0f); + light0Pos = rsMatrixMultiply(&l0Mat, light0Pos); + rs_matrix4x4 l1Mat; + rsMatrixLoadRotate(&l1Mat, gLight1Rotation, 0.0f, 0.0f, 1.0f); + light1Pos = rsMatrixMultiply(&l1Mat, light1Pos); + + // Set light 0 properties + gVSConstants->light0_Posision = light0Pos; + gVSConstants->light0_Diffuse = 1.0f; + gVSConstants->light0_Specular = 0.5f; + gVSConstants->light0_CosinePower = 10.0f; + // Set light 1 properties + gVSConstants->light1_Posision = light1Pos; + gVSConstants->light1_Diffuse = 1.0f; + gVSConstants->light1_Specular = 0.7f; + gVSConstants->light1_CosinePower = 25.0f; + rsgAllocationSyncAll(rsGetAllocation(gVSConstants)); + + // Update fragment shader constants + // Set light 0 colors + gFSConstants->light0_DiffuseColor = light0DiffCol; + gFSConstants->light0_SpecularColor = light0SpecCol; + // Set light 1 colors + gFSConstants->light1_DiffuseColor = light1DiffCol; + gFSConstants->light1_SpecularColor = light1SpecCol; + rsgAllocationSyncAll(rsGetAllocation(gFSConstants)); + + // Set light 0 properties for per pixel lighting + gFSConstPixel->light0_Posision = light0Pos; + gFSConstPixel->light0_Diffuse = 1.0f; + gFSConstPixel->light0_Specular = 0.5f; + gFSConstPixel->light0_CosinePower = 10.0f; + gFSConstPixel->light0_DiffuseColor = light0DiffCol; + gFSConstPixel->light0_SpecularColor = light0SpecCol; + // Set light 1 properties + gFSConstPixel->light1_Posision = light1Pos; + gFSConstPixel->light1_Diffuse = 1.0f; + gFSConstPixel->light1_Specular = 0.7f; + gFSConstPixel->light1_CosinePower = 25.0f; + gFSConstPixel->light1_DiffuseColor = light1DiffCol; + gFSConstPixel->light1_SpecularColor = light1SpecCol; + rsgAllocationSyncAll(rsGetAllocation(gFSConstPixel)); +} + +static void displayCustomShaderSamples(int numMeshes) { + + // Update vertex shader constants + // Load model matrix + // Apply a rotation to our mesh + gTorusRotation += 50.0f * gDt; + if (gTorusRotation > 360.0f) { + gTorusRotation -= 360.0f; + } + + // Setup the projection matrix + float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; + rsMatrixLoadPerspective(&gVSConstants->proj, 30.0f, aspect, 0.1f, 100.0f); + setupCustomShaderLights(); + + rsgBindProgramVertex(gProgVertexCustom); + + // Fragment shader with texture + rsgBindProgramStore(gProgStoreBlendNoneDepth); + rsgBindProgramFragment(gProgFragmentCustom); + rsgBindSampler(gProgFragmentCustom, 0, gLinearClamp); + rsgBindTexture(gProgFragmentCustom, 0, gTexTorus); + + // Use back face culling + rsgBindProgramRaster(gCullBack); + + drawToruses(numMeshes, &gVSConstants->model, gVSConstants); +} + +static void displayPixelLightSamples(int numMeshes, bool heavyVertex) { + + // Update vertex shader constants + // Load model matrix + // Apply a rotation to our mesh + gTorusRotation += 30.0f * gDt; + if (gTorusRotation > 360.0f) { + gTorusRotation -= 360.0f; + } + + gVSConstPixel->time = rsUptimeMillis()*0.005; + + // Setup the projection matrix + float aspect = (float)gRenderSurfaceW / (float)gRenderSurfaceH; + rsMatrixLoadPerspective(&gVSConstPixel->proj, 30.0f, aspect, 0.1f, 100.0f); + setupCustomShaderLights(); + + if (heavyVertex) { + rsgBindProgramVertex(gProgVertexPixelLightMove); + } else { + rsgBindProgramVertex(gProgVertexPixelLight); + } + + // Fragment shader with texture + rsgBindProgramStore(gProgStoreBlendNoneDepth); + rsgBindProgramFragment(gProgFragmentPixelLight); + rsgBindSampler(gProgFragmentPixelLight, 0, gLinearClamp); + rsgBindTexture(gProgFragmentPixelLight, 0, gTexTorus); + + // Use back face culling + rsgBindProgramRaster(gCullBack); + + drawToruses(numMeshes, &gVSConstPixel->model, gVSConstPixel); +} + + +void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) { + TestData *testData = (TestData*)usrData; + gRenderSurfaceW = testData->renderSurfaceW; + gRenderSurfaceH = testData->renderSurfaceH; + gDt = testData->dt; + + switch(testData->user) { + case 0: + displaySimpleGeoSamples(testData->user1 == 1 ? true : false, testData->user2); + break; + case 1: + displayCustomShaderSamples(testData->user1); + break; + case 2: + displayPixelLightSamples(testData->user1, testData->user2 == 1 ? true : false); + break; + } +} diff --git a/voip/java/com/android/server/sip/SipWakeupTimer.java b/voip/java/com/android/server/sip/SipWakeupTimer.java index 76780c0..00d47ac 100644 --- a/voip/java/com/android/server/sip/SipWakeupTimer.java +++ b/voip/java/com/android/server/sip/SipWakeupTimer.java @@ -83,7 +83,7 @@ class SipWakeupTimer extends BroadcastReceiver { mEventQueue = null; } - private synchronized boolean stopped() { + private boolean stopped() { if (mEventQueue == null) { Log.w(TAG, "Timer stopped"); return true; @@ -233,7 +233,7 @@ class SipWakeupTimer extends BroadcastReceiver { } @Override - public void onReceive(Context context, Intent intent) { + public synchronized void onReceive(Context context, Intent intent) { // This callback is already protected by AlarmManager's wake lock. String action = intent.getAction(); if (getAction().equals(action) @@ -261,7 +261,7 @@ class SipWakeupTimer extends BroadcastReceiver { } } - private synchronized void execute(long triggerTime) { + private void execute(long triggerTime) { if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " + showTime(triggerTime) + ": " + mEventQueue.size()); if (stopped() || mEventQueue.isEmpty()) return; diff --git a/vpn/java/android/net/vpn/IVpnService.aidl b/vpn/java/android/net/vpn/IVpnService.aidl deleted file mode 100644 index 6bf3edd..0000000 --- a/vpn/java/android/net/vpn/IVpnService.aidl +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.net.vpn; - -import android.net.vpn.VpnProfile; - -/** - * Interface to access a VPN service. - * {@hide} - */ -interface IVpnService { - /** - * Sets up a VPN connection. - * @param profile the profile object - * @param username the username for authentication - * @param password the corresponding password for authentication - * @return true if VPN is successfully connected - */ - boolean connect(in VpnProfile profile, String username, String password); - - /** - * Tears down the VPN connection. - */ - void disconnect(); - - /** - * Gets the the current connection state. - */ - String getState(in VpnProfile profile); - - /** - * Returns the idle state. - * @return true if the system is not connecting/connected to a VPN - */ - boolean isIdle(); -} diff --git a/vpn/java/android/net/vpn/L2tpIpsecProfile.java b/vpn/java/android/net/vpn/L2tpIpsecProfile.java deleted file mode 100644 index 4ae2dec..0000000 --- a/vpn/java/android/net/vpn/L2tpIpsecProfile.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.net.vpn; - -import android.os.Parcel; - -/** - * The profile for certificate-based L2TP-over-IPSec type of VPN. - * {@hide} - */ -public class L2tpIpsecProfile extends L2tpProfile { - private static final long serialVersionUID = 1L; - - private String mUserCertificate; - private String mCaCertificate; - - @Override - public VpnType getType() { - return VpnType.L2TP_IPSEC; - } - - public void setCaCertificate(String name) { - mCaCertificate = name; - } - - public String getCaCertificate() { - return mCaCertificate; - } - - public void setUserCertificate(String name) { - mUserCertificate = name; - } - - public String getUserCertificate() { - return mUserCertificate; - } - - @Override - protected void readFromParcel(Parcel in) { - super.readFromParcel(in); - mCaCertificate = in.readString(); - mUserCertificate = in.readString(); - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - super.writeToParcel(parcel, flags); - parcel.writeString(mCaCertificate); - parcel.writeString(mUserCertificate); - } -} diff --git a/vpn/java/android/net/vpn/L2tpIpsecPskProfile.java b/vpn/java/android/net/vpn/L2tpIpsecPskProfile.java deleted file mode 100644 index 7a03018..0000000 --- a/vpn/java/android/net/vpn/L2tpIpsecPskProfile.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.net.vpn; - -import android.os.Parcel; - -/** - * The profile for pre-shared-key-based L2TP-over-IPSec type of VPN. - * {@hide} - */ -public class L2tpIpsecPskProfile extends L2tpProfile { - private static final long serialVersionUID = 1L; - - private String mPresharedKey; - - @Override - public VpnType getType() { - return VpnType.L2TP_IPSEC_PSK; - } - - public void setPresharedKey(String key) { - mPresharedKey = key; - } - - public String getPresharedKey() { - return mPresharedKey; - } - - @Override - protected void readFromParcel(Parcel in) { - super.readFromParcel(in); - mPresharedKey = in.readString(); - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - super.writeToParcel(parcel, flags); - parcel.writeString(mPresharedKey); - } -} diff --git a/vpn/java/android/net/vpn/L2tpProfile.java b/vpn/java/android/net/vpn/L2tpProfile.java deleted file mode 100644 index dbba0c5..0000000 --- a/vpn/java/android/net/vpn/L2tpProfile.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.net.vpn; - -import android.os.Parcel; - -/** - * The profile for L2TP type of VPN. - * {@hide} - */ -public class L2tpProfile extends VpnProfile { - private static final long serialVersionUID = 1L; - - private boolean mSecret; - private String mSecretString; - - @Override - public VpnType getType() { - return VpnType.L2TP; - } - - /** - * Enables/disables the secret for authenticating tunnel connection. - */ - public void setSecretEnabled(boolean enabled) { - mSecret = enabled; - } - - public boolean isSecretEnabled() { - return mSecret; - } - - public void setSecretString(String secret) { - mSecretString = secret; - } - - public String getSecretString() { - return mSecretString; - } - - @Override - protected void readFromParcel(Parcel in) { - super.readFromParcel(in); - mSecret = in.readInt() > 0; - mSecretString = in.readString(); - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - super.writeToParcel(parcel, flags); - parcel.writeInt(mSecret ? 1 : 0); - parcel.writeString(mSecretString); - } -} diff --git a/vpn/java/android/net/vpn/PptpProfile.java b/vpn/java/android/net/vpn/PptpProfile.java deleted file mode 100644 index b4b7be5..0000000 --- a/vpn/java/android/net/vpn/PptpProfile.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.net.vpn; - -import android.os.Parcel; - -/** - * The profile for PPTP type of VPN. - * {@hide} - */ -public class PptpProfile extends VpnProfile { - private static final long serialVersionUID = 1L; - private boolean mEncryption = true; - - @Override - public VpnType getType() { - return VpnType.PPTP; - } - - /** - * Enables/disables the encryption for PPTP tunnel. - */ - public void setEncryptionEnabled(boolean enabled) { - mEncryption = enabled; - } - - public boolean isEncryptionEnabled() { - return mEncryption; - } - - @Override - protected void readFromParcel(Parcel in) { - super.readFromParcel(in); - mEncryption = in.readInt() > 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - super.writeToParcel(parcel, flags); - parcel.writeInt(mEncryption ? 1 : 0); - } -} diff --git a/vpn/java/android/net/vpn/VpnManager.java b/vpn/java/android/net/vpn/VpnManager.java deleted file mode 100644 index 02486bb..0000000 --- a/vpn/java/android/net/vpn/VpnManager.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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.net.vpn; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Environment; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemProperties; -import android.util.Log; - -import com.android.server.vpn.VpnServiceBinder; - -/** - * The class provides interface to manage all VPN-related tasks, including: - * <ul> - * <li>The list of supported VPN types. - * <li>API's to start/stop the service of a particular type. - * <li>API's to start the settings activity. - * <li>API's to create a profile. - * <li>API's to register/unregister a connectivity receiver and the keys to - * access the fields in a connectivity broadcast event. - * </ul> - * {@hide} - */ -public class VpnManager { - /** Key to the profile name of a connectivity broadcast event. */ - public static final String BROADCAST_PROFILE_NAME = "profile_name"; - /** Key to the connectivity state of a connectivity broadcast event. */ - public static final String BROADCAST_CONNECTION_STATE = "connection_state"; - /** Key to the error code of a connectivity broadcast event. */ - public static final String BROADCAST_ERROR_CODE = "err"; - /** Error code to indicate an error from authentication. */ - public static final int VPN_ERROR_AUTH = 51; - /** Error code to indicate the connection attempt failed. */ - public static final int VPN_ERROR_CONNECTION_FAILED = 101; - /** Error code to indicate the server is not known. */ - public static final int VPN_ERROR_UNKNOWN_SERVER = 102; - /** Error code to indicate an error from challenge response. */ - public static final int VPN_ERROR_CHALLENGE = 5; - /** Error code to indicate an error of remote server hanging up. */ - public static final int VPN_ERROR_REMOTE_HUNG_UP = 7; - /** Error code to indicate an error of remote PPP server hanging up. */ - public static final int VPN_ERROR_REMOTE_PPP_HUNG_UP = 48; - /** Error code to indicate a PPP negotiation error. */ - public static final int VPN_ERROR_PPP_NEGOTIATION_FAILED = 42; - /** Error code to indicate an error of losing connectivity. */ - public static final int VPN_ERROR_CONNECTION_LOST = 103; - /** Largest error code used by VPN. */ - public static final int VPN_ERROR_LARGEST = 200; - /** Error code to indicate a successful connection. */ - public static final int VPN_ERROR_NO_ERROR = 0; - - public static final String PROFILES_PATH = "/misc/vpn/profiles"; - - private static final String PACKAGE_PREFIX = - VpnManager.class.getPackage().getName() + "."; - - // Action for broadcasting a connectivity state. - private static final String ACTION_VPN_CONNECTIVITY = "vpn.connectivity"; - - private static final String VPN_SERVICE_NAME = "vpn"; - - // Action to start VPN settings - private static final String ACTION_VPN_SETTINGS = - PACKAGE_PREFIX + "SETTINGS"; - - public static final String TAG = VpnManager.class.getSimpleName(); - - // TODO(oam): Test VPN when EFS is enabled (will do later)... - public static String getProfilePath() { - // This call will return the correct path if Encrypted FS is enabled or not. - return Environment.getSecureDataDirectory().getPath() + PROFILES_PATH; - } - - /** - * Returns all supported VPN types. - */ - public static VpnType[] getSupportedVpnTypes() { - return VpnType.values(); - } - - public static void startVpnService(Context c) { - ServiceManager.addService(VPN_SERVICE_NAME, new VpnServiceBinder(c)); - } - - private Context mContext; - private IVpnService mVpnService; - - /** - * Creates a manager object with the specified context. - */ - public VpnManager(Context c) { - mContext = c; - createVpnServiceClient(); - } - - private void createVpnServiceClient() { - IBinder b = ServiceManager.getService(VPN_SERVICE_NAME); - mVpnService = IVpnService.Stub.asInterface(b); - } - - /** - * Sets up a VPN connection. - * @param profile the profile object - * @param username the username for authentication - * @param password the corresponding password for authentication - * @return true if VPN is successfully connected - */ - public boolean connect(VpnProfile p, String username, String password) { - try { - return mVpnService.connect(p, username, password); - } catch (RemoteException e) { - Log.e(TAG, "connect()", e); - return false; - } - } - - /** - * Tears down the VPN connection. - */ - public void disconnect() { - try { - mVpnService.disconnect(); - } catch (RemoteException e) { - Log.e(TAG, "disconnect()", e); - } - } - - /** - * Gets the the current connection state. - */ - public VpnState getState(VpnProfile p) { - try { - return Enum.valueOf(VpnState.class, mVpnService.getState(p)); - } catch (RemoteException e) { - Log.e(TAG, "getState()", e); - return VpnState.IDLE; - } - } - - /** - * Returns the idle state. - * @return true if the system is not connecting/connected to a VPN - */ - public boolean isIdle() { - try { - return mVpnService.isIdle(); - } catch (RemoteException e) { - Log.e(TAG, "isIdle()", e); - return true; - } - } - - /** - * Creates a VPN profile of the specified type. - * - * @param type the VPN type - * @return the profile object - */ - public VpnProfile createVpnProfile(VpnType type) { - return createVpnProfile(type, false); - } - - /** - * Creates a VPN profile of the specified type. - * - * @param type the VPN type - * @param customized true if the profile is custom made - * @return the profile object - */ - public VpnProfile createVpnProfile(VpnType type, boolean customized) { - try { - VpnProfile p = (VpnProfile) type.getProfileClass().newInstance(); - p.setCustomized(customized); - return p; - } catch (InstantiationException e) { - return null; - } catch (IllegalAccessException e) { - return null; - } - } - - /** Broadcasts the connectivity state of the specified profile. */ - public void broadcastConnectivity(String profileName, VpnState s) { - broadcastConnectivity(profileName, s, VPN_ERROR_NO_ERROR); - } - - /** Broadcasts the connectivity state with an error code. */ - public void broadcastConnectivity(String profileName, VpnState s, - int error) { - Intent intent = new Intent(ACTION_VPN_CONNECTIVITY); - intent.putExtra(BROADCAST_PROFILE_NAME, profileName); - intent.putExtra(BROADCAST_CONNECTION_STATE, s); - if (error != VPN_ERROR_NO_ERROR) { - intent.putExtra(BROADCAST_ERROR_CODE, error); - } - mContext.sendBroadcast(intent); - } - - public void registerConnectivityReceiver(BroadcastReceiver r) { - IntentFilter filter = new IntentFilter(); - filter.addAction(VpnManager.ACTION_VPN_CONNECTIVITY); - mContext.registerReceiver(r, filter); - } - - public void unregisterConnectivityReceiver(BroadcastReceiver r) { - mContext.unregisterReceiver(r); - } - - /** Starts the VPN settings activity. */ - public void startSettingsActivity() { - Intent intent = new Intent(ACTION_VPN_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(intent); - } - - /** Creates an intent to start the VPN settings activity. */ - public Intent createSettingsActivityIntent() { - Intent intent = new Intent(ACTION_VPN_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return intent; - } -} diff --git a/vpn/java/android/net/vpn/VpnProfile.aidl b/vpn/java/android/net/vpn/VpnProfile.aidl deleted file mode 100644 index edeaef0..0000000 --- a/vpn/java/android/net/vpn/VpnProfile.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.net.vpn; - -parcelable VpnProfile; diff --git a/vpn/java/android/net/vpn/VpnProfile.java b/vpn/java/android/net/vpn/VpnProfile.java deleted file mode 100644 index bd6c809..0000000 --- a/vpn/java/android/net/vpn/VpnProfile.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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.net.vpn; - -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; - -import java.io.IOException; -import java.io.Serializable; - -/** - * A VPN profile. - * {@hide} - */ -public abstract class VpnProfile implements Parcelable, Serializable { - private static final long serialVersionUID = 1L; - private String mName; // unique display name - private String mId; // unique identifier - private String mServerName; // VPN server name - private String mDomainSuffices; // space separated list - private String mRouteList; // space separated list - private String mSavedUsername; - private boolean mIsCustomized; - private transient VpnState mState = VpnState.IDLE; - - /** Sets a user-friendly name for this profile. */ - public void setName(String name) { - mName = name; - } - - public String getName() { - return mName; - } - - /** - * Sets an ID for this profile. The caller should make sure the - * uniqueness of the ID. - */ - public void setId(String id) { - mId = id; - } - - public String getId() { - return mId; - } - - /** - * Sets the name of the VPN server. Used for DNS lookup. - */ - public void setServerName(String name) { - mServerName = name; - } - - public String getServerName() { - return mServerName; - } - - /** - * Sets the domain suffices for DNS resolution. - * - * @param entries a comma-separated list of domain suffices - */ - public void setDomainSuffices(String entries) { - mDomainSuffices = entries; - } - - public String getDomainSuffices() { - return mDomainSuffices; - } - - /** - * Sets the routing info for this VPN connection. - * - * @param entries a comma-separated list of routes; each entry is in the - * format of "(network address)/(network mask)" - */ - public void setRouteList(String entries) { - mRouteList = entries; - } - - public String getRouteList() { - return mRouteList; - } - - public void setSavedUsername(String name) { - mSavedUsername = name; - } - - public String getSavedUsername() { - return mSavedUsername; - } - - public void setState(VpnState state) { - mState = state; - } - - public VpnState getState() { - return ((mState == null) ? VpnState.IDLE : mState); - } - - public boolean isIdle() { - return (mState == VpnState.IDLE); - } - - /** - * Returns whether this profile is custom made (as opposed to being - * created by provided user interface). - */ - public boolean isCustomized() { - return mIsCustomized; - } - - /** - * Returns the VPN type of the profile. - */ - public abstract VpnType getType(); - - void setCustomized(boolean customized) { - mIsCustomized = customized; - } - - protected void readFromParcel(Parcel in) { - mName = in.readString(); - mId = in.readString(); - mServerName = in.readString(); - mDomainSuffices = in.readString(); - mRouteList = in.readString(); - mSavedUsername = in.readString(); - } - - public static final Parcelable.Creator<VpnProfile> CREATOR = - new Parcelable.Creator<VpnProfile>() { - public VpnProfile createFromParcel(Parcel in) { - VpnType type = Enum.valueOf(VpnType.class, in.readString()); - boolean customized = in.readInt() > 0; - VpnProfile p = new VpnManager(null).createVpnProfile(type, - customized); - if (p == null) return null; - p.readFromParcel(in); - return p; - } - - public VpnProfile[] newArray(int size) { - return new VpnProfile[size]; - } - }; - - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(getType().toString()); - parcel.writeInt(mIsCustomized ? 1 : 0); - parcel.writeString(mName); - parcel.writeString(mId); - parcel.writeString(mServerName); - parcel.writeString(mDomainSuffices); - parcel.writeString(mRouteList); - parcel.writeString(mSavedUsername); - } - - public int describeContents() { - return 0; - } -} diff --git a/vpn/java/android/net/vpn/VpnState.java b/vpn/java/android/net/vpn/VpnState.java deleted file mode 100644 index 6e61f9c..0000000 --- a/vpn/java/android/net/vpn/VpnState.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.net.vpn; - -/** - * Enumeration of all VPN states. - * - * A normal VPN connection lifetime starts in {@link IDLE}. When a new - * connection is about to be set up, it goes to {@link CONNECTING} and then - * {@link CONNECTED} if successful; back to {@link IDLE} if failed. - * When the connection is about to be torn down, it goes to - * {@link DISCONNECTING} and then {@link IDLE}. - * {@link CANCELLED} is a state when a VPN connection attempt is aborted, and - * is in transition to {@link IDLE}. - * The {@link UNUSABLE} state indicates that the profile is not in a state for - * connecting due to possibly the integrity of the fields or another profile is - * connecting etc. - * The {@link UNKNOWN} state indicates that the profile state is to be - * determined. - * {@hide} - */ -public enum VpnState { - CONNECTING, DISCONNECTING, CANCELLED, CONNECTED, IDLE, UNUSABLE, UNKNOWN -} diff --git a/vpn/java/android/net/vpn/VpnType.java b/vpn/java/android/net/vpn/VpnType.java deleted file mode 100644 index 356f8b1..0000000 --- a/vpn/java/android/net/vpn/VpnType.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.net.vpn; - -import com.android.internal.R; - -/** - * Enumeration of all supported VPN types. - * {@hide} - */ -public enum VpnType { - PPTP("PPTP", R.string.pptp_vpn_description, PptpProfile.class), - L2TP("L2TP", R.string.l2tp_vpn_description, L2tpProfile.class), - L2TP_IPSEC_PSK("L2TP/IPSec PSK", R.string.l2tp_ipsec_psk_vpn_description, - L2tpIpsecPskProfile.class), - L2TP_IPSEC("L2TP/IPSec CRT", R.string.l2tp_ipsec_crt_vpn_description, - L2tpIpsecProfile.class); - - private String mDisplayName; - private int mDescriptionId; - private Class<? extends VpnProfile> mClass; - - VpnType(String displayName, int descriptionId, - Class<? extends VpnProfile> klass) { - mDisplayName = displayName; - mDescriptionId = descriptionId; - mClass = klass; - } - - public String getDisplayName() { - return mDisplayName; - } - - public int getDescriptionId() { - return mDescriptionId; - } - - public Class<? extends VpnProfile> getProfileClass() { - return mClass; - } -} diff --git a/vpn/java/com/android/server/vpn/DaemonProxy.java b/vpn/java/com/android/server/vpn/DaemonProxy.java deleted file mode 100644 index 289ee45..0000000 --- a/vpn/java/com/android/server/vpn/DaemonProxy.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import android.net.LocalSocket; -import android.net.LocalSocketAddress; -import android.net.vpn.VpnManager; -import android.os.SystemProperties; -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; - -/** - * Proxy to start, stop and interact with a VPN daemon. - * The daemon is expected to accept connection through Unix domain socket. - * When the proxy successfully starts the daemon, it will establish a socket - * connection with the daemon, to both send commands to the daemon and receive - * response and connecting error code from the daemon. - */ -class DaemonProxy implements Serializable { - private static final long serialVersionUID = 1L; - private static final boolean DBG = true; - - private static final int WAITING_TIME = 15; // sec - - private static final String SVC_STATE_CMD_PREFIX = "init.svc."; - private static final String SVC_START_CMD = "ctl.start"; - private static final String SVC_STOP_CMD = "ctl.stop"; - private static final String SVC_STATE_RUNNING = "running"; - private static final String SVC_STATE_STOPPED = "stopped"; - - private static final int END_OF_ARGUMENTS = 255; - - private String mName; - private String mTag; - private transient LocalSocket mControlSocket; - - /** - * Creates a proxy of the specified daemon. - * @param daemonName name of the daemon - */ - DaemonProxy(String daemonName) { - mName = daemonName; - mTag = "SProxy_" + daemonName; - } - - String getName() { - return mName; - } - - void start() throws IOException { - String svc = mName; - - Log.i(mTag, "Start VPN daemon: " + svc); - SystemProperties.set(SVC_START_CMD, svc); - - if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) { - throw new IOException("cannot start service: " + svc); - } else { - mControlSocket = createServiceSocket(); - } - } - - void sendCommand(String ...args) throws IOException { - OutputStream out = getControlSocketOutput(); - for (String arg : args) outputString(out, arg); - out.write(END_OF_ARGUMENTS); - out.flush(); - - int result = getResultFromSocket(true); - if (result != args.length) { - throw new IOException("socket error, result from service: " - + result); - } - } - - // returns 0 if nothing is in the receive buffer - int getResultFromSocket() throws IOException { - return getResultFromSocket(false); - } - - void closeControlSocket() { - if (mControlSocket == null) return; - try { - mControlSocket.close(); - } catch (IOException e) { - Log.w(mTag, "close control socket", e); - } finally { - mControlSocket = null; - } - } - - void stop() { - String svc = mName; - Log.i(mTag, "Stop VPN daemon: " + svc); - SystemProperties.set(SVC_STOP_CMD, svc); - boolean success = blockUntil(SVC_STATE_STOPPED, 5); - if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success); - } - - boolean isStopped() { - String cmd = SVC_STATE_CMD_PREFIX + mName; - return SVC_STATE_STOPPED.equals(SystemProperties.get(cmd)); - } - - private int getResultFromSocket(boolean blocking) throws IOException { - LocalSocket s = mControlSocket; - if (s == null) return 0; - InputStream in = s.getInputStream(); - if (!blocking && in.available() == 0) return 0; - - int data = in.read(); - Log.i(mTag, "got data from control socket: " + data); - - return data; - } - - private LocalSocket createServiceSocket() throws IOException { - LocalSocket s = new LocalSocket(); - LocalSocketAddress a = new LocalSocketAddress(mName, - LocalSocketAddress.Namespace.RESERVED); - - // try a few times in case the service has not listen()ed - IOException excp = null; - for (int i = 0; i < 10; i++) { - try { - s.connect(a); - return s; - } catch (IOException e) { - if (DBG) Log.d(mTag, "service not yet listen()ing; try again"); - excp = e; - sleep(500); - } - } - throw excp; - } - - private OutputStream getControlSocketOutput() throws IOException { - if (mControlSocket != null) { - return mControlSocket.getOutputStream(); - } else { - throw new IOException("no control socket available"); - } - } - - /** - * Waits for the process to be in the expected state. The method returns - * false if after the specified duration (in seconds), the process is still - * not in the expected state. - */ - private boolean blockUntil(String expectedState, int waitTime) { - String cmd = SVC_STATE_CMD_PREFIX + mName; - int sleepTime = 200; // ms - int n = waitTime * 1000 / sleepTime; - for (int i = 0; i < n; i++) { - if (expectedState.equals(SystemProperties.get(cmd))) { - if (DBG) { - Log.d(mTag, mName + " is " + expectedState + " after " - + (i * sleepTime) + " msec"); - } - break; - } - sleep(sleepTime); - } - return expectedState.equals(SystemProperties.get(cmd)); - } - - private void outputString(OutputStream out, String s) throws IOException { - byte[] bytes = s.getBytes(); - out.write(bytes.length); - out.write(bytes); - out.flush(); - } - - private void sleep(int msec) { - try { - Thread.currentThread().sleep(msec); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } -} diff --git a/vpn/java/com/android/server/vpn/L2tpIpsecPskService.java b/vpn/java/com/android/server/vpn/L2tpIpsecPskService.java deleted file mode 100644 index 50e0de1..0000000 --- a/vpn/java/com/android/server/vpn/L2tpIpsecPskService.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import android.net.vpn.L2tpIpsecPskProfile; - -import java.io.IOException; - -/** - * The service that manages the preshared key based L2TP-over-IPSec VPN - * connection. - */ -class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> { - private static final String IPSEC = "racoon"; - - @Override - protected void connect(String serverIp, String username, String password) - throws IOException { - L2tpIpsecPskProfile p = getProfile(); - VpnDaemons daemons = getDaemons(); - - // IPSEC - daemons.startIpsecForL2tp(serverIp, p.getPresharedKey()) - .closeControlSocket(); - - sleep(2000); // 2 seconds - - // L2TP - daemons.startL2tp(serverIp, - (p.isSecretEnabled() ? p.getSecretString() : null), - username, password); - } -} diff --git a/vpn/java/com/android/server/vpn/L2tpIpsecService.java b/vpn/java/com/android/server/vpn/L2tpIpsecService.java deleted file mode 100644 index 663b0e8..0000000 --- a/vpn/java/com/android/server/vpn/L2tpIpsecService.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import android.net.vpn.L2tpIpsecProfile; -import android.security.Credentials; - -import java.io.IOException; - -/** - * The service that manages the certificate based L2TP-over-IPSec VPN connection. - */ -class L2tpIpsecService extends VpnService<L2tpIpsecProfile> { - private static final String IPSEC = "racoon"; - - @Override - protected void connect(String serverIp, String username, String password) - throws IOException { - L2tpIpsecProfile p = getProfile(); - VpnDaemons daemons = getDaemons(); - - // IPSEC - DaemonProxy ipsec = daemons.startIpsecForL2tp(serverIp, - Credentials.USER_PRIVATE_KEY + p.getUserCertificate(), - Credentials.USER_CERTIFICATE + p.getUserCertificate(), - Credentials.CA_CERTIFICATE + p.getCaCertificate()); - ipsec.closeControlSocket(); - - sleep(2000); // 2 seconds - - // L2TP - daemons.startL2tp(serverIp, - (p.isSecretEnabled() ? p.getSecretString() : null), - username, password); - } -} diff --git a/vpn/java/com/android/server/vpn/L2tpService.java b/vpn/java/com/android/server/vpn/L2tpService.java deleted file mode 100644 index 784a366..0000000 --- a/vpn/java/com/android/server/vpn/L2tpService.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import android.net.vpn.L2tpProfile; - -import java.io.IOException; - -/** - * The service that manages the L2TP VPN connection. - */ -class L2tpService extends VpnService<L2tpProfile> { - @Override - protected void connect(String serverIp, String username, String password) - throws IOException { - L2tpProfile p = getProfile(); - getDaemons().startL2tp(serverIp, - (p.isSecretEnabled() ? p.getSecretString() : null), - username, password); - } -} diff --git a/vpn/java/com/android/server/vpn/PptpService.java b/vpn/java/com/android/server/vpn/PptpService.java deleted file mode 100644 index de12710..0000000 --- a/vpn/java/com/android/server/vpn/PptpService.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import android.net.vpn.PptpProfile; - -import java.io.IOException; - -/** - * The service that manages the PPTP VPN connection. - */ -class PptpService extends VpnService<PptpProfile> { - @Override - protected void connect(String serverIp, String username, String password) - throws IOException { - PptpProfile p = getProfile(); - getDaemons().startPptp(serverIp, username, password, - p.isEncryptionEnabled()); - } -} diff --git a/vpn/java/com/android/server/vpn/VpnConnectingError.java b/vpn/java/com/android/server/vpn/VpnConnectingError.java deleted file mode 100644 index 3c4ec7d..0000000 --- a/vpn/java/com/android/server/vpn/VpnConnectingError.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import java.io.IOException; - -/** - * Exception thrown when a connecting attempt fails. - */ -class VpnConnectingError extends IOException { - private int mErrorCode; - - VpnConnectingError(int errorCode) { - super("Connecting error: " + errorCode); - mErrorCode = errorCode; - } - - int getErrorCode() { - return mErrorCode; - } -} diff --git a/vpn/java/com/android/server/vpn/VpnDaemons.java b/vpn/java/com/android/server/vpn/VpnDaemons.java deleted file mode 100644 index 499195f..0000000 --- a/vpn/java/com/android/server/vpn/VpnDaemons.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import android.util.Log; - -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * A helper class for managing native VPN daemons. - */ -class VpnDaemons implements Serializable { - static final long serialVersionUID = 1L; - private final String TAG = VpnDaemons.class.getSimpleName(); - - private static final String MTPD = "mtpd"; - private static final String IPSEC = "racoon"; - - private static final String L2TP = "l2tp"; - private static final String L2TP_PORT = "1701"; - - private static final String PPTP = "pptp"; - private static final String PPTP_PORT = "1723"; - - private static final String VPN_LINKNAME = "vpn"; - private static final String PPP_ARGS_SEPARATOR = ""; - - private List<DaemonProxy> mDaemonList = new ArrayList<DaemonProxy>(); - - public DaemonProxy startL2tp(String serverIp, String secret, - String username, String password) throws IOException { - return startMtpd(L2TP, serverIp, L2TP_PORT, secret, username, password, - false); - } - - public DaemonProxy startPptp(String serverIp, String username, - String password, boolean encryption) throws IOException { - return startMtpd(PPTP, serverIp, PPTP_PORT, null, username, password, - encryption); - } - - public DaemonProxy startIpsecForL2tp(String serverIp, String pskKey) - throws IOException { - DaemonProxy ipsec = startDaemon(IPSEC); - ipsec.sendCommand(serverIp, L2TP_PORT, pskKey); - return ipsec; - } - - public DaemonProxy startIpsecForL2tp(String serverIp, String userKeyKey, - String userCertKey, String caCertKey) throws IOException { - DaemonProxy ipsec = startDaemon(IPSEC); - ipsec.sendCommand(serverIp, L2TP_PORT, userKeyKey, userCertKey, - caCertKey); - return ipsec; - } - - public synchronized void stopAll() { - new DaemonProxy(MTPD).stop(); - new DaemonProxy(IPSEC).stop(); - } - - public synchronized void closeSockets() { - for (DaemonProxy s : mDaemonList) s.closeControlSocket(); - } - - public synchronized boolean anyDaemonStopped() { - for (DaemonProxy s : mDaemonList) { - if (s.isStopped()) { - Log.w(TAG, " VPN daemon gone: " + s.getName()); - return true; - } - } - return false; - } - - public synchronized int getSocketError() { - for (DaemonProxy s : mDaemonList) { - int errCode = getResultFromSocket(s); - if (errCode != 0) return errCode; - } - return 0; - } - - private synchronized DaemonProxy startDaemon(String daemonName) - throws IOException { - DaemonProxy daemon = new DaemonProxy(daemonName); - mDaemonList.add(daemon); - daemon.start(); - return daemon; - } - - private int getResultFromSocket(DaemonProxy s) { - try { - return s.getResultFromSocket(); - } catch (IOException e) { - return -1; - } - } - - private DaemonProxy startMtpd(String protocol, - String serverIp, String port, String secret, String username, - String password, boolean encryption) throws IOException { - ArrayList<String> args = new ArrayList<String>(); - args.addAll(Arrays.asList(protocol, serverIp, port)); - if (secret != null) args.add(secret); - args.add(PPP_ARGS_SEPARATOR); - addPppArguments(args, serverIp, username, password, encryption); - - DaemonProxy mtpd = startDaemon(MTPD); - mtpd.sendCommand(args.toArray(new String[args.size()])); - return mtpd; - } - - private static void addPppArguments(ArrayList<String> args, String serverIp, - String username, String password, boolean encryption) - throws IOException { - args.addAll(Arrays.asList( - "linkname", VPN_LINKNAME, - "name", username, - "password", password, - "refuse-eap", "nodefaultroute", "usepeerdns", - "idle", "1800", - "mtu", "1400", - "mru", "1400")); - if (encryption) { - args.add("+mppe"); - } - } -} diff --git a/vpn/java/com/android/server/vpn/VpnService.java b/vpn/java/com/android/server/vpn/VpnService.java deleted file mode 100644 index 4966c06..0000000 --- a/vpn/java/com/android/server/vpn/VpnService.java +++ /dev/null @@ -1,477 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.net.vpn.VpnManager; -import android.net.vpn.VpnProfile; -import android.net.vpn.VpnState; -import android.os.SystemProperties; -import android.text.TextUtils; -import android.util.Log; - -import com.android.internal.R; - -import java.io.IOException; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.UnknownHostException; - -/** - * The service base class for managing a type of VPN connection. - */ -abstract class VpnService<E extends VpnProfile> { - private static final boolean DBG = true; - private static final int NOTIFICATION_ID = 1; - - private static final String DNS1 = "net.dns1"; - private static final String DNS2 = "net.dns2"; - private static final String VPN_DNS1 = "vpn.dns1"; - private static final String VPN_DNS2 = "vpn.dns2"; - private static final String VPN_STATUS = "vpn.status"; - private static final String VPN_IS_UP = "ok"; - private static final String VPN_IS_DOWN = "down"; - - private static final String REMOTE_IP = "net.ipremote"; - private static final String DNS_DOMAIN_SUFFICES = "net.dns.search"; - - private final String TAG = VpnService.class.getSimpleName(); - - E mProfile; - transient Context mContext; - - private VpnState mState = VpnState.IDLE; - private Throwable mError; - - // connection settings - private String mOriginalDns1; - private String mOriginalDns2; - private String mOriginalDomainSuffices; - private String mLocalIp; - private String mLocalIf; - - private long mStartTime; // VPN connection start time - - // for helping managing daemons - private VpnDaemons mDaemons = new VpnDaemons(); - - // for helping showing, updating notification - private transient NotificationHelper mNotification; - - /** - * Establishes a VPN connection with the specified username and password. - */ - protected abstract void connect(String serverIp, String username, - String password) throws IOException; - - /** - * Returns the daemons management class for this service object. - */ - protected VpnDaemons getDaemons() { - return mDaemons; - } - - /** - * Returns the VPN profile associated with the connection. - */ - protected E getProfile() { - return mProfile; - } - - /** - * Returns the IP address of the specified host name. - */ - protected String getIp(String hostName) throws IOException { - return InetAddress.getByName(hostName).getHostAddress(); - } - - void setContext(Context context, E profile) { - mProfile = profile; - mContext = context; - mNotification = new NotificationHelper(); - - if (VpnState.CONNECTED.equals(mState)) { - Log.i("VpnService", " recovered: " + mProfile.getName()); - startConnectivityMonitor(); - } - } - - VpnState getState() { - return mState; - } - - boolean isIdle() { - return (mState == VpnState.IDLE); - } - - synchronized boolean onConnect(String username, String password) { - try { - setState(VpnState.CONNECTING); - - mDaemons.stopAll(); - String serverIp = getIp(getProfile().getServerName()); - saveLocalIpAndInterface(serverIp); - onBeforeConnect(); - connect(serverIp, username, password); - waitUntilConnectedOrTimedout(); - return true; - } catch (Throwable e) { - onError(e); - return false; - } - } - - synchronized void onDisconnect() { - try { - Log.i(TAG, "disconnecting VPN..."); - setState(VpnState.DISCONNECTING); - mNotification.showDisconnect(); - - mDaemons.stopAll(); - } catch (Throwable e) { - Log.e(TAG, "onDisconnect()", e); - } finally { - onFinalCleanUp(); - } - } - - private void onError(Throwable error) { - // error may occur during or after connection setup - // and it may be due to one or all services gone - if (mError != null) { - Log.w(TAG, " multiple errors occur, record the last one: " - + error); - } - Log.e(TAG, "onError()", error); - mError = error; - onDisconnect(); - } - - private void onError(int errorCode) { - onError(new VpnConnectingError(errorCode)); - } - - - private void onBeforeConnect() throws IOException { - mNotification.disableNotification(); - - SystemProperties.set(VPN_DNS1, ""); - SystemProperties.set(VPN_DNS2, ""); - SystemProperties.set(VPN_STATUS, VPN_IS_DOWN); - if (DBG) { - Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_STATUS)); - } - } - - private void waitUntilConnectedOrTimedout() throws IOException { - sleep(2000); // 2 seconds - for (int i = 0; i < 80; i++) { - if (mState != VpnState.CONNECTING) { - break; - } else if (VPN_IS_UP.equals( - SystemProperties.get(VPN_STATUS))) { - onConnected(); - return; - } else { - int err = mDaemons.getSocketError(); - if (err != 0) { - onError(err); - return; - } - } - sleep(500); // 0.5 second - } - - if (mState == VpnState.CONNECTING) { - onError(new IOException("Connecting timed out")); - } - } - - private synchronized void onConnected() throws IOException { - if (DBG) Log.d(TAG, "onConnected()"); - - mDaemons.closeSockets(); - saveOriginalDns(); - saveAndSetDomainSuffices(); - - mStartTime = System.currentTimeMillis(); - - setState(VpnState.CONNECTED); - setVpnDns(); - - startConnectivityMonitor(); - } - - private synchronized void onFinalCleanUp() { - if (DBG) Log.d(TAG, "onFinalCleanUp()"); - - if (mState == VpnState.IDLE) return; - - // keep the notification when error occurs - if (!anyError()) mNotification.disableNotification(); - - restoreOriginalDns(); - restoreOriginalDomainSuffices(); - setState(VpnState.IDLE); - - SystemProperties.set(VPN_STATUS, VPN_IS_DOWN); - } - - private boolean anyError() { - return (mError != null); - } - - private void restoreOriginalDns() { - // restore only if they are not overridden - String vpnDns1 = SystemProperties.get(VPN_DNS1); - if (vpnDns1.equals(SystemProperties.get(DNS1))) { - Log.i(TAG, String.format("restore original dns prop: %s --> %s", - SystemProperties.get(DNS1), mOriginalDns1)); - Log.i(TAG, String.format("restore original dns prop: %s --> %s", - SystemProperties.get(DNS2), mOriginalDns2)); - SystemProperties.set(DNS1, mOriginalDns1); - SystemProperties.set(DNS2, mOriginalDns2); - } - } - - private void saveOriginalDns() { - mOriginalDns1 = SystemProperties.get(DNS1); - mOriginalDns2 = SystemProperties.get(DNS2); - Log.i(TAG, String.format("save original dns prop: %s, %s", - mOriginalDns1, mOriginalDns2)); - } - - private void setVpnDns() { - String vpnDns1 = SystemProperties.get(VPN_DNS1); - String vpnDns2 = SystemProperties.get(VPN_DNS2); - SystemProperties.set(DNS1, vpnDns1); - SystemProperties.set(DNS2, vpnDns2); - Log.i(TAG, String.format("set vpn dns prop: %s, %s", - vpnDns1, vpnDns2)); - } - - private void saveAndSetDomainSuffices() { - mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES); - Log.i(TAG, "save original suffices: " + mOriginalDomainSuffices); - String list = mProfile.getDomainSuffices(); - if (!TextUtils.isEmpty(list)) { - SystemProperties.set(DNS_DOMAIN_SUFFICES, list); - } - } - - private void restoreOriginalDomainSuffices() { - Log.i(TAG, "restore original suffices --> " + mOriginalDomainSuffices); - SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices); - } - - private void setState(VpnState newState) { - mState = newState; - broadcastConnectivity(newState); - } - - private void broadcastConnectivity(VpnState s) { - VpnManager m = new VpnManager(mContext); - Throwable err = mError; - if ((s == VpnState.IDLE) && (err != null)) { - if (err instanceof UnknownHostException) { - m.broadcastConnectivity(mProfile.getName(), s, - VpnManager.VPN_ERROR_UNKNOWN_SERVER); - } else if (err instanceof VpnConnectingError) { - m.broadcastConnectivity(mProfile.getName(), s, - ((VpnConnectingError) err).getErrorCode()); - } else if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) { - m.broadcastConnectivity(mProfile.getName(), s, - VpnManager.VPN_ERROR_CONNECTION_LOST); - } else { - m.broadcastConnectivity(mProfile.getName(), s, - VpnManager.VPN_ERROR_CONNECTION_FAILED); - } - } else { - m.broadcastConnectivity(mProfile.getName(), s); - } - } - - private void startConnectivityMonitor() { - new Thread(new Runnable() { - public void run() { - Log.i(TAG, "VPN connectivity monitor running"); - try { - mNotification.update(mStartTime); // to pop up notification - for (int i = 10; ; i--) { - long now = System.currentTimeMillis(); - - boolean heavyCheck = i == 0; - synchronized (VpnService.this) { - if (mState != VpnState.CONNECTED) break; - mNotification.update(now); - - if (heavyCheck) { - i = 10; - if (checkConnectivity()) checkDns(); - } - long t = 1000L - System.currentTimeMillis() + now; - if (t > 100L) VpnService.this.wait(t); - } - } - } catch (InterruptedException e) { - onError(e); - } - Log.i(TAG, "VPN connectivity monitor stopped"); - } - }).start(); - } - - private void saveLocalIpAndInterface(String serverIp) throws IOException { - DatagramSocket s = new DatagramSocket(); - int port = 80; // arbitrary - s.connect(InetAddress.getByName(serverIp), port); - InetAddress localIp = s.getLocalAddress(); - mLocalIp = localIp.getHostAddress(); - NetworkInterface localIf = NetworkInterface.getByInetAddress(localIp); - mLocalIf = (localIf == null) ? null : localIf.getName(); - if (TextUtils.isEmpty(mLocalIf)) { - throw new IOException("Local interface is empty!"); - } - if (DBG) { - Log.d(TAG, " Local IP: " + mLocalIp + ", if: " + mLocalIf); - } - } - - // returns false if vpn connectivity is broken - private boolean checkConnectivity() { - if (mDaemons.anyDaemonStopped() || isLocalIpChanged()) { - onError(new IOException("Connectivity lost")); - return false; - } else { - return true; - } - } - - private void checkDns() { - String dns1 = SystemProperties.get(DNS1); - String vpnDns1 = SystemProperties.get(VPN_DNS1); - if (!dns1.equals(vpnDns1) && dns1.equals(mOriginalDns1)) { - // dhcp expires? - setVpnDns(); - } - } - - private boolean isLocalIpChanged() { - try { - InetAddress localIp = InetAddress.getByName(mLocalIp); - NetworkInterface localIf = - NetworkInterface.getByInetAddress(localIp); - if (localIf == null || !mLocalIf.equals(localIf.getName())) { - Log.w(TAG, " local If changed from " + mLocalIf - + " to " + localIf); - return true; - } else { - return false; - } - } catch (IOException e) { - Log.w(TAG, "isLocalIpChanged()", e); - return true; - } - } - - protected void sleep(int ms) { - try { - Thread.currentThread().sleep(ms); - } catch (InterruptedException e) { - } - } - - // Helper class for showing, updating notification. - private class NotificationHelper { - private NotificationManager mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - private Notification mNotification = - new Notification(R.drawable.vpn_connected, null, 0L); - private PendingIntent mPendingIntent = PendingIntent.getActivity( - mContext, 0, - new VpnManager(mContext).createSettingsActivityIntent(), 0); - private String mConnectedTitle; - - void update(long now) { - Notification n = mNotification; - if (now == mStartTime) { - // to pop up the notification for the first time - n.when = mStartTime; - n.tickerText = mConnectedTitle = getNotificationTitle(true); - } else { - n.tickerText = null; - } - n.setLatestEventInfo(mContext, mConnectedTitle, - getConnectedNotificationMessage(now), - mPendingIntent); - n.flags |= Notification.FLAG_NO_CLEAR; - n.flags |= Notification.FLAG_ONGOING_EVENT; - enableNotification(n); - } - - void showDisconnect() { - String title = getNotificationTitle(false); - Notification n = new Notification(R.drawable.vpn_disconnected, - title, System.currentTimeMillis()); - n.setLatestEventInfo(mContext, title, - getDisconnectedNotificationMessage(), - mPendingIntent); - n.flags |= Notification.FLAG_AUTO_CANCEL; - disableNotification(); - enableNotification(n); - } - - void disableNotification() { - mNotificationManager.cancel(NOTIFICATION_ID); - } - - private void enableNotification(Notification n) { - mNotificationManager.notify(NOTIFICATION_ID, n); - } - - private String getNotificationTitle(boolean connected) { - String formatString = connected - ? mContext.getString( - R.string.vpn_notification_title_connected) - : mContext.getString( - R.string.vpn_notification_title_disconnected); - return String.format(formatString, mProfile.getName()); - } - - private String getFormattedTime(int duration) { - int hours = duration / 3600; - StringBuilder sb = new StringBuilder(); - if (hours > 0) sb.append(hours).append(':'); - sb.append(String.format("%02d:%02d", (duration % 3600 / 60), - (duration % 60))); - return sb.toString(); - } - - private String getConnectedNotificationMessage(long now) { - return getFormattedTime((int) (now - mStartTime) / 1000); - } - - private String getDisconnectedNotificationMessage() { - return mContext.getString( - R.string.vpn_notification_hint_disconnected); - } - } -} diff --git a/vpn/java/com/android/server/vpn/VpnServiceBinder.java b/vpn/java/com/android/server/vpn/VpnServiceBinder.java deleted file mode 100644 index c474ff9..0000000 --- a/vpn/java/com/android/server/vpn/VpnServiceBinder.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 com.android.server.vpn; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.net.vpn.IVpnService; -import android.net.vpn.L2tpIpsecProfile; -import android.net.vpn.L2tpIpsecPskProfile; -import android.net.vpn.L2tpProfile; -import android.net.vpn.PptpProfile; -import android.net.vpn.VpnManager; -import android.net.vpn.VpnProfile; -import android.net.vpn.VpnState; -import android.util.Log; - -/** - * The service class for managing a VPN connection. It implements the - * {@link IVpnService} binder interface. - */ -public class VpnServiceBinder extends IVpnService.Stub { - private static final String TAG = VpnServiceBinder.class.getSimpleName(); - private static final boolean DBG = true; - - // The actual implementation is delegated to the VpnService class. - private VpnService<? extends VpnProfile> mService; - - private Context mContext; - - public VpnServiceBinder(Context context) { - mContext = context; - } - - @Override - public synchronized boolean connect(VpnProfile p, final String username, - final String password) { - if ((mService != null) && !mService.isIdle()) return false; - final VpnService s = mService = createService(p); - - new Thread(new Runnable() { - public void run() { - s.onConnect(username, password); - } - }).start(); - return true; - } - - @Override - public synchronized void disconnect() { - if (mService == null) return; - final VpnService s = mService; - mService = null; - - new Thread(new Runnable() { - public void run() { - s.onDisconnect(); - } - }).start(); - } - - @Override - public synchronized String getState(VpnProfile p) { - if ((mService == null) - || (!p.getName().equals(mService.mProfile.getName()))) { - return VpnState.IDLE.toString(); - } else { - return mService.getState().toString(); - } - } - - @Override - public synchronized boolean isIdle() { - return (mService == null || mService.isIdle()); - } - - private VpnService<? extends VpnProfile> createService(VpnProfile p) { - switch (p.getType()) { - case L2TP: - L2tpService l2tp = new L2tpService(); - l2tp.setContext(mContext, (L2tpProfile) p); - return l2tp; - - case PPTP: - PptpService pptp = new PptpService(); - pptp.setContext(mContext, (PptpProfile) p); - return pptp; - - case L2TP_IPSEC_PSK: - L2tpIpsecPskService psk = new L2tpIpsecPskService(); - psk.setContext(mContext, (L2tpIpsecPskProfile) p); - return psk; - - case L2TP_IPSEC: - L2tpIpsecService l2tpIpsec = new L2tpIpsecService(); - l2tpIpsec.setContext(mContext, (L2tpIpsecProfile) p); - return l2tpIpsec; - - default: - return null; - } - } -} diff --git a/vpn/tests/vpntests/Android.mk b/vpn/tests/vpntests/Android.mk deleted file mode 100644 index a19fb56..0000000 --- a/vpn/tests/vpntests/Android.mk +++ /dev/null @@ -1,14 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -# We only want this apk build for tests. -LOCAL_MODULE_TAGS := tests - -# Include all test java files. -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_JAVA_LIBRARIES := android.test.runner -LOCAL_PACKAGE_NAME := FrameworksVpnTests - -include $(BUILD_PACKAGE) - diff --git a/vpn/tests/vpntests/AndroidManifest.xml b/vpn/tests/vpntests/AndroidManifest.xml deleted file mode 100644 index d8405f6..0000000 --- a/vpn/tests/vpntests/AndroidManifest.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.frameworks.vpntests"> - <uses-permission android:name="android.permission.RECEIVE_SMS"/> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.READ_CONTACTS" /> - <uses-permission android:name="android.permission.WRITE_CONTACTS" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> - <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> - <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" /> - <uses-permission android:name="android.permission.BROADCAST_STICKY" /> - - <application> - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation - android:name="android.test.InstrumentationTestRunner" - android:targetPackage="com.android.frameworks.vpntests" - android:label="Frameworks VPN Tests" /> -</manifest> diff --git a/vpn/tests/vpntests/src/android/net/vpn/VpnTest.java b/vpn/tests/vpntests/src/android/net/vpn/VpnTest.java deleted file mode 100755 index 46a57d3..0000000 --- a/vpn/tests/vpntests/src/android/net/vpn/VpnTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.net.vpn; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.vpn.L2tpProfile; -import android.net.vpn.L2tpIpsecProfile; -import android.net.vpn.L2tpIpsecPskProfile; -import android.net.vpn.PptpProfile; -import android.net.vpn.VpnManager; -import android.net.vpn.VpnProfile; -import android.net.vpn.VpnState; -import android.net.vpn.VpnType; -import android.os.ConditionVariable; -import android.os.Parcel; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; -import android.text.TextUtils; - -/** - * Unit test class to test VPN api - * Use the below command to run the vpn unit test only - * runtest vpntest or - * adb shell am instrument -e class 'com.android.unit_tests.VpnTest' - * -w com.android.unit_tests/android.test.InstrumentationTestRunner - */ -public class VpnTest extends AndroidTestCase { - private static final String NAME = "a name"; - private static final String SERVER_NAME = "a server name"; - private static final String ID = "some id"; - private static final String SUFFICES = "some suffices"; - private static final String ROUTES = "some routes"; - private static final String SAVED_NAME = "some name"; - - @Override - public void setUp() { - } - - @Override - public void tearDown() { - } - - @SmallTest - public void testVpnType() { - testVpnType(VpnType.L2TP); - testVpnType(VpnType.L2TP_IPSEC); - testVpnType(VpnType.L2TP_IPSEC_PSK); - testVpnType(VpnType.PPTP); - } - - @SmallTest - public void testVpnProfile() { - VpnState state = VpnState.CONNECTING; - testVpnProfile(createTestProfile(state), state); - } - - @SmallTest - public void testGetType() { - assertEquals(VpnType.L2TP, new L2tpProfile().getType()); - assertEquals(VpnType.L2TP_IPSEC, new L2tpIpsecProfile().getType()); - assertEquals(VpnType.L2TP_IPSEC_PSK, - new L2tpIpsecPskProfile().getType()); - assertEquals(VpnType.PPTP, new PptpProfile().getType()); - } - - @SmallTest - public void testVpnTypes() { - assertTrue(VpnManager.getSupportedVpnTypes().length > 0); - } - - @SmallTest - public void testGetTypeFromManager() { - VpnManager m = new VpnManager(getContext()); - VpnType[] types = VpnManager.getSupportedVpnTypes(); - for (VpnType t : types) { - assertEquals(t, m.createVpnProfile(t).getType()); - } - } - - @SmallTest - public void testParcelable() { - VpnProfile p = createTestProfile(VpnState.CONNECTED); - Parcel parcel = Parcel.obtain(); - p.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - - // VpnState is transient and not saved in the parcel - testVpnProfile(VpnProfile.CREATOR.createFromParcel(parcel), null); - } - - @SmallTest - public void testReceiver() { - final String profileName = "whatever"; - final VpnState state = VpnState.DISCONNECTING; - final ConditionVariable cv = new ConditionVariable(); - cv.close(); - BroadcastReceiver r = new BroadcastReceiver() { - public void onReceive(Context c, Intent i) { - assertEquals(profileName, - i.getStringExtra(VpnManager.BROADCAST_PROFILE_NAME)); - assertEquals(state, i.getSerializableExtra( - VpnManager.BROADCAST_CONNECTION_STATE)); - cv.open(); - } - }; - - VpnManager m = new VpnManager(getContext()); - m.registerConnectivityReceiver(r); - m.broadcastConnectivity(profileName, state); - - // fail it if onReceive() doesn't get executed in 5 sec - assertTrue(cv.block(5000)); - } - - private void testVpnType(VpnType type) { - assertFalse(TextUtils.isEmpty(type.getDisplayName())); - assertNotNull(type.getProfileClass()); - } - - private VpnProfile createTestProfile(VpnState state) { - VpnProfile p = new L2tpProfile(); - p.setName(NAME); - p.setServerName(SERVER_NAME); - p.setId(ID); - p.setDomainSuffices(SUFFICES); - p.setRouteList(ROUTES); - p.setSavedUsername(SAVED_NAME); - p.setState(state); - return p; - } - - private void testVpnProfile(VpnProfile p, VpnState state) { - assertEquals(NAME, p.getName()); - assertEquals(SERVER_NAME, p.getServerName()); - assertEquals(ID, p.getId()); - assertEquals(SUFFICES, p.getDomainSuffices()); - assertEquals(ROUTES, p.getRouteList()); - assertEquals(SAVED_NAME, p.getSavedUsername()); - if (state != null) assertEquals(state, p.getState()); - } -} |