Merge "Keep last known keepalive interval to avoid duplicate effort."
diff --git a/Android.mk b/Android.mk
index 5f3327c..c2d4abc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -190,7 +190,6 @@
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 @@
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 $(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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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..bbf6b14e
--- /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 @@
{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 @@
* 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 @@
* 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 @@
* {@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 @@
* @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 @@
/**
* 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 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 @@
* @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 @@
* <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 @@
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 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
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 @@
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.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.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 @@
new HashMap<String, SharedPreferencesImpl>();
/*package*/ LoadedApk mPackageInfo;
+ private String mBasePackageName;
private Resources mResources;
/*package*/ ActivityThread mMainThread;
private Context mOuterContext;
@@ -1030,7 +1029,7 @@
}
try {
return ActivityManagerNative.getDefault().registerReceiver(
- mMainThread.getApplicationThread(),
+ mMainThread.getApplicationThread(), mBasePackageName,
rd, filter, broadcastPermission);
} catch (RemoteException e) {
return null;
@@ -1397,7 +1396,7 @@
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 @@
*/
public ContextImpl(ContextImpl context) {
mPackageInfo = context.mPackageInfo;
+ mBasePackageName = context.mBasePackageName;
mResources = context.mResources;
mMainThread = context.mMainThread;
mContentResolver = context.mContentResolver;
@@ -1458,13 +1458,14 @@
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 @@
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 @@
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 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 @@
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 @@
* 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 @@
* 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 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 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 @@
* @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 @@
}
/**
+ * @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 @@
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 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 @@
*
* <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 @@
*
* <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 @@
/** @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 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..d6f5643 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -23,6 +23,7 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
/**
@@ -101,7 +102,11 @@
void protectVpn(in ParcelFileDescriptor socket);
- String prepareVpn(String packageName);
+ boolean prepareVpn(String oldPackage, String newPackage);
ParcelFileDescriptor establishVpn(in VpnConfig config);
+
+ void startLegacyVpn(in VpnConfig config, in String[] racoon, in String[] mtpd);
+
+ LegacyVpnInfo getLegacyVpnInfo();
}
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 @@
/** 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 @@
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 @@
@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 @@
* @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 @@
*/
@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 @@
*/
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 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.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 @@
/**
* 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 @@
/**
* 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 @@
* </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 @@
* 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 @@
/**
* 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 @@
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 @@
* 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 @@
/**
* 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 @@
/**
* 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 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 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 @@
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 @@
* 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 @@
/**
* 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 @@
/**
* 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 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 @@
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 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 @@
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 @@
/**
* 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 @@
// 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 @@
* 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 @@
}
/**
- * 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..8e7128b 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -52,18 +52,15 @@
/** The authority used by the voicemail provider. */
public static final String AUTHORITY = "com.android.voicemail";
-
- /** URI to insert/retrieve all voicemails. */
- 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/");
+ /**
+ * 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 +68,19 @@
*/
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";
-
+ /** 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. */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/voicemail");
+
+ /** 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 +144,101 @@
* @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 @@
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 @@
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 @@
}
}
- Alignment align = mAlignment;
+ Alignment paraAlign = mAlignment;
TabStops tabStops = null;
boolean tabStopsIsInitialized = false;
@@ -310,10 +310,10 @@
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 @@
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 @@
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 @@
}
}
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 @@
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 @@
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 @@
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.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 @@
* <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 @@
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 MenuInflater(Context context) {
mContext = context;
+ mActionViewConstructorArguments = new Object[] {context};
+ mActionProviderConstructorArguments = mActionViewConstructorArguments;
}
/**
@@ -172,14 +183,14 @@
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 @@
private int itemActionViewLayout;
private String itemActionViewClassName;
-
+ private String itemActionProviderClassName;
+
private String itemListenerMethodName;
private static final int defaultGroupId = NO_ID;
@@ -333,9 +345,10 @@
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 @@
}
}
+ 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.");
}
- } else if (itemActionViewLayout > 0) {
- item.setActionView(itemActionViewLayout);
+ }
+ 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.");
+ }
}
}
-
+
public void addItem() {
itemAdded = true;
setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
@@ -406,6 +434,18 @@
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 @@
* @see MenuItem#expandActionView()
* @see MenuItem#collapseActionView()
* @see MenuItem#setShowAsActionFlags(int)
- * @see MenuItem#
*/
public interface OnActionExpandListener {
/**
@@ -480,6 +479,10 @@
* 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 @@
* 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 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 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 @@
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 @@
@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 @@
}
/**
- * 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 @@
mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
- if (mLayoutParams != null) {
+ if (mLayoutParams != null && mParent != null) {
mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
}
@@ -12990,6 +13010,7 @@
* {@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 @@
* {@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 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 @@
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 @@
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 @@
* @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 @@
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 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 @@
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 @@
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 @@
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.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 @@
* <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 @@
* <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 @@
* <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 @@
* <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 @@
* <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 @@
* <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 @@
* 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 @@
* (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>
+ * </ul>
+ * </p>
* <p>
- * <b>TRANSITION TYPES</b> <br>
- * <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 @@
* <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 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 @@
* @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 @@
}
/**
- * 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 @@
/**
* 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 @@
/**
* 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 @@
}
/**
- * 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 @@
@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 @@
* 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 @@
/**
* 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 @@
* 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.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 @@
* 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 @@
}
/**
- * 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 @@
* @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 @@
}
/**
- * 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 @@
* 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 @@
}
/**
- * 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.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.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 @@
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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* {@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 @@
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 @@
* @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.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 @@
}
/**
- * 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 @@
/**
* 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 @@
/**
* 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 @@
}
/**
+ * 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/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 9d84c3e..4a98336 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -160,10 +160,10 @@
if (mSubtypeNameResId == 0) {
return localeStr;
}
- final String subtypeName = context.getPackageManager().getText(
- packageName, mSubtypeNameResId, appInfo).toString();
+ final CharSequence subtypeName = context.getPackageManager().getText(
+ packageName, mSubtypeNameResId, appInfo);
if (!TextUtils.isEmpty(subtypeName)) {
- return String.format(subtypeName, localeStr);
+ return String.format(subtypeName.toString(), localeStr);
} else {
return localeStr;
}
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 @@
// 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 @@
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 @@
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 void pause() {
- if (mCurrentState == STATE_PREPARED && mPlayer.isPlaying()) {
+ if (isPlaying()) {
mPlayer.pause();
} else if (mCurrentState == STATE_NOTPREPARED) {
mPauseDuringPreparing = true;
@@ -120,11 +121,18 @@
}
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 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 @@
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/WebView.java b/core/java/android/webkit/WebView.java
index 9f632d1..2fda7de 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1971,9 +1971,14 @@
}
/**
- * Load the given data into the WebView using a 'data' scheme URL. Content
- * loaded in this way does not have the ability to load content from the
- * network.
+ * Load the given data into the WebView using a 'data' scheme URL.
+ * <p>
+ * Note that JavaScript's same origin policy means that script running in a
+ * page loaded using this method will be unable to access content loaded
+ * using any scheme other than 'data', including 'http(s)'. To avoid this
+ * restriction, use {@link
+ * #loadDataWithBaseURL(String,String,String,String,String)
+ * loadDataWithBaseURL()} with an appropriate base URL.
* <p>
* If the value of the encoding parameter is 'base64', then the data must
* be encoded as base64. Otherwise, the data must use ASCII encoding for
@@ -2000,15 +2005,15 @@
}
/**
- * Load the given data into the WebView, use the provided URL as the base
- * URL for the content. The base URL is the URL that represents the page
- * that is loaded through this interface. As such, it is used to resolve any
- * relative URLs. The historyUrl is used for the history entry.
+ * Load the given data into the WebView, using baseUrl as the base URL for
+ * the content. The base URL is used both to resolve relative URLs and when
+ * applying JavaScript's same origin policy. The historyUrl is used for the
+ * history entry.
* <p>
* Note that content specified in this way can access local device files
* (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
* 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
- * @param baseUrl Url to resolve relative paths with, if null defaults to
+ * @param baseUrl URL to use as the page's base URL. If null defaults to
* "about:blank"
* @param data A String of data in the given encoding.
* @param mimeType The MIMEType of the data. i.e. text/html. If null,
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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/WebViewFragment.java b/core/java/android/webkit/WebViewFragment.java
index 466f174..852878b 100644
--- a/core/java/android/webkit/WebViewFragment.java
+++ b/core/java/android/webkit/WebViewFragment.java
@@ -30,6 +30,7 @@
*/
public class WebViewFragment extends Fragment {
private WebView mWebView;
+ private boolean mIsWebViewAvailable;
public WebViewFragment() {
}
@@ -40,7 +41,11 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
+ if (mWebView != null) {
+ mWebView.destroy();
+ }
mWebView = new WebView(getActivity());
+ mIsWebViewAvailable = true;
return mWebView;
}
@@ -63,19 +68,31 @@
}
/**
- * Called when the view has been detached from the fragment. Destroys the WebView.
+ * Called when the WebView has been detached from the fragment.
+ * The WebView is no longer available after this time.
*/
@Override
public void onDestroyView() {
- mWebView.destroy();
- mWebView = null;
+ mIsWebViewAvailable = false;
super.onDestroyView();
}
/**
+ * Called when the fragment is no longer in use. Destroys the internal state of the WebView.
+ */
+ @Override
+ public void onDestroy() {
+ if (mWebView != null) {
+ mWebView.destroy();
+ mWebView = null;
+ }
+ super.onDestroy();
+ }
+
+ /**
* Gets the WebView.
*/
public WebView getWebView() {
- return mWebView;
+ return mIsWebViewAvailable ? mWebView : null;
}
}
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 @@
} 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.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.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 @@
*
* <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 @@
*
* 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 @@
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 @@
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 @@
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 @@
}
}
+ /**
+ * {@inheritDoc}
+ */
+ public GridLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public GridLayout(Context context) {
+ this(context, null);
+ }
+
// Implementation
/**
@@ -527,11 +520,10 @@
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 @@
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 @@
}
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 @@
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 @@
// Draw margins
paint.setColor(Color.YELLOW);
-
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
drawRectangle(canvas,
@@ -819,11 +810,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
}
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 @@
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 @@
}
// 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) {
+ private PackedMap<Interval, MutableInt> getBackwardLinks() {
+ if (backwardLinks == null) {
+ backwardLinks = createLinks(false);
+ }
+ if (!backwardLinksValid) {
+ computeLinks(backwardLinks, false);
+ backwardLinksValid = true;
+ }
+ return backwardLinks;
+ }
+
+ 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 (col>0) constraints obliterating valid entries
- for (Arc arc : arcs) {
- Interval span = arc.span;
- if (span.equals(key)) {
- return;
+ // 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, MutableInt min, MutableInt max,
- boolean both) {
- include(arcs, span, min);
- if (both) {
- // todo
-// include(arcs, span.inverse(), max.neg());
- }
- }
-
- 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 @@
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;
}
- visited[loc] = COMPLETE;
- } else if (state == PENDING) {
- return true;
- } else if (state == COMPLETE) {
+ case PENDING: {
+ assert false;
+ break;
+ }
+ case COMPLETE: {
+ break;
+ }
}
- 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 @@
return result;
}
- private Arc[] createArcs() {
- List<Arc> result = 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);
+ 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> mins = new ArrayList<Arc>();
+ List<Arc> maxs = new ArrayList<Arc>();
+
+ // 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 @@
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 @@
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 @@
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);
}
+ return;
+ }
+ }
+
+ 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]);
+ }
+ }
+ 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;
}
}
- if (changed) {
- Log.d(TAG, "*** Algorithm failed to terminate ***");
- }
- return locations;
+ 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 @@
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 @@
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 @@
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();
+ private int getMeasure(int min, int max) {
+ setParentConstraints(min, max);
+ return size(getLocations());
+ }
- int totalDelta = max(0, targetSize - size(mins)); // confine to expansion
-
- float[] weights = getWeights();
- float totalWeight = sum(weights);
-
- 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 @@
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 @@
* <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 @@
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 @@
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 Group columnGroup;
/**
- * The proportional space that should be taken by the associated row group
- * during excess space distribution.
- */
- public float rowWeight;
- /**
* The proportional space that should be taken by the associated column group
* during excess space distribution.
*/
- public float columnWeight;
+ public Spec widthSpec;
+ /**
+ * The proportional space that should be taken by the associated row group
+ * during excess space distribution.
+ */
+ 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 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 @@
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 @@
!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 @@
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 @@
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 @@
@Override
public String toString() {
- return span + " " + (completesCycle ? "+>" : "->") + " " + value;
+ return span + " " + (!valid ? "+>" : "->") + " " + value;
}
}
@@ -1903,6 +1930,41 @@
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 int before;
public int after;
+ public boolean canStretch;
private Bounds() {
reset();
@@ -1997,6 +2060,7 @@
protected void reset() {
before = Integer.MIN_VALUE;
after = Integer.MIN_VALUE;
+ canStretch = false;
}
protected void include(int before, int after) {
@@ -2004,12 +2068,26 @@
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 @@
* 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 @@
* 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 @@
* @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 @@
* 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 @@
*
* @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 @@
*
* @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 @@
* 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 @@
* @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 @@
@Override
protected void reset() {
super.reset();
- size = 0;
+ size = Integer.MIN_VALUE;
}
@Override
@@ -2388,13 +2462,13 @@
}
@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 @@
* {@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
*/
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 @@
*/
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 @@
foundStrongLtr = true;
} else if (isStrongRtlChar(dir)) {
foundStrongRtl = true;
+ break;
}
}
if (foundStrongRtl) {
@@ -10121,6 +10190,41 @@
}
/**
+ * 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.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 @@
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 @@
// Clear out the context mode views after the animation finishes
mContextView.closeMode();
+
mActionMode = null;
if (mWasHiddenBeforeMode) {
diff --git a/vpn/java/android/net/vpn/VpnProfile.aidl b/core/java/com/android/internal/net/LegacyVpnInfo.aidl
similarity index 75%
rename from vpn/java/android/net/vpn/VpnProfile.aidl
rename to core/java/com/android/internal/net/LegacyVpnInfo.aidl
index edeaef0..0ca2627 100644
--- a/vpn/java/android/net/vpn/VpnProfile.aidl
+++ b/core/java/com/android/internal/net/LegacyVpnInfo.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2009, The Android Open Source Project
+ * 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
+ * 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,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.net.vpn;
+package com.android.internal.net;
-parcelable VpnProfile;
+parcelable LegacyVpnInfo;
diff --git a/core/java/com/android/internal/net/LegacyVpnInfo.java b/core/java/com/android/internal/net/LegacyVpnInfo.java
new file mode 100644
index 0000000..b620abac
--- /dev/null
+++ b/core/java/com/android/internal/net/LegacyVpnInfo.java
@@ -0,0 +1,69 @@
+/*
+ * 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.internal.net;
+
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A simple container used to carry information of the ongoing legacy VPN.
+ * Internal use only.
+ *
+ * @hide
+ */
+public class LegacyVpnInfo implements Parcelable {
+ public static final int STATE_DISCONNECTED = 0;
+ public static final int STATE_INITIALIZING = 1;
+ public static final int STATE_CONNECTING = 2;
+ public static final int STATE_CONNECTED = 3;
+ public static final int STATE_TIMEOUT = 4;
+ public static final int STATE_FAILED = 5;
+
+ public String key;
+ public int state = -1;
+ public PendingIntent intent;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(key);
+ out.writeInt(state);
+ out.writeParcelable(intent, flags);
+ }
+
+ public static final Parcelable.Creator<LegacyVpnInfo> CREATOR =
+ new Parcelable.Creator<LegacyVpnInfo>() {
+ @Override
+ public LegacyVpnInfo createFromParcel(Parcel in) {
+ LegacyVpnInfo info = new LegacyVpnInfo();
+ info.key = in.readString();
+ info.state = in.readInt();
+ info.intent = in.readParcelable(null);
+ return info;
+ }
+
+ @Override
+ public LegacyVpnInfo[] newArray(int size) {
+ return new LegacyVpnInfo[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 773be5b..d36be10 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -21,7 +21,8 @@
import android.content.Intent;
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,
@@ -33,11 +34,7 @@
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();
@@ -45,24 +42,25 @@
return intent;
}
- public static PendingIntent getIntentForNotification(Context context, VpnConfig config) {
- config.startTime = SystemClock.elapsedRealtime();
+ public static PendingIntent getIntentForStatusPanel(Context context, VpnConfig config) {
Intent intent = new Intent();
intent.setClassName("com.android.vpndialogs", "com.android.vpndialogs.ManageDialog");
intent.putExtra("config", config);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ return PendingIntent.getActivity(context, 0, intent, (config == null) ?
+ PendingIntent.FLAG_NO_CREATE : 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 +70,15 @@
@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 +87,15 @@
@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 @@
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 @@
}
@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.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 @@
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.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 @@
private int mShowAsAction = SHOW_AS_ACTION_NEVER;
private View mActionView;
+ private ActionProvider mActionProvider;
private OnActionExpandListener mOnActionExpandListener;
private boolean mIsActionViewExpanded = false;
@@ -98,10 +100,8 @@
/**
- * 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 @@
mItemCallback.run();
return true;
}
-
+
if (mIntent != null) {
try {
mMenu.getContext().startActivity(mIntent);
@@ -163,7 +163,14 @@
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 MenuItem setActionView(View view) {
mActionView = view;
+ mActionProvider = null;
mMenu.onItemActionRequestChanged(this);
return this;
}
@@ -563,7 +571,25 @@
}
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 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 @@
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 @@
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 @@
private boolean mUserTitle;
private boolean mIncludeTabs;
private boolean mIsCollapsable;
+ private boolean mIsCollapsed;
private MenuBuilder mOptionsMenu;
@@ -692,6 +693,10 @@
mIsCollapsable = collapsable;
}
+ public boolean isCollapsed() {
+ return mIsCollapsed;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int childCount = getChildCount();
@@ -708,9 +713,11 @@
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.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 @@
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 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 @@
}
}
}
+
+ 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 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 @@
* 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 @@
}
// 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 @@
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 @@
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 @@
case STATE_FIRST_TOUCH:
stopHandleAnimation();
deactivateTargets();
- showTargets();
+ showTargets(true);
mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
setGrabbedState(OnTriggerListener.CENTER_HANDLE);
break;
@@ -228,17 +238,18 @@
* 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 @@
"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 @@
}
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 @@
}
}
- /**
- * 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 @@
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 @@
}
}
- @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 @@
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 @@
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 @@
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
old mode 100644
new mode 100755
index 4c20179..f7b6e99
--- a/core/res/res/drawable-hdpi/textfield_default.9.png
+++ b/core/res/res/drawable-hdpi/textfield_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_disabled.9.png b/core/res/res/drawable-hdpi/textfield_disabled.9.png
old mode 100644
new mode 100755
index 81569d1..3011502
--- a/core/res/res/drawable-hdpi/textfield_disabled.9.png
+++ b/core/res/res/drawable-hdpi/textfield_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png b/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png
old mode 100644
new mode 100755
index 2591490..e0f82eb
--- a/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png
+++ b/core/res/res/drawable-hdpi/textfield_disabled_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_selected.9.png b/core/res/res/drawable-hdpi/textfield_selected.9.png
old mode 100644
new mode 100755
index a36ed72..cf2cae3
--- a/core/res/res/drawable-hdpi/textfield_selected.9.png
+++ b/core/res/res/drawable-hdpi/textfield_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_default.9.png b/core/res/res/drawable-mdpi/textfield_default.9.png
index cc78e6c..1a59bb2 100644
--- a/core/res/res/drawable-mdpi/textfield_default.9.png
+++ b/core/res/res/drawable-mdpi/textfield_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_disabled.9.png b/core/res/res/drawable-mdpi/textfield_disabled.9.png
index 9c77149..800205a 100644
--- a/core/res/res/drawable-mdpi/textfield_disabled.9.png
+++ b/core/res/res/drawable-mdpi/textfield_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_disabled_selected.9.png b/core/res/res/drawable-mdpi/textfield_disabled_selected.9.png
index 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
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_selected.9.png b/core/res/res/drawable-mdpi/textfield_selected.9.png
index 0c1b446..faadace 100644
--- a/core/res/res/drawable-mdpi/textfield_selected.9.png
+++ b/core/res/res/drawable-mdpi/textfield_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_bottom_bright.9.png b/core/res/res/drawable-xhdpi/popup_bottom_bright.9.png
new file mode 100644
index 0000000..cdc0afb
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_bottom_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_bottom_dark.9.png b/core/res/res/drawable-xhdpi/popup_bottom_dark.9.png
new file mode 100644
index 0000000..36b0448
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_bottom_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_bottom_medium.9.png b/core/res/res/drawable-xhdpi/popup_bottom_medium.9.png
new file mode 100644
index 0000000..3a7a8b3
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_bottom_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_center_bright.9.png b/core/res/res/drawable-xhdpi/popup_center_bright.9.png
new file mode 100644
index 0000000..b1a8e3e
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_center_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_center_dark.9.png b/core/res/res/drawable-xhdpi/popup_center_dark.9.png
new file mode 100644
index 0000000..87378e1
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_center_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_center_medium.9.png b/core/res/res/drawable-xhdpi/popup_center_medium.9.png
new file mode 100644
index 0000000..ea29ed4
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_center_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_full_bright.9.png b/core/res/res/drawable-xhdpi/popup_full_bright.9.png
new file mode 100644
index 0000000..114faa0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_full_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_full_dark.9.png b/core/res/res/drawable-xhdpi/popup_full_dark.9.png
new file mode 100644
index 0000000..996beaa
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_full_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_top_bright.9.png b/core/res/res/drawable-xhdpi/popup_top_bright.9.png
new file mode 100644
index 0000000..64e4139
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_top_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_top_dark.9.png b/core/res/res/drawable-xhdpi/popup_top_dark.9.png
new file mode 100644
index 0000000..902bc29
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_top_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_black_16.png b/core/res/res/drawable-xhdpi/spinner_black_16.png
new file mode 100644
index 0000000..5b1422c
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/spinner_black_16.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_black_20.png b/core/res/res/drawable-xhdpi/spinner_black_20.png
new file mode 100644
index 0000000..5f53e38
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/spinner_black_20.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_black_48.png b/core/res/res/drawable-xhdpi/spinner_black_48.png
new file mode 100644
index 0000000..3aab620
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/spinner_black_48.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_black_76.png b/core/res/res/drawable-xhdpi/spinner_black_76.png
new file mode 100644
index 0000000..2968d8c
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/spinner_black_76.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_white_16.png b/core/res/res/drawable-xhdpi/spinner_white_16.png
new file mode 100644
index 0000000..69be752
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/spinner_white_16.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_white_48.png b/core/res/res/drawable-xhdpi/spinner_white_48.png
new file mode 100644
index 0000000..7b196bc
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/spinner_white_48.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/spinner_white_76.png b/core/res/res/drawable-xhdpi/spinner_white_76.png
new file mode 100644
index 0000000..ba47005
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/spinner_white_76.png
Binary files differ
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..867014b9
--- /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 88ed9c6b..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 @@
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..1647ff3 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -174,12 +174,15 @@
* 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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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.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 @@
}
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 @@
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 @@
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 @@
}
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 @@
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 @@
* 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
mLoop = false;
mStatus = NO_INIT;
mAudioSessionId = audioSessionId;
+ mUID = uid;
#if CALLBACK_ANTAGONIZER
LOGD("create Antagonizer");
@@ -671,6 +685,9 @@
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 @@
pid_t pid,
int32_t connId,
const sp<IMediaPlayerClient>& client,
- int audioSessionId);
+ int audioSessionId,
+ uid_t uid);
Client();
virtual ~Client();
@@ -336,6 +337,7 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
////////////////////////////////////////////////////////////////////////////////
NuPlayer::NuPlayer()
- : mAudioEOS(false),
+ : mUIDValid(false),
+ mAudioEOS(false),
mVideoEOS(false),
mScanSourcesPending(false),
mScanSourcesGeneration(0),
@@ -57,6 +58,11 @@
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 @@
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 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 @@
};
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 @@
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 @@
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 @@
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 @@
return BaseState::onOMXEvent(event, data1, data2);
}
}
+
void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() {
if (mCodec->allYourBuffersAreBelongToUs()) {
CHECK_EQ(mCodec->mOMX->sendCommand(
@@ -2282,6 +2294,11 @@
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 @@
}
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 @@
////////////////////////////////////////////////////////////////////////////////
AwesomePlayer::AwesomePlayer()
: mQueueStarted(false),
+ mUIDValid(false),
mTimeSource(NULL),
mVideoRendererIsPreview(false),
mAudioPlayer(NULL),
@@ -243,6 +247,13 @@
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 @@
? HTTPBase::kFlagIncognito
: 0);
+ if (mUIDValid) {
+ mConnectingDataSource->setUID(mUID);
+ }
+
mLock.unlock();
status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders);
mLock.lock();
@@ -2009,6 +2024,10 @@
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 @@
mTotalTransferBytes(0),
mPrevBandwidthMeasureTimeUs(0),
mPrevEstimatedBandWidthKbps(0),
- mBandWidthCollectFreqMs(5000) {
+ mBandWidthCollectFreqMs(5000),
+ mUIDValid(false) {
}
// static
@@ -119,4 +120,19 @@
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 @@
HTTPStream::HTTPStream()
: mState(READY),
+ mUIDValid(false),
mSocket(-1),
mSSLContext(NULL),
mSSL(NULL) {
@@ -57,6 +58,11 @@
}
}
+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 @@
continue;
}
+ if (mUIDValid) {
+ RegisterSocketUser(mSocket, mUID);
+ }
+
setReceiveTimeout(30); // Time out reads after 30 secs by default.
int s = mSocket;
@@ -596,5 +606,18 @@
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 @@
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 @@
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 @@
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 @@
return NULL;
}
-status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) {
- mIsMetaDataStoredInVideoBuffers = false;
- if (flags & kStoreMetaDataInVideoBuffers) {
- mIsMetaDataStoredInVideoBuffers = true;
- }
+status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
+ LOGV("configureCodec protected=%d",
+ (mFlags & kEnableGrallocUsageProtected) ? 1 : 0);
- mOnlySubmitOneBufferAtOneTime = false;
- if (flags & kOnlySubmitOneInputBufferAtOneTime) {
- mOnlySubmitOneBufferAtOneTime = true;
- }
-
- 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 @@
initOutputFormat(meta);
- if ((flags & kClientNeedsFramebuffer)
+ if ((mFlags & kClientNeedsFramebuffer)
&& !strncmp(mComponentName, "OMX.SEC.", 8)) {
OMX_INDEXTYPE index;
@@ -1468,7 +1464,8 @@
}
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 @@
mOMXLivesLocally(omx->livesLocally(getpid())),
mNode(node),
mQuirks(quirks),
+ mFlags(flags),
mIsEncoder(isEncoder),
mMIME(strdup(mime)),
mComponentName(strdup(componentName)),
@@ -1645,13 +1643,14 @@
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 @@
IOMX::buffer_id buffer;
if (portIndex == kPortIndexInput
- && (mQuirks & kRequiresAllocateBufferOnInputPorts)) {
+ && ((mQuirks & kRequiresAllocateBufferOnInputPorts)
+ || (mFlags & kUseSecureInputBuffers))) {
if (mOMXLivesLocally) {
mem.clear();
@@ -1748,6 +1748,31 @@
// 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 @@
// XXX: Currently this error is logged, but not fatal.
usage = 0;
}
- if (mEnableGrallocUsageProtected) {
+ if (mFlags & kEnableGrallocUsageProtected) {
usage |= GRALLOC_USAGE_PROTECTED;
}
@@ -2067,7 +2092,12 @@
} 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::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 (info->mStatus != OWNED_BY_US) {
- continue;
+ 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 (!drainInputBuffer(info)) {
- break;
- }
+ if (info->mStatus != OWNED_BY_US) {
+ continue;
+ }
- if (mOnlySubmitOneBufferAtOneTime) {
- break;
+ if (!drainInputBuffer(info)) {
+ break;
+ }
+
+ if (mFlags & kOnlySubmitOneInputBufferAtOneTime) {
+ 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;
+ }
+ }
+
+ 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 @@
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 @@
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 @@
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 @@
typedef WVMLoadableExtractor *(*GetInstanceFunc)(sp<DataSource>);
GetInstanceFunc getInstanceFunc =
- (GetInstanceFunc) dlsym(sVendorLibHandle,
+ (GetInstanceFunc) dlsym(gVendorLibHandle,
"_ZN7android11GetInstanceENS_2spINS_10DataSourceEEE");
if (getInstanceFunc) {
@@ -71,17 +72,6 @@
}
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 @@
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 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 @@
}
// 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 @@
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 @@
mSeekDone(false),
mDisconnectPending(false),
mMonitorQueueGeneration(0) {
+ if (mUIDValid) {
+ mHTTPDataSource->setUID(mUID);
+ }
}
LiveSession::~LiveSession() {
@@ -408,13 +413,20 @@
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 @@
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 (mPlaylist->isComplete()) {
+ size_t index = 0;
+ int64_t segmentStartUs = 0;
+ while (index < mPlaylist->size()) {
+ sp<AMessage> itemMeta;
+ CHECK(mPlaylist->itemAt(
+ index, NULL /* uri */, &itemMeta));
- if (index >= 0 && index < mPlaylist->size()) {
+ 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 @@
? 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 @@
}
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 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 @@
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 @@
~AwesomePlayer();
void setListener(const wp<MediaPlayerBase> &listener);
+ void setUID(uid_t uid);
status_t setDataSource(
const char *uri,
@@ -150,6 +151,8 @@
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 @@
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 @@
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 @@
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 @@
// _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 @@
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 @@
// 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 @@
};
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 @@
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 @@
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 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 @@
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 @@
ARTSPController::ARTSPController(const sp<ALooper> &looper)
: mState(DISCONNECTED),
mLooper(looper),
+ mUIDValid(false),
mSeekDoneCb(NULL),
mSeekDoneCookie(NULL),
mLastSeekCompletedTimeUs(-1) {
@@ -40,6 +41,11 @@
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 @@
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 @@
// 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 @@
}
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 @@
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 @@
++n;
}
- mSeekable = seekable;
+ mSeekable = true;
}
sp<APacketSource> getPacketSource(size_t index) {
@@ -1080,6 +1084,8 @@
List<sp<ABuffer> > mPackets;
};
+ bool mUIDValid;
+ uid_t mUID;
sp<ALooper> mLooper;
sp<ALooper> mNetLooper;
sp<ARTSPConnection> mConn;
@@ -1174,6 +1180,11 @@
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
new file mode 100644
index 0000000..e14111d
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/global_screenshot_background.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png b/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png
new file mode 100644
index 0000000..e14111d
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/global_screenshot_background.9.png
Binary files differ
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 @@
// 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 @@
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 @@
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 @@
DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
private static final String TAG = "VpnConfirm";
- private String mPackageName;
+ private String mPackage;
private IConnectivityManager mService;
@@ -47,19 +47,19 @@
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 @@
@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 @@
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);
- if (mConfig.configureActivity != null) {
+ 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.configureIntent != null) {
mDialog.setButton(DialogInterface.BUTTON_POSITIVE,
getText(R.string.configure), this);
}
@@ -113,11 +123,9 @@
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 @@
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 @@
private KeyguardUpdateMonitor mUpdateMonitor;
private KeyguardScreenCallback mCallback;
- private SlidingTab mSlidingTab;
private TextView mScreenLocked;
private TextView mEmergencyCallText;
private Button mEmergencyCallButton;
@@ -89,11 +88,9 @@
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 SlidingTabMethods implements SlidingTab.OnTriggerListener {
+ private interface UnlockWidgetCommonMethods {
+ // Update resources based on phone state
+ public void updateResources();
- private void updateRightTabResources() {
+ // Get the view associated with this widget
+ public View getView();
+
+ // 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 @@
mCallback.goToUnlockScreen();
} else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
toggleRingMode();
- updateRightTabResources();
doSilenceRingToast();
mCallback.pokeWakelock();
}
@@ -195,12 +210,29 @@
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 @@
/** {@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 @@
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 @@
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 @@
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 @@
* 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 @@
/** {@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 @@
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.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 @@
// 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 @@
}
}
+ 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 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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::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 @@
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 @@
#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 @@
// 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 @@
}
}
-/* 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 @@
MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) :
TouchInputMapper(device), mSlotCount(0), mUsingSlotsProtocol(false) {
- clearState();
}
MultiTouchInputMapper::~MultiTouchInputMapper() {
@@ -5404,6 +5005,25 @@
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 @@
// 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;
- if (id >= 0 && id <= MAX_POINTER_ID) {
+ for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty(); ) {
+ uint32_t n = idBits.firstMarkedBit();
+ idBits.clearBit(n);
+
+ if (mPointerTrackingIdMap[n] == trackingId) {
+ id = n;
+ }
+ }
+
+ 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 @@
mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp;
mCurrentTouch.buttonState = mButtonState;
+ mPointerIdBits = mCurrentTouch.idBits;
+
syncTouch(when, havePointerIds);
if (!mUsingSlotsProtocol) {
@@ -5682,6 +5308,8 @@
}
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 @@
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 @@
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 @@
int32_t associatedDisplayId;
bool orientationAware;
- bool useBadTouchFilter;
- bool useJumpyTouchFilter;
- bool useAveragingTouchFilter;
-
enum GestureMode {
GESTURE_MODE_POINTER,
GESTURE_MODE_SPOTS,
@@ -1042,38 +1028,6 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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..b98d2a2 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -44,7 +44,6 @@
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;
@@ -65,6 +64,7 @@
import android.util.Slog;
import android.util.SparseIntArray;
+import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.telephony.Phone;
import com.android.server.connectivity.Tethering;
@@ -496,11 +496,8 @@
mSettingsObserver.observe(mContext);
loadGlobalProxy();
-
- VpnManager.startVpnService(context);
}
-
/**
* Sets the preferred network.
* @param preference the new preference
@@ -2473,8 +2470,8 @@
/**
* Protect a socket from VPN routing rules. This method is used by
- * VpnBuilder and not available in ConnectivityManager. Permission
- * checks are done in Vpn class.
+ * VpnBuilder and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
* @hide
*/
@Override
@@ -2484,20 +2481,20 @@
/**
* Prepare for a VPN application. This method is used by VpnDialogs
- * and not available in ConnectivityManager. Permission checks are
- * done in Vpn class.
+ * and not available in ConnectivityManager. Permissions are checked
+ * in Vpn class.
* @hide
*/
@Override
- public String prepareVpn(String packageName) {
- return mVpn.prepare(packageName);
+ public boolean prepareVpn(String oldPackage, String newPackage) {
+ return mVpn.prepare(oldPackage, newPackage);
}
/**
* Configure a TUN interface and return its file descriptor. Parameters
* are encoded and opaque to this class. This method is used by VpnBuilder
- * and not available in ConnectivityManager. Permission checks are done
- * in Vpn class.
+ * and not available in ConnectivityManager. Permissions are checked in
+ * Vpn class.
* @hide
*/
@Override
@@ -2505,6 +2502,28 @@
return mVpn.establish(config);
}
+ /**
+ * Start legacy VPN and return an intent to VpnDialogs. This method is
+ * used by VpnSettings and not available in ConnectivityManager.
+ * Permissions are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
+ mVpn.startLegacyVpn(config, racoon, mtpd);
+ }
+
+ /**
+ * Return the information of the ongoing legacy VPN. This method is used
+ * by VpnSettings and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public LegacyVpnInfo getLegacyVpnInfo() {
+ return mVpn.getLegacyVpnInfo();
+ }
+
private String getDefaultInterface() {
if (ConnectivityManager.isNetworkTypeValid(mActiveDefaultNetwork)) {
NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
@@ -2533,7 +2552,7 @@
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 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 @@
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 @@
* 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 @@
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 @@
// 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 @@
}
- 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");
- // [24-27] for com
- writeString(buf, 24, "com");
+ // [23-26] for com
+ writeString(buf, 23, "com");
- // [29-30] bytes are for QTYPE, set to 1
- buf[30] = 1;
+ // [27] is a null byte terminator byte for the url
- // [31-32] bytes are for QCLASS, set to 1
- buf[32] = 1;
+ // [28-29] bytes are for QTYPE, set to 1 = A (host address)
+ buf[29] = 0x01;
+
+ // [30-31] bytes are for QCLASS, set to 1 = IN (internet)
+ buf[31] = 0x01;
+
+ 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 @@
/**
* {@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 @@
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 @@
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 @@
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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
}
// 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 @@
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 @@
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 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 @@
return null;
}
- checkPermissionsSafe(provider);
+ checkPermissionsSafe(provider, null);
Bundle b = new Bundle();
b.putBoolean("network", p.requiresNetwork());
@@ -1668,7 +1697,7 @@
}
private boolean _isProviderEnabledLocked(String provider) {
- checkPermissionsSafe(provider);
+ checkPermissionsSafe(provider, null);
LocationProviderInterface p = mProvidersByName.get(provider);
if (p == null) {
@@ -1694,7 +1723,7 @@
}
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.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 @@
/** Set of UIDs with active reject rules. */
private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray();
+ private boolean mBandwidthControlEnabled;
+
/**
* Constructs a new NetworkManagementService instance
*
@@ -161,6 +165,29 @@
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 @@
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 @@
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 @@
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 @@
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 @@
android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
}
- if (mProcStatsNetfilter.exists()) {
+ if (mBandwidthControlEnabled) {
return getNetworkStatsDetailNetfilter(uid);
} else {
return getNetworkStatsDetailUidstat(uid);
@@ -1151,12 +1190,6 @@
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 @@
// 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 @@
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 @@
}
return true;
}
+
+ @Override
+ protected String packageForFilter(BroadcastFilter filter) {
+ return filter.packageName;
+ }
};
/**
@@ -1825,6 +1830,8 @@
// 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 @@
}
}
- return pir.sendInner(0, fillInIntent, resolvedType,
+ return pir.sendInner(0, fillInIntent, resolvedType, null,
null, resultTo, resultWho, requestCode, flagsMask, flagsValues);
}
@@ -4162,6 +4169,27 @@
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 @@
}
}
- 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 @@
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 @@
}
}
- // 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));
+ 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 + ": remove task");
- EventLog.writeEvent(EventLogTags.AM_KILL, pr.pid,
- pr.processName, pr.setAdj, "remove task");
- Process.killProcessQuiet(pr.pid);
- } else {
- pr.waitingToKill = "remove task";
+ // 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 @@
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 @@
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 @@
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 @@
+ " (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 @@
}
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 @@
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 @@
+ " 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 @@
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 @@
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 @@
}
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 @@
// 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..c185012 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -37,12 +37,13 @@
import android.util.Log;
import com.android.internal.R;
+import com.android.internal.net.LegacyVpnInfo;
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 +56,8 @@
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,60 +66,16 @@
}
/**
- * Prepare for a VPN application.
- *
- * @param packageName The package name of the new VPN application.
- * @return The name of the current prepared package.
- */
- public synchronized String prepare(String packageName) {
- // Return the current prepared package if the new one is null.
- if (packageName == null) {
- return mPackageName;
- }
-
- // Check the permission of the caller.
- PackageManager pm = mContext.getPackageManager();
- VpnConfig.enforceCallingPackage(pm.getNameForUid(Binder.getCallingUid()));
-
- // 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);
- }
-
- // Reset the interface and hide the notification.
- if (mInterfaceName != null) {
- nativeReset(mInterfaceName);
- mCallback.restore();
- hideNotification();
- mInterfaceName = null;
- }
-
- // Notify the package being revoked.
- if (mPackageName != null) {
- Intent intent = new Intent(VpnConfig.ACTION_VPN_REVOKED);
- intent.setPackage(mPackageName);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcast(intent);
- }
-
- 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) {
+ public void protect(ParcelFileDescriptor socket, String interfaze) {
try {
mContext.enforceCallingPermission(VPN, "protect");
- nativeProtect(socket.getFd(), name);
+ jniProtect(socket.getFd(), interfaze);
} finally {
try {
socket.close();
@@ -130,10 +86,78 @@
}
/**
- * Configure a TUN interface and return its file descriptor.
+ * 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.
*
- * @param configuration The parameters to configure the interface.
- * @return The file descriptor of the interface.
+ * 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.
+ 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 (mInterface != null) {
+ jniReset(mInterface);
+ mCallback.restore();
+ hideNotification();
+ mInterface = null;
+ }
+
+ // Send out the broadcast or stop LegacyVpnRunner.
+ if (!mPackage.equals(VpnConfig.LEGACY_VPN)) {
+ Intent intent = new Intent(VpnConfig.ACTION_VPN_REVOKED);
+ 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 " + mPackage + " to " + newPackage);
+ mPackage = newPackage;
+ return true;
+ }
+
+ /**
+ * 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 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 +167,7 @@
PackageManager pm = mContext.getPackageManager();
ApplicationInfo app = null;
try {
- app = pm.getApplicationInfo(mPackageName, 0);
+ app = pm.getApplicationInfo(mPackage, 0);
} catch (Exception e) {
return null;
}
@@ -151,89 +175,91 @@
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();
- // Replace the interface and abort if it fails.
+ // 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));
+ }
+
+ // 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);
+ config.startTime = SystemClock.elapsedRealtime();
- // 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))
+ .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext, config))
.setDefaults(Notification.DEFAULT_ALL)
.setOngoing(true)
.getNotification();
@@ -253,35 +279,39 @@
}
}
- 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.
+ * Start legacy VPN. 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.
+ public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
+ // Prepare for the new request. This also checks the caller.
+ prepare(null, VpnConfig.LEGACY_VPN);
+
+ // Start a new LegacyVpnRunner and we are done!
+ mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
+ mLegacyVpnRunner.start();
+ }
+
+ /**
+ * Return the information of the current ongoing legacy VPN.
+ */
+ public synchronized LegacyVpnInfo getLegacyVpnInfo() {
+ // Only system user can call this method.
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();
- }
-
- // Start a new runner and we are done!
- mLegacyVpnRunner = new LegacyVpnRunner(
- new String[] {"racoon", "mtpd"}, racoon, mtpd);
- mLegacyVpnRunner.start();
+ return (mLegacyVpnRunner == null) ? null : mLegacyVpnRunner.getInfo();
}
/**
@@ -293,27 +323,51 @@
*/
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 final LegacyVpnInfo mInfo;
+
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};
+ mInfo = new LegacyVpnInfo();
+
+ // Legacy VPN is not a real package, so we use it to carry the key.
+ mInfo.key = mConfig.packagz;
+ 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();
+ }
+
+ public LegacyVpnInfo getInfo() {
+ // Update the info when VPN is disconnected.
+ if (mInfo.state == LegacyVpnInfo.STATE_CONNECTED && mInterface == null) {
+ mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED;
+ mInfo.intent = null;
+ }
+ return mInfo;
}
@Override
public void run() {
// Wait for the previous thread since it has been interrupted.
- Log.v(TAG, "wait");
+ Log.v(TAG, "Waiting");
synchronized (TAG) {
- Log.v(TAG, "run");
+ Log.v(TAG, "Executing");
execute();
- Log.v(TAG, "exit");
}
}
@@ -325,7 +379,8 @@
} else if (now - mTimer <= 30000) {
Thread.sleep(yield ? 200 : 1);
} else {
- throw new InterruptedException("timeout");
+ mInfo.state = LegacyVpnInfo.STATE_TIMEOUT;
+ throw new IllegalStateException("time is up");
}
}
@@ -334,15 +389,16 @@
try {
// Initialize the timer.
checkpoint(false);
+ mInfo.state = LegacyVpnInfo.STATE_INITIALIZING;
- // 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,28 +412,30 @@
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);
}
if (!restart) {
+ mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED;
return;
}
+ mInfo.state = LegacyVpnInfo.STATE_CONNECTING;
- // 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 +443,7 @@
// 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 +458,22 @@
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,25 +484,57 @@
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 IllegalStateException(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");
+ // 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!");
+ mInfo.state = LegacyVpnInfo.STATE_CONNECTED;
+ mInfo.intent = VpnConfig.getIntentForStatusPanel(mContext, null);
+ }
} catch (Exception e) {
- Log.i(TAG, e.getMessage());
- for (String service : mServices) {
- SystemProperties.set("ctl.stop", service);
+ Log.i(TAG, "Aborting", e);
+ exit();
+ } finally {
+ // Do not leave an unstable state.
+ if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING ||
+ mInfo.state == LegacyVpnInfo.STATE_CONNECTING) {
+ mInfo.state = LegacyVpnInfo.STATE_FAILED;
}
}
}
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 @@
// 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 @@
* 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 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 @@
}
}
+ @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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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.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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
} 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 @@
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 @@
}
private void setAdbEnabled(boolean enable) {
+ if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
if (enable != mAdbEnabled) {
mAdbEnabled = enable;
String functions;
@@ -449,7 +361,7 @@
functions = removeFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB);
}
setCurrentFunction(functions, true);
- updateAdbNotification(mAdbEnabled && mConnected);
+ updateAdbNotification();
}
}
@@ -469,7 +381,7 @@
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 @@
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 @@
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 @@
}
break;
case MSG_SYSTEM_READY:
- updateUsbNotification(mConnected);
- updateAdbNotification(mAdbEnabled && mConnected);
+ updateUsbNotification();
+ updateAdbNotification();
updateUsbState();
if (mCurrentAccessory != null && mDeferAccessoryAttached) {
mSettingsManager.accessoryAttached(mCurrentAccessory);
@@ -576,6 +488,105 @@
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 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.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 @@
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 @@
} 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.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 @@
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 @@
}
}
} 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
jmethodID checkInjectEventsPermission;
- jmethodID filterTouchEvents;
- jmethodID filterJumpyTouchEvents;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
jmethodID getKeyRepeatTimeout;
@@ -381,18 +379,6 @@
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 @@
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 @@
#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 @@
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 @@
char address[65];
int prefix;
-
int chars;
int count = 0;
@@ -164,7 +152,7 @@
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 @@
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 @@
char address[65];
int prefix;
-
int chars;
int count = 0;
@@ -211,32 +193,50 @@
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 @@
count = BAD_ARGUMENT;
}
- close(inet4);
- close(inet6);
return count;
}
@@ -262,31 +260,25 @@
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 @@
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 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 @@
}
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 @@
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 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 @@
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 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 @@
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 @@
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 @@
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 @@
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 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 @@
+ " 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 @@
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 @@
+ 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.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 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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
mScript.set_gProgVertex(mProgVertex);
+
// For galaxy live wallpaper
mPvStarAlloc = new ScriptField_VpConsts(mRS, 1);
mScript.bind_vpConstants(mPvStarAlloc);
@@ -447,13 +443,11 @@
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 @@
}
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 @@
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 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 @@
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 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 @@
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 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 @@
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 @@
rsgBindDepthTarget(gRenderBufferDepth);
}
+rs_script gFontScript;
+rs_script gTorusScript;
+rs_allocation gDummyAlloc;
+
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;
- }
- }
- }
+ TestData testData;
+ testData.renderSurfaceW = gRenderSurfaceW;
+ testData.renderSurfaceH = gRenderSurfaceH;
+ testData.user = fillNum;
+ rsForEach(gFontScript, gDummyAlloc, gDummyAlloc, &testData);
}
static void bindProgramVertexOrtho() {
@@ -555,212 +511,37 @@
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 @@
"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 @@
displaySingletexFill(true, 10);
break;
case 18:
- displayMultitextureSample(true, 8);
+ displayMultitextureSample(true, 10);
break;
case 19:
displayPixelLightSamples(1, false);
@@ -992,14 +787,7 @@
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 @@
benchMode ++;
- gTorusRotation = 0;
-
if (benchMode == gMaxModes) {
rsSendToClientBlocking(RS_MSG_RESULTS_READY, gResultBuffer, gMaxModes*sizeof(float));
benchMode = 0;
@@ -1058,5 +844,30 @@
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..b635373b
--- /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/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 047eb8d..4e44402 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -1223,9 +1223,9 @@
private void establishCall(boolean enableKeepAlive) {
mState = SipSession.State.IN_CALL;
- mInCall = true;
cancelSessionTimer();
- if (enableKeepAlive) enableKeepAlive();
+ if (!mInCall && enableKeepAlive) enableKeepAlive();
+ mInCall = true;
mProxy.onCallEstablished(this, mPeerSessionDescription);
}
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.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());
- }
-}