Merge "Use android.os.Process.THREAD_PRIORITY_* symbols"
diff --git a/Android.mk b/Android.mk
index c838600..e987f91 100644
--- a/Android.mk
+++ b/Android.mk
@@ -110,7 +110,9 @@
core/java/android/net/IConnectivityManager.aidl \
core/java/android/net/INetworkManagementEventObserver.aidl \
core/java/android/net/IThrottleManager.aidl \
+ core/java/android/net/INetworkPolicyListener.aidl \
core/java/android/net/INetworkPolicyManager.aidl \
+ core/java/android/net/INetworkStatsService.aidl \
core/java/android/nfc/ILlcpConnectionlessSocket.aidl \
core/java/android/nfc/ILlcpServiceSocket.aidl \
core/java/android/nfc/ILlcpSocket.aidl \
@@ -123,7 +125,6 @@
core/java/android/os/IHardwareService.aidl \
core/java/android/os/IMessenger.aidl \
core/java/android/os/INetworkManagementService.aidl \
- core/java/android/os/INetStatService.aidl \
core/java/android/os/IPermissionController.aidl \
core/java/android/os/IPowerManager.aidl \
core/java/android/os/IRemoteCallback.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b19bed0..1e59ff6 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -98,6 +98,7 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
$(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)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/api/current.txt b/api/current.txt
index 9e87e9c..3a90ef7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6814,6 +6814,7 @@
method public void setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
method public void setDistinct(boolean);
method public void setProjectionMap(java.util.Map<java.lang.String, java.lang.String>);
+ method public void setStrict(boolean);
method public void setTables(java.lang.String);
}
@@ -11041,6 +11042,7 @@
method public static android.net.NetworkInfo.DetailedState valueOf(java.lang.String);
method public static final android.net.NetworkInfo.DetailedState[] values();
enum_constant public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
+ enum_constant public static final android.net.NetworkInfo.DetailedState BLOCKED;
enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTED;
enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTING;
enum_constant public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
@@ -20970,6 +20972,8 @@
}
public abstract interface MenuItem {
+ method public abstract boolean collapseActionView();
+ method public abstract boolean expandActionView();
method public abstract android.view.View getActionView();
method public abstract char getAlphabeticShortcut();
method public abstract int getGroupId();
@@ -20983,6 +20987,7 @@
method public abstract java.lang.CharSequence getTitle();
method public abstract java.lang.CharSequence getTitleCondensed();
method public abstract boolean hasSubMenu();
+ method public abstract boolean isActionViewExpanded();
method public abstract boolean isCheckable();
method public abstract boolean isChecked();
method public abstract boolean isEnabled();
@@ -20997,19 +21002,27 @@
method public abstract android.view.MenuItem setIcon(int);
method public abstract android.view.MenuItem setIntent(android.content.Intent);
method public abstract android.view.MenuItem setNumericShortcut(char);
+ method public abstract android.view.MenuItem setOnActionExpandListener(android.view.MenuItem.OnActionExpandListener);
method public abstract android.view.MenuItem setOnMenuItemClickListener(android.view.MenuItem.OnMenuItemClickListener);
method public abstract android.view.MenuItem setShortcut(char, char);
method public abstract void setShowAsAction(int);
+ method public abstract android.view.MenuItem setShowAsActionFlags(int);
method public abstract android.view.MenuItem setTitle(java.lang.CharSequence);
method public abstract android.view.MenuItem setTitle(int);
method public abstract android.view.MenuItem setTitleCondensed(java.lang.CharSequence);
method public abstract android.view.MenuItem setVisible(boolean);
field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
+ field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
field public static final int SHOW_AS_ACTION_IF_ROOM = 1; // 0x1
field public static final int SHOW_AS_ACTION_NEVER = 0; // 0x0
field public static final int SHOW_AS_ACTION_WITH_TEXT = 4; // 0x4
}
+ public static abstract interface MenuItem.OnActionExpandListener {
+ method public abstract boolean onMenuItemActionCollapse(android.view.MenuItem);
+ method public abstract boolean onMenuItemActionExpand(android.view.MenuItem);
+ }
+
public static abstract interface MenuItem.OnMenuItemClickListener {
method public abstract boolean onMenuItemClick(android.view.MenuItem);
}
@@ -23177,6 +23190,7 @@
public final class InputMethodSubtype implements android.os.Parcelable {
method public boolean containsExtraValueKey(java.lang.String);
method public int describeContents();
+ method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
method public int getIconResId();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a660bd7..aecec66 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1545,6 +1545,8 @@
public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
/** {@hide} */
+ public static final String NETWORK_STATS_SERVICE = "netstats";
+ /** {@hide} */
public static final String NETWORK_POLICY_SERVICE = "netpolicy";
/**
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index b6aca2b..9c09e81 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -24,8 +24,8 @@
import java.util.Iterator;
import java.util.Map;
-import java.util.Set;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -43,7 +43,7 @@
private StringBuilder mWhereClause = null; // lazily created
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
- private boolean mStrictProjectionMap;
+ private boolean mStrict;
public SQLiteQueryBuilder() {
mDistinct = false;
@@ -145,10 +145,37 @@
}
/**
+ * Need to keep this to not break the build until ContactsProvider2 has been changed to
+ * use the new API
+ * TODO: Remove this
* @hide
*/
public void setStrictProjectionMap(boolean flag) {
- mStrictProjectionMap = flag;
+ }
+
+ /**
+ * When set, the selection is verified against malicious arguments.
+ * When using this class to create a statement using
+ * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
+ * non-numeric limits will raise an exception. If a projection map is specified, fields
+ * not in that map will be ignored.
+ * If this class is used to execute the statement directly using
+ * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
+ * or
+ * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
+ * additionally also parenthesis escaping selection are caught.
+ *
+ * To summarize: To get maximum protection against malicious third party apps (for example
+ * content provider consumers), make sure to do the following:
+ * <ul>
+ * <li>Set this value to true</li>
+ * <li>Use a projection map</li>
+ * <li>Use one of the query overloads instead of getting the statement as a sql string</li>
+ * </ul>
+ * By default, this value is false.
+ */
+ public void setStrict(boolean flag) {
+ mStrict = flag;
}
/**
@@ -217,13 +244,6 @@
}
}
- private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) {
- if (!TextUtils.isEmpty(clause)) {
- s.append(name);
- DatabaseUtils.appendEscapedSQLString(s, clause);
- }
- }
-
/**
* Add the names that are non-null in columns to s, separating
* them with commas.
@@ -320,6 +340,19 @@
return null;
}
+ if (mStrict && selection != null && selection.length() > 0) {
+ // Validate the user-supplied selection to detect syntactic anomalies
+ // in the selection string that could indicate a SQL injection attempt.
+ // The idea is to ensure that the selection clause is a valid SQL expression
+ // by compiling it twice: once wrapped in parentheses and once as
+ // originally specified. An attacker cannot create an expression that
+ // would escape the SQL expression while maintaining balanced parentheses
+ // in both the wrapped and original forms.
+ String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
+ having, sortOrder, limit);
+ validateSql(db, sqlForValidation); // will throw if query is invalid
+ }
+
String sql = buildQuery(
projectionIn, selection, groupBy, having,
sortOrder, limit);
@@ -329,7 +362,20 @@
}
return db.rawQueryWithFactory(
mFactory, sql, selectionArgs,
- SQLiteDatabase.findEditTable(mTables));
+ SQLiteDatabase.findEditTable(mTables)); // will throw if query is invalid
+ }
+
+ /**
+ * Verifies that a SQL statement is valid by compiling it.
+ * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
+ */
+ private void validateSql(SQLiteDatabase db, String sql) {
+ db.lock(sql);
+ try {
+ new SQLiteCompiledSql(db, sql).releaseSqlStatement();
+ } finally {
+ db.unlock();
+ }
}
/**
@@ -541,7 +587,7 @@
continue;
}
- if (!mStrictProjectionMap &&
+ if (!mStrict &&
( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
/* A column alias already exist */
projection[i] = userColumn;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 419288b..c72c4b0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -22,7 +22,6 @@
import android.os.RemoteException;
import java.net.InetAddress;
-import java.net.UnknownHostException;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -40,8 +39,9 @@
* state of the available networks</li>
* </ol>
*/
-public class ConnectivityManager
-{
+public class ConnectivityManager {
+ private static final String TAG = "ConnectivityManager";
+
/**
* A change in network connectivity has occurred. A connection has either
* been established or lost. The NetworkInfo for the affected network is
@@ -109,7 +109,7 @@
* The lookup key for an int that provides information about
* our connection to the internet at large. 0 indicates no connection,
* 100 indicates a great connection. Retrieve it with
- * {@link android.content.Intent@getIntExtra(String)}.
+ * {@link android.content.Intent#getIntExtra(String, int)}.
* {@hide}
*/
public static final String EXTRA_INET_CONDITION = "inetCondition";
@@ -120,13 +120,12 @@
* <p>
* If an application uses the network in the background, it should listen
* for this broadcast and stop using the background data if the value is
- * false.
+ * {@code false}.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
"android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
-
/**
* Broadcast Action: The network connection may not be good
* uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
@@ -255,7 +254,7 @@
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
- private IConnectivityManager mService;
+ private final IConnectivityManager mService;
static public boolean isNetworkTypeValid(int networkType) {
return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
@@ -284,6 +283,15 @@
}
}
+ /** {@hide} */
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ try {
+ return mService.getActiveNetworkInfoForUid(uid);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
@@ -300,7 +308,7 @@
}
}
- /** @hide */
+ /** {@hide} */
public LinkProperties getActiveLinkProperties() {
try {
return mService.getActiveLinkProperties();
@@ -309,7 +317,7 @@
}
}
- /** @hide */
+ /** {@hide} */
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkProperties(networkType);
@@ -479,19 +487,11 @@
}
/**
- * Don't allow use of default constructor.
- */
- @SuppressWarnings({"UnusedDeclaration"})
- private ConnectivityManager() {
- }
-
- /**
* {@hide}
*/
public ConnectivityManager(IConnectivityManager service) {
if (service == null) {
- throw new IllegalArgumentException(
- "ConnectivityManager() cannot be constructed with null service");
+ throw new IllegalArgumentException("missing IConnectivityManager");
}
mService = service;
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 8be492c..647a60a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -33,13 +33,11 @@
int getNetworkPreference();
NetworkInfo getActiveNetworkInfo();
-
+ NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
-
NetworkInfo[] getAllNetworkInfo();
LinkProperties getActiveLinkProperties();
-
LinkProperties getLinkProperties(int networkType);
boolean setRadios(boolean onOff);
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
new file mode 100644
index 0000000..9230151
--- /dev/null
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.net;
+
+/** {@hide} */
+oneway interface INetworkPolicyListener {
+
+ void onRulesChanged(int uid, int uidRules);
+
+}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index d9351ee..c1f3530 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -16,6 +16,8 @@
package android.net;
+import android.net.INetworkPolicyListener;
+
/**
* Interface that creates and modifies network policy rules.
*
@@ -26,6 +28,11 @@
void setUidPolicy(int uid, int policy);
int getUidPolicy(int uid);
+ boolean isUidForeground(int uid);
+
+ void registerListener(INetworkPolicyListener listener);
+ void unregisterListener(INetworkPolicyListener listener);
+
// TODO: build API to surface stats details for settings UI
}
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
new file mode 100644
index 0000000..6d57036
--- /dev/null
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.net;
+
+import android.net.NetworkStatsHistory;
+
+/** {@hide} */
+interface INetworkStatsService {
+
+ NetworkStatsHistory[] getNetworkStatsSummary(int networkType);
+ NetworkStatsHistory getNetworkStatsUid(int uid);
+
+}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 5f5e11c..537750a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -74,7 +74,9 @@
/** IP traffic not available. */
DISCONNECTED,
/** Attempt to connect failed. */
- FAILED
+ FAILED,
+ /** Access to this network is blocked. */
+ BLOCKED
}
/**
@@ -96,6 +98,7 @@
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+ stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
}
private int mNetworkType;
@@ -138,6 +141,23 @@
mIsRoaming = false;
}
+ /** {@hide} */
+ public NetworkInfo(NetworkInfo source) {
+ if (source != null) {
+ mNetworkType = source.mNetworkType;
+ mSubtype = source.mSubtype;
+ mTypeName = source.mTypeName;
+ mSubtypeName = source.mSubtypeName;
+ mState = source.mState;
+ mDetailedState = source.mDetailedState;
+ mReason = source.mReason;
+ mExtraInfo = source.mExtraInfo;
+ mIsFailover = source.mIsFailover;
+ mIsRoaming = source.mIsRoaming;
+ mIsAvailable = source.mIsAvailable;
+ }
+ }
+
/**
* Reports the type of network (currently mobile or Wi-Fi) to which the
* info in this object pertains.
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 1913aa7..dd7c1b0 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.os.RemoteException;
+import java.io.PrintWriter;
+
/**
* Manager for creating and modifying network policy rules.
*
@@ -28,12 +30,13 @@
/** No specific network policy, use system default. */
public static final int POLICY_NONE = 0x0;
- /** Reject network usage when application in background. */
- public static final int POLICY_REJECT_BACKGROUND = 0x1;
- /** Reject network usage on paid network connections. */
- public static final int POLICY_REJECT_PAID = 0x2;
- /** Application should conserve data. */
- public static final int POLICY_CONSERVE_DATA = 0x4;
+ /** Reject network usage on paid networks when application in background. */
+ public static final int POLICY_REJECT_PAID_BACKGROUND = 0x1;
+
+ /** All network traffic should be allowed. */
+ public static final int RULE_ALLOW_ALL = 0x0;
+ /** Reject traffic on paid networks. */
+ public static final int RULE_REJECT_PAID = 0x1;
private INetworkPolicyManager mService;
@@ -51,9 +54,8 @@
/**
* Set policy flags for specific UID.
*
- * @param policy {@link #POLICY_NONE} or combination of
- * {@link #POLICY_REJECT_BACKGROUND}, {@link #POLICY_REJECT_PAID},
- * or {@link #POLICY_CONSERVE_DATA}.
+ * @param policy {@link #POLICY_NONE} or combination of flags like
+ * {@link #POLICY_REJECT_PAID_BACKGROUND}.
*/
public void setUidPolicy(int uid, int policy) {
try {
@@ -69,5 +71,23 @@
return POLICY_NONE;
}
}
+
+ /** {@hide} */
+ public static void dumpPolicy(PrintWriter fout, int policy) {
+ fout.write("[");
+ if ((policy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+ fout.write("REJECT_PAID_BACKGROUND");
+ }
+ fout.write("]");
+ }
+
+ /** {@hide} */
+ public static void dumpRules(PrintWriter fout, int rules) {
+ fout.write("[");
+ if ((rules & RULE_REJECT_PAID) != 0) {
+ fout.write("REJECT_PAID");
+ }
+ fout.write("]");
+ }
}
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 0f207bc..588bf64 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -22,19 +22,22 @@
import java.io.CharArrayWriter;
import java.io.PrintWriter;
+import java.util.HashSet;
/**
- * Collection of network statistics. Can contain summary details across all
- * interfaces, or details with per-UID granularity. Designed to parcel quickly
- * across process boundaries.
+ * Collection of active network statistics. Can contain summary details across
+ * all interfaces, or details with per-UID granularity. Internally stores data
+ * as a large table, closely matching {@code /proc/} data format. This structure
+ * optimizes for rapid in-memory comparison, but consider using
+ * {@link NetworkStatsHistory} when persisting.
*
* @hide
*/
public class NetworkStats implements Parcelable {
- /** {@link #iface} value when entry is summarized over all interfaces. */
+ /** {@link #iface} value when interface details unavailable. */
public static final String IFACE_ALL = null;
- /** {@link #uid} value when entry is summarized over all UIDs. */
- public static final int UID_ALL = 0;
+ /** {@link #uid} value when UID details unavailable. */
+ public static final int UID_ALL = -1;
// NOTE: data should only be accounted for once in this structure; if data
// is broken out, the summarized version should not be included.
@@ -49,7 +52,7 @@
public final long[] rx;
public final long[] tx;
- // TODO: add fg/bg stats and tag granularity
+ // TODO: add fg/bg stats once reported by kernel
private NetworkStats(long elapsedRealtime, String[] iface, int[] uid, long[] rx, long[] tx) {
this.elapsedRealtime = elapsedRealtime;
@@ -120,15 +123,35 @@
}
/**
+ * Return list of unique interfaces known by this data structure.
+ */
+ public String[] getKnownIfaces() {
+ final HashSet<String> ifaces = new HashSet<String>();
+ for (String iface : this.iface) {
+ if (iface != IFACE_ALL) {
+ ifaces.add(iface);
+ }
+ }
+ return ifaces.toArray(new String[ifaces.size()]);
+ }
+
+ /**
* Subtract the given {@link NetworkStats}, effectively leaving the delta
* between two snapshots in time. Assumes that statistics rows collect over
* time, and that none of them have disappeared.
+ *
+ * @param enforceMonotonic Validate that incoming value is strictly
+ * monotonic compared to this object.
*/
- public NetworkStats subtract(NetworkStats value) {
- // result will have our rows, but no meaningful timestamp
- final int length = length();
- final NetworkStats.Builder result = new NetworkStats.Builder(-1, length);
+ public NetworkStats subtract(NetworkStats value, boolean enforceMonotonic) {
+ final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
+ if (enforceMonotonic && deltaRealtime < 0) {
+ throw new IllegalArgumentException("found non-monotonic realtime");
+ }
+ // result will have our rows, and elapsed time between snapshots
+ final int length = length();
+ final NetworkStats.Builder result = new NetworkStats.Builder(deltaRealtime, length);
for (int i = 0; i < length; i++) {
final String iface = this.iface[i];
final int uid = this.uid[i];
@@ -142,6 +165,9 @@
// existing row, subtract remote value
final long rx = this.rx[i] - value.rx[j];
final long tx = this.tx[i] - value.tx[j];
+ if (enforceMonotonic && (rx < 0 || tx < 0)) {
+ throw new IllegalArgumentException("found non-monotonic values");
+ }
result.addEntry(iface, uid, rx, tx);
}
}
diff --git a/core/java/android/net/NetworkStatsHistory.aidl b/core/java/android/net/NetworkStatsHistory.aidl
new file mode 100644
index 0000000..8b9069f
--- /dev/null
+++ b/core/java/android/net/NetworkStatsHistory.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.net;
+
+parcelable NetworkStatsHistory;
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
new file mode 100644
index 0000000..b16101f
--- /dev/null
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -0,0 +1,285 @@
+/*
+ * 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.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.CharArrayWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Collection of historical network statistics, recorded into equally-sized
+ * "buckets" in time. Internally it stores data in {@code long} series for more
+ * efficient persistence.
+ * <p>
+ * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
+ * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
+ * sorted at all times.
+ *
+ * @hide
+ */
+public class NetworkStatsHistory implements Parcelable {
+ private static final int VERSION = 1;
+
+ /** {@link #uid} value when UID details unavailable. */
+ public static final int UID_ALL = -1;
+
+ // TODO: teach about zigzag encoding to use less disk space
+ // TODO: teach how to convert between bucket sizes
+
+ public final int networkType;
+ public final String identity;
+ public final int uid;
+ public final long bucketDuration;
+
+ int bucketCount;
+ long[] bucketStart;
+ long[] rx;
+ long[] tx;
+
+ public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) {
+ this.networkType = networkType;
+ this.identity = identity;
+ this.uid = uid;
+ this.bucketDuration = bucketDuration;
+ bucketStart = new long[0];
+ rx = new long[0];
+ tx = new long[0];
+ bucketCount = bucketStart.length;
+ }
+
+ public NetworkStatsHistory(Parcel in) {
+ networkType = in.readInt();
+ identity = in.readString();
+ uid = in.readInt();
+ bucketDuration = in.readLong();
+ bucketStart = readLongArray(in);
+ rx = in.createLongArray();
+ tx = in.createLongArray();
+ bucketCount = bucketStart.length;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(networkType);
+ out.writeString(identity);
+ out.writeInt(uid);
+ out.writeLong(bucketDuration);
+ writeLongArray(out, bucketStart, bucketCount);
+ writeLongArray(out, rx, bucketCount);
+ writeLongArray(out, tx, bucketCount);
+ }
+
+ public NetworkStatsHistory(DataInputStream in) throws IOException {
+ final int version = in.readInt();
+ networkType = in.readInt();
+ identity = in.readUTF();
+ uid = in.readInt();
+ bucketDuration = in.readLong();
+ bucketStart = readLongArray(in);
+ rx = readLongArray(in);
+ tx = readLongArray(in);
+ bucketCount = bucketStart.length;
+ }
+
+ public void writeToStream(DataOutputStream out) throws IOException {
+ out.writeInt(VERSION);
+ out.writeInt(networkType);
+ out.writeUTF(identity);
+ out.writeInt(uid);
+ out.writeLong(bucketDuration);
+ writeLongArray(out, bucketStart, bucketCount);
+ writeLongArray(out, rx, bucketCount);
+ writeLongArray(out, tx, bucketCount);
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Record that data traffic occurred in the given time range. Will
+ * distribute across internal buckets, creating new buckets as needed.
+ */
+ public void recordData(long start, long end, long rx, long tx) {
+ // create any buckets needed by this range
+ ensureBuckets(start, end);
+
+ // distribute data usage into buckets
+ final long duration = end - start;
+ for (int i = bucketCount - 1; i >= 0; i--) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // bucket is older than record; we're finished
+ if (curEnd < start) break;
+ // bucket is newer than record; keep looking
+ if (curStart > end) continue;
+
+ final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
+ if (overlap > 0) {
+ this.rx[i] += rx * overlap / duration;
+ this.tx[i] += tx * overlap / duration;
+ }
+ }
+ }
+
+ /**
+ * Ensure that buckets exist for given time range, creating as needed.
+ */
+ private void ensureBuckets(long start, long end) {
+ // normalize incoming range to bucket boundaries
+ start -= start % bucketDuration;
+ end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
+
+ for (long now = start; now < end; now += bucketDuration) {
+ // try finding existing bucket
+ final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
+ if (index < 0) {
+ // bucket missing, create and insert
+ insertBucket(~index, now);
+ }
+ }
+ }
+
+ /**
+ * Insert new bucket at requested index and starting time.
+ */
+ private void insertBucket(int index, long start) {
+ // create more buckets when needed
+ if (bucketCount + 1 > bucketStart.length) {
+ final int newLength = bucketStart.length + 10;
+ bucketStart = Arrays.copyOf(bucketStart, newLength);
+ rx = Arrays.copyOf(rx, newLength);
+ tx = Arrays.copyOf(tx, newLength);
+ }
+
+ // create gap when inserting bucket in middle
+ if (index < bucketCount) {
+ final int dstPos = index + 1;
+ final int length = bucketCount - index;
+
+ System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
+ System.arraycopy(rx, index, rx, dstPos, length);
+ System.arraycopy(tx, index, tx, dstPos, length);
+ }
+
+ bucketStart[index] = start;
+ rx[index] = 0;
+ tx[index] = 0;
+ bucketCount++;
+ }
+
+ /**
+ * Remove buckets older than requested cutoff.
+ */
+ public void removeBucketsBefore(long cutoff) {
+ int i;
+ for (i = 0; i < bucketCount; i++) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // cutoff happens before or during this bucket; everything before
+ // this bucket should be removed.
+ if (curEnd > cutoff) break;
+ }
+
+ if (i > 0) {
+ final int length = bucketStart.length;
+ bucketStart = Arrays.copyOfRange(bucketStart, i, length);
+ rx = Arrays.copyOfRange(rx, i, length);
+ tx = Arrays.copyOfRange(tx, i, length);
+ bucketCount -= i;
+ }
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ // TODO: consider stripping identity when dumping
+ pw.print(prefix);
+ pw.print("NetworkStatsHistory: networkType="); pw.print(networkType);
+ pw.print(" identity="); pw.print(identity);
+ pw.print(" uid="); pw.println(uid);
+ for (int i = 0; i < bucketCount; i++) {
+ pw.print(prefix);
+ pw.print(" timestamp="); pw.print(bucketStart[i]);
+ pw.print(" rx="); pw.print(rx[i]);
+ pw.print(" tx="); pw.println(tx[i]);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump("", new PrintWriter(writer));
+ return writer.toString();
+ }
+
+ public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
+ public NetworkStatsHistory createFromParcel(Parcel in) {
+ return new NetworkStatsHistory(in);
+ }
+
+ public NetworkStatsHistory[] newArray(int size) {
+ return new NetworkStatsHistory[size];
+ }
+ };
+
+ private static long[] readLongArray(DataInputStream in) throws IOException {
+ final int size = in.readInt();
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException {
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeLong(values[i]);
+ }
+ }
+
+ private static long[] readLongArray(Parcel in) {
+ final int size = in.readInt();
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ private static void writeLongArray(Parcel out, long[] values, int size) {
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeLong(values[i]);
+ }
+ }
+
+}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index c0ff734..8ab64fa 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -141,7 +141,8 @@
// subtract starting values and return delta
final NetworkStats profilingStop = getNetworkStatsForUid(context);
- final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
+ final NetworkStats profilingDelta = profilingStop.subtract(
+ sActiveProfilingStart, false);
sActiveProfilingStart = null;
return profilingDelta;
}
diff --git a/core/java/android/os/INetStatService.aidl b/core/java/android/os/INetStatService.aidl
deleted file mode 100644
index a8f3de0..0000000
--- a/core/java/android/os/INetStatService.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/**
- * Retrieves packet and byte counts for the phone data interface,
- * and for all interfaces.
- * Used for the data activity icon and the phone status in Settings.
- *
- * {@hide}
- */
-interface INetStatService {
- long getMobileTxPackets();
- long getMobileRxPackets();
- long getMobileTxBytes();
- long getMobileRxBytes();
- long getTotalTxPackets();
- long getTotalRxPackets();
- long getTotalTxBytes();
- long getTotalRxBytes();
-}
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 780c52e..dc68264 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -51,6 +51,13 @@
* it also has an icon specified.
*/
public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
+
+ /**
+ * This item's action view collapses to a normal menu item.
+ * When expanded, the action view temporarily takes over
+ * a larger segment of its container.
+ */
+ public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
/**
* Interface definition for a callback to be invoked when a menu item is
@@ -74,6 +81,34 @@
}
/**
+ * Interface definition for a callback to be invoked when a menu item
+ * marked with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is
+ * expanded or collapsed.
+ *
+ * @see MenuItem#expandActionView()
+ * @see MenuItem#collapseActionView()
+ * @see MenuItem#setShowAsActionFlags(int)
+ * @see MenuItem#
+ */
+ public interface OnActionExpandListener {
+ /**
+ * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+ * is expanded.
+ * @param item Item that was expanded
+ * @return true if the item should expand, false if expansion should be suppressed.
+ */
+ public boolean onMenuItemActionExpand(MenuItem item);
+
+ /**
+ * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+ * is collapsed.
+ * @param item Item that was collapsed
+ * @return true if the item should collapse, false if collapsing should be suppressed.
+ */
+ public boolean onMenuItemActionCollapse(MenuItem item);
+ }
+
+ /**
* Return the identifier for this menu item. The identifier can not
* be changed after the menu is created.
*
@@ -421,6 +456,27 @@
public void setShowAsAction(int actionEnum);
/**
+ * Sets how this item should display in the presence of an Action Bar.
+ * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS},
+ * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should
+ * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}.
+ * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action,
+ * it should be shown with a text label.
+ *
+ * <p>Note: This method differs from {@link #setShowAsAction(int)} only in that it
+ * returns the current MenuItem instance for call chaining.
+ *
+ * @param actionEnum How the item should display. One of
+ * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or
+ * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default.
+ *
+ * @see android.app.ActionBar
+ * @see #setActionView(View)
+ * @return This MenuItem instance for call chaining.
+ */
+ public MenuItem setShowAsActionFlags(int actionEnum);
+
+ /**
* 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.
@@ -453,4 +509,52 @@
* @see #setShowAsAction(int)
*/
public View getActionView();
+
+ /**
+ * 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}.
+ * If a listener has been set using {@link #setOnActionExpandListener(OnActionExpandListener)}
+ * it will have its {@link OnActionExpandListener#onMenuItemActionExpand(MenuItem)}
+ * method invoked. The listener may return false from this method to prevent expanding
+ * the action view.
+ *
+ * @return true if the action view was expanded, false otherwise.
+ */
+ public boolean expandActionView();
+
+ /**
+ * Collapse 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}. If a listener has been set using
+ * {@link #setOnActionExpandListener(OnActionExpandListener)} it will have its
+ * {@link OnActionExpandListener#onMenuItemActionCollapse(MenuItem)} method invoked.
+ * The listener may return false from this method to prevent collapsing the action view.
+ *
+ * @return true if the action view was collapsed, false otherwise.
+ */
+ public boolean collapseActionView();
+
+ /**
+ * Returns true if this menu item's action view has been expanded.
+ *
+ * @return true if the item's action view is expanded, false otherwise.
+ *
+ * @see #expandActionView()
+ * @see #collapseActionView()
+ * @see #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
+ * @see OnActionExpandListener
+ */
+ public boolean isActionViewExpanded();
+
+ /**
+ * Set an {@link OnActionExpandListener} on this menu item to be notified when
+ * the associated action view is expanded or collapsed. The menu item must
+ * be configured to expand or collapse its action view using the flag
+ * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+ *
+ * @param listener Listener that will respond to expand/collapse events
+ * @return This menu item instance for call chaining
+ */
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener);
}
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 1f7441d..fee69e0 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -145,7 +145,9 @@
a.getString(com.android.internal.R.styleable
.InputMethod_Subtype_imeSubtypeMode),
a.getString(com.android.internal.R.styleable
- .InputMethod_Subtype_imeSubtypeExtraValue));
+ .InputMethod_Subtype_imeSubtypeExtraValue),
+ a.getBoolean(com.android.internal.R.styleable
+ .InputMethod_Subtype_isAuxiliary, false));
mSubtypes.add(subtype);
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index de38fbe..20cf93d 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -17,8 +17,10 @@
package android.view.inputmethod;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Slog;
import java.util.ArrayList;
@@ -137,6 +139,31 @@
return mIsAuxiliary;
}
+ /**
+ * @param context Context will be used for getting Locale and PackageManager.
+ * @param packageName The package name of the IME
+ * @param appInfo The application info of the IME
+ * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
+ * can have only one %s in it. If there is, the %s part will be replaced with the locale's
+ * display name by the formatter. If there is not, this method simply returns the string
+ * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
+ * framework to generate an appropriate display name.
+ */
+ public CharSequence getDisplayName(
+ Context context, String packageName, ApplicationInfo appInfo) {
+ final String locale = context.getResources().getConfiguration().locale.getDisplayName();
+ if (mSubtypeNameResId == 0) {
+ return locale;
+ }
+ final String subtypeName = context.getPackageManager().getText(
+ packageName, mSubtypeNameResId, appInfo).toString();
+ if (!TextUtils.isEmpty(subtypeName)) {
+ return String.format(subtypeName, locale);
+ } else {
+ return locale;
+ }
+ }
+
private HashMap<String, String> getExtraValueHashMap() {
if (mExtraValueHashMapCache == null) {
mExtraValueHashMapCache = new HashMap<String, String>();
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 2a1398d..4ee16e7 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -16,6 +16,8 @@
package android.widget;
+import java.util.ArrayList;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -23,14 +25,12 @@
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.Gravity;
import android.widget.RemoteViews.RemoteView;
-import java.util.ArrayList;
-
/**
* FrameLayout is designed to block out an area on the screen to display
@@ -364,10 +364,10 @@
gravity = DEFAULT_CHILD_GRAVITY;
}
- final int horizontalGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
- switch (horizontalGravity) {
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
childLeft = parentLeft + lp.leftMargin;
break;
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 8d449e0..298fe00 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -1430,8 +1430,8 @@
if (gravity < 0) {
gravity = minorGravity;
}
- gravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
- switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, isLayoutRtl());
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index 0ef4861..a4bcf60 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -236,4 +236,31 @@
public MenuItem setActionView(int resId) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ setShowAsAction(actionEnum);
+ return this;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean isActionViewExpanded() {
+ return false;
+ }
+
+ @Override
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ // No need to save the listener; ActionMenuItem does not support collapsing items.
+ 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 0051ec3..91dd7e36 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -112,8 +112,11 @@
@Override
public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
- final View actionView = item.getActionView();
- return actionView != null ? actionView : super.getItemView(item, convertView, parent);
+ View actionView = item.getActionView();
+ actionView = actionView != null && !item.hasCollapsibleActionView() ?
+ actionView : super.getItemView(item, convertView, parent);
+ actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
+ return actionView;
}
@Override
@@ -303,7 +306,7 @@
if (item.requiresActionButton()) {
View v = item.getActionView();
- if (v == null) {
+ if (v == null || item.hasCollapsibleActionView()) {
v = getItemView(item, mScrapActionButtonView, parent);
if (mScrapActionButtonView == null) {
mScrapActionButtonView = v;
@@ -329,7 +332,7 @@
if (isAction) {
View v = item.getActionView();
- if (v == null) {
+ if (v == null || item.hasCollapsibleActionView()) {
v = getItemView(item, mScrapActionButtonView, parent);
if (mScrapActionButtonView == null) {
mScrapActionButtonView = v;
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
index 16f51fd..ddbb08c 100644
--- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -192,4 +192,12 @@
public boolean flagActionItems() {
return false;
}
+
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
index 2cb2a10..f8d24a3 100644
--- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -159,6 +159,14 @@
return false;
}
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
public void saveHierarchyState(Bundle outState) {
SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
if (mMenuView != null) {
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index e9fcb23..fdfa954 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -744,11 +744,14 @@
if (itemImpl == null || !itemImpl.isEnabled()) {
return false;
- }
+ }
boolean invoked = itemImpl.invoke();
- if (item.hasSubMenu()) {
+ if (itemImpl.hasCollapsibleActionView()) {
+ invoked |= itemImpl.expandActionView();
+ if (invoked) close(true);
+ } else if (item.hasSubMenu()) {
close(false);
invoked |= dispatchSubMenuSelected((SubMenuBuilder) item.getSubMenu());
@@ -1081,4 +1084,42 @@
boolean getOptionalIconsVisible() {
return mOptionalIconsVisible;
}
+
+ public boolean expandItemActionView(MenuItemImpl item) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean expanded = false;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if ((expanded = presenter.expandItemActionView(this, item))) {
+ break;
+ }
+ }
+ startDispatchingItemsChanged();
+
+ return expanded;
+ }
+
+ public boolean collapseItemActionView(MenuItemImpl item) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean collapsed = false;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if ((collapsed = presenter.collapseItemActionView(this, item))) {
+ break;
+ }
+ }
+ startDispatchingItemsChanged();
+
+ return collapsed;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index c6d386d..f2430e4 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -77,6 +77,8 @@
private int mShowAsAction = SHOW_AS_ACTION_NEVER;
private View mActionView;
+ private OnActionExpandListener mOnActionExpandListener;
+ private boolean mIsActionViewExpanded = false;
/** Used for the icon resource ID if this item does not have an icon */
static final int NO_ICON = 0;
@@ -561,4 +563,61 @@
public View getActionView() {
return mActionView;
}
+
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ setShowAsAction(actionEnum);
+ return this;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) {
+ return false;
+ }
+
+ if (mOnActionExpandListener == null ||
+ mOnActionExpandListener.onMenuItemActionExpand(this)) {
+ return mMenu.expandItemActionView(this);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
+ return false;
+ }
+ if (mActionView == null) {
+ // We're already collapsed if we have no action view.
+ return true;
+ }
+
+ if (mOnActionExpandListener == null ||
+ mOnActionExpandListener.onMenuItemActionCollapse(this)) {
+ return mMenu.collapseItemActionView(this);
+ }
+
+ return false;
+ }
+
+ @Override
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ mOnActionExpandListener = listener;
+ return this;
+ }
+
+ public boolean hasCollapsibleActionView() {
+ return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null;
+ }
+
+ public void setActionViewExpanded(boolean isExpanded) {
+ mIsActionViewExpanded = isExpanded;
+ mMenu.onItemsChanged(false);
+ }
+
+ public boolean isActionViewExpanded() {
+ return mIsActionViewExpanded;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 38cec29..5767519 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -250,6 +250,14 @@
return false;
}
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
private class MenuAdapter extends BaseAdapter {
private MenuBuilder mAdapterMenu;
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
index 5baf419..bd66448 100644
--- a/core/java/com/android/internal/view/menu/MenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -107,4 +107,22 @@
* @return true if this presenter changed the action status of any items.
*/
public boolean flagActionItems();
+
+ /**
+ * Called when a menu item with a collapsable action view should expand its action view.
+ *
+ * @param menu Menu containing the item to be expanded
+ * @param item Item to be expanded
+ * @return true if this presenter expanded the action view, false otherwise.
+ */
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+ /**
+ * Called when a menu item with a collapsable action view should collapse its action view.
+ *
+ * @param menu Menu containing the item to be collapsed
+ * @param item Item to be collapsed
+ * @return true if this presenter collapsed the action view, false otherwise.
+ */
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item);
}
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index 834041f..fb1cd5e 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -111,4 +111,14 @@
public SubMenu setHeaderView(View view) {
return (SubMenu) super.setHeaderViewInt(view);
}
+
+ @Override
+ public boolean expandItemActionView(MenuItemImpl item) {
+ return mParentMenu.expandItemActionView(item);
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuItemImpl item) {
+ return mParentMenu.collapseItemActionView(item);
+ }
}
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 788883b..ccbce3e 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -30,7 +30,7 @@
public abstract class AbsActionBarView extends ViewGroup {
protected ActionMenuView mMenuView;
- protected ActionMenuPresenter mMenuPresenter;
+ protected ActionMenuPresenter mActionMenuPresenter;
protected ActionBarContainer mSplitView;
protected Animator mVisibilityAnim;
@@ -108,8 +108,8 @@
}
public boolean showOverflowMenu() {
- if (mMenuPresenter != null) {
- return mMenuPresenter.showOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.showOverflowMenu();
}
return false;
}
@@ -123,26 +123,26 @@
}
public boolean hideOverflowMenu() {
- if (mMenuPresenter != null) {
- return mMenuPresenter.hideOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.hideOverflowMenu();
}
return false;
}
public boolean isOverflowMenuShowing() {
- if (mMenuPresenter != null) {
- return mMenuPresenter.isOverflowMenuShowing();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.isOverflowMenuShowing();
}
return false;
}
public boolean isOverflowReserved() {
- return mMenuPresenter != null && mMenuPresenter.isOverflowReserved();
+ return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
}
public void dismissPopupMenus() {
- if (mMenuPresenter != null) {
- mMenuPresenter.dismissPopupMenus();
+ if (mActionMenuPresenter != null) {
+ mActionMenuPresenter.dismissPopupMenus();
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index deed1c5..e4c4989 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -167,9 +167,9 @@
});
final MenuBuilder menu = (MenuBuilder) mode.getMenu();
- mMenuPresenter = new ActionMenuPresenter();
- menu.addMenuPresenter(mMenuPresenter);
- mMenuView = (ActionMenuView) mMenuPresenter.getMenuView(this);
+ mActionMenuPresenter = new ActionMenuPresenter();
+ menu.addMenuPresenter(mActionMenuPresenter);
+ mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
@@ -178,10 +178,10 @@
addView(mMenuView);
} else {
// Allow full screen width in split mode.
- mMenuPresenter.setWidthLimit(
+ mActionMenuPresenter.setWidthLimit(
getContext().getResources().getDisplayMetrics().widthPixels, true);
// No limit to the item count; use whatever will fit.
- mMenuPresenter.setItemLimit(Integer.MAX_VALUE);
+ mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
// Span the whole width
layoutParams.width = LayoutParams.MATCH_PARENT;
mSplitView.addView(mMenuView);
@@ -227,24 +227,24 @@
@Override
public boolean showOverflowMenu() {
- if (mMenuPresenter != null) {
- return mMenuPresenter.showOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.showOverflowMenu();
}
return false;
}
@Override
public boolean hideOverflowMenu() {
- if (mMenuPresenter != null) {
- return mMenuPresenter.hideOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.hideOverflowMenu();
}
return false;
}
@Override
public boolean isOverflowMenuShowing() {
- if (mMenuPresenter != null) {
- return mMenuPresenter.isOverflowMenuShowing();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.isOverflowMenuShowing();
}
return false;
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index b7aac14..eb97ea8 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -21,7 +21,10 @@
import com.android.internal.view.menu.ActionMenuPresenter;
import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.SubMenuBuilder;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
@@ -33,21 +36,22 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.text.TextUtils;
-import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@@ -87,9 +91,8 @@
private Drawable mIcon;
private Drawable mLogo;
- private View mHomeLayout;
- private View mHomeAsUpView;
- private ImageView mIconView;
+ private HomeView mHomeLayout;
+ private HomeView mExpandedHomeLayout;
private LinearLayout mTitleLayout;
private TextView mTitleView;
private TextView mSubtitleView;
@@ -124,6 +127,9 @@
private Runnable mTabSelector;
+ private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
+ View mExpandedActionView;
+
private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView parent, View view, int position, long id) {
@@ -136,7 +142,15 @@
}
};
- private OnClickListener mTabClickListener = null;
+ private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final MenuItemImpl item = mExpandedMenuPresenter.mCurrentExpandedItem;
+ if (item != null) {
+ item.collapseActionView();
+ }
+ }
+ };
public ActionBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -187,10 +201,11 @@
com.android.internal.R.styleable.ActionBar_homeLayout,
com.android.internal.R.layout.action_bar_home);
- mHomeLayout = inflater.inflate(homeResId, this, false);
+ mHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
- mHomeAsUpView = mHomeLayout.findViewById(com.android.internal.R.id.up);
- mIconView = (ImageView) mHomeLayout.findViewById(com.android.internal.R.id.home);
+ mExpandedHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
+ mExpandedHomeLayout.setUp(true);
+ mExpandedHomeLayout.setOnClickListener(mExpandedActionViewUpListener);
mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
@@ -285,7 +300,7 @@
public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
mTabScrollView = tabs;
mIncludeTabs = tabs != null;
- if (mIncludeTabs) {
+ if (mIncludeTabs && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
addView(mTabScrollView);
}
}
@@ -298,7 +313,8 @@
if (menu == mOptionsMenu) return;
if (mOptionsMenu != null) {
- mOptionsMenu.removeMenuPresenter(mMenuPresenter);
+ mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
+ mOptionsMenu.removeMenuPresenter(mExpandedMenuPresenter);
}
MenuBuilder builder = (MenuBuilder) menu;
@@ -306,13 +322,15 @@
if (mMenuView != null) {
removeView(mMenuView);
}
- if (mMenuPresenter == null) {
- mMenuPresenter = new ActionMenuPresenter();
- mMenuPresenter.setCallback(cb);
+ if (mActionMenuPresenter == null) {
+ mActionMenuPresenter = new ActionMenuPresenter();
+ mActionMenuPresenter.setCallback(cb);
+ mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
}
- builder.addMenuPresenter(mMenuPresenter);
+ builder.addMenuPresenter(mActionMenuPresenter);
+ builder.addMenuPresenter(mExpandedMenuPresenter);
- final ActionMenuView menuView = (ActionMenuView) mMenuPresenter.getMenuView(this);
+ final ActionMenuView menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
menuView.setLayoutParams(layoutParams);
@@ -320,10 +338,10 @@
addView(menuView);
} else {
// Allow full screen width in split mode.
- mMenuPresenter.setWidthLimit(
+ mActionMenuPresenter.setWidthLimit(
getContext().getResources().getDisplayMetrics().widthPixels, true);
// No limit to the item count; use whatever will fit.
- mMenuPresenter.setItemLimit(Integer.MAX_VALUE);
+ mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
// Span the whole width
layoutParams.width = LayoutParams.MATCH_PARENT;
if (mSplitView != null) {
@@ -411,13 +429,12 @@
mHomeLayout.setVisibility(vis);
if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
- final boolean isUp = (options & ActionBar.DISPLAY_HOME_AS_UP) != 0;
- mHomeAsUpView.setVisibility(isUp ? VISIBLE : GONE);
+ mHomeLayout.setUp((options & ActionBar.DISPLAY_HOME_AS_UP) != 0);
}
if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) {
final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
- mIconView.setImageDrawable(logoVis ? mLogo : mIcon);
+ mHomeLayout.setIcon(logoVis ? mLogo : mIcon);
}
if ((flagsChanged & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
@@ -457,7 +474,7 @@
mIcon = icon;
if (icon != null &&
((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) {
- mIconView.setImageDrawable(icon);
+ mHomeLayout.setIcon(icon);
}
}
@@ -468,7 +485,7 @@
public void setLogo(Drawable logo) {
mLogo = logo;
if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
- mIconView.setImageDrawable(logo);
+ mHomeLayout.setIcon(logo);
}
}
@@ -482,7 +499,7 @@
private int getPreferredIconDensity() {
final Resources res = mContext.getResources();
final int availableHeight = getLayoutParams().height -
- mIconView.getPaddingTop() - mIconView.getPaddingBottom();
+ mHomeLayout.getVerticalIconPadding();
int iconSize = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
if (iconSize * DisplayMetrics.DENSITY_LOW >= availableHeight) {
@@ -500,7 +517,7 @@
if (mode != oldMode) {
switch (oldMode) {
case ActionBar.NAVIGATION_MODE_LIST:
- if (mSpinner != null) {
+ if (mListNavLayout != null) {
removeView(mListNavLayout);
}
break;
@@ -685,10 +702,13 @@
int leftOfCenter = availableWidth / 2;
int rightOfCenter = leftOfCenter;
- if (mHomeLayout.getVisibility() != GONE) {
- mHomeLayout.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+ View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
+
+ if (homeLayout.getVisibility() != GONE) {
+ homeLayout.measure(
+ MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- final int homeWidth = mHomeLayout.getMeasuredWidth();
+ final int homeWidth = homeLayout.getMeasuredWidth();
availableWidth = Math.max(0, availableWidth - homeWidth);
leftOfCenter = Math.max(0, availableWidth - homeWidth);
}
@@ -699,40 +719,42 @@
rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth());
}
- boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
- (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
- if (showTitle) {
- availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
- leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
- }
+ if (mExpandedActionView == null) {
+ boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
+ (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+ if (showTitle) {
+ availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
+ leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
+ }
- switch (mNavigationMode) {
- case ActionBar.NAVIGATION_MODE_LIST:
- if (mListNavLayout != null) {
- final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
- availableWidth = Math.max(0, availableWidth - itemPaddingSize);
- leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
- mListNavLayout.measure(
- MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- final int listNavWidth = mListNavLayout.getMeasuredWidth();
- availableWidth = Math.max(0, availableWidth - listNavWidth);
- leftOfCenter = Math.max(0, leftOfCenter - listNavWidth);
+ switch (mNavigationMode) {
+ case ActionBar.NAVIGATION_MODE_LIST:
+ if (mListNavLayout != null) {
+ final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
+ availableWidth = Math.max(0, availableWidth - itemPaddingSize);
+ leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
+ mListNavLayout.measure(
+ MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ final int listNavWidth = mListNavLayout.getMeasuredWidth();
+ availableWidth = Math.max(0, availableWidth - listNavWidth);
+ leftOfCenter = Math.max(0, leftOfCenter - listNavWidth);
+ }
+ break;
+ case ActionBar.NAVIGATION_MODE_TABS:
+ if (mTabScrollView != null) {
+ final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
+ availableWidth = Math.max(0, availableWidth - itemPaddingSize);
+ leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
+ mTabScrollView.measure(
+ MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ final int tabWidth = mTabScrollView.getMeasuredWidth();
+ availableWidth = Math.max(0, availableWidth - tabWidth);
+ leftOfCenter = Math.max(0, leftOfCenter - tabWidth);
+ }
+ break;
}
- break;
- case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabScrollView != null) {
- final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
- availableWidth = Math.max(0, availableWidth - itemPaddingSize);
- leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
- mTabScrollView.measure(
- MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- final int tabWidth = mTabScrollView.getMeasuredWidth();
- availableWidth = Math.max(0, availableWidth - tabWidth);
- leftOfCenter = Math.max(0, leftOfCenter - tabWidth);
- }
- break;
}
if (mIndeterminateProgressView != null &&
@@ -743,8 +765,16 @@
rightOfCenter - mIndeterminateProgressView.getMeasuredWidth());
}
- if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
- final LayoutParams lp = generateLayoutParams(mCustomNavView.getLayoutParams());
+ View customView = null;
+ if (mExpandedActionView != null) {
+ customView = mExpandedActionView;
+ } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
+ mCustomNavView != null) {
+ customView = mCustomNavView;
+ }
+
+ if (customView != null) {
+ final LayoutParams lp = generateLayoutParams(customView.getLayoutParams());
final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
(ActionBar.LayoutParams) lp : null;
@@ -781,7 +811,7 @@
customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2;
}
- mCustomNavView.measure(
+ customView.measure(
MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
}
@@ -822,31 +852,34 @@
return;
}
- if (mHomeLayout.getVisibility() != GONE) {
- x += positionChild(mHomeLayout, x, y, contentHeight);
- }
-
- final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
- (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
- if (showTitle) {
- x += positionChild(mTitleLayout, x, y, contentHeight);
+ View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
+ if (homeLayout.getVisibility() != GONE) {
+ x += positionChild(homeLayout, x, y, contentHeight);
}
- switch (mNavigationMode) {
- case ActionBar.NAVIGATION_MODE_STANDARD:
- break;
- case ActionBar.NAVIGATION_MODE_LIST:
- if (mListNavLayout != null) {
- if (showTitle) x += mItemPadding;
- x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
+ if (mExpandedActionView == null) {
+ final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
+ (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+ if (showTitle) {
+ x += positionChild(mTitleLayout, x, y, contentHeight);
}
- break;
- case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabScrollView != null) {
- if (showTitle) x += mItemPadding;
- x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
+
+ switch (mNavigationMode) {
+ case ActionBar.NAVIGATION_MODE_STANDARD:
+ break;
+ case ActionBar.NAVIGATION_MODE_LIST:
+ if (mListNavLayout != null) {
+ if (showTitle) x += mItemPadding;
+ x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
+ }
+ break;
+ case ActionBar.NAVIGATION_MODE_TABS:
+ if (mTabScrollView != null) {
+ if (showTitle) x += mItemPadding;
+ x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
+ }
+ break;
}
- break;
}
int menuLeft = r - l - getPaddingRight();
@@ -861,13 +894,20 @@
menuLeft -= mIndeterminateProgressView.getMeasuredWidth();
}
- if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
- LayoutParams lp = mCustomNavView.getLayoutParams();
+ View customView = null;
+ if (mExpandedActionView != null) {
+ customView = mExpandedActionView;
+ } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
+ mCustomNavView != null) {
+ customView = mCustomNavView;
+ }
+ if (customView != null) {
+ LayoutParams lp = customView.getLayoutParams();
final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
(ActionBar.LayoutParams) lp : null;
final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
- final int navWidth = mCustomNavView.getMeasuredWidth();
+ final int navWidth = customView.getMeasuredWidth();
int topMargin = 0;
int bottomMargin = 0;
@@ -907,17 +947,17 @@
case Gravity.CENTER_VERTICAL:
final int paddedTop = mTop + getPaddingTop();
final int paddedBottom = mBottom - getPaddingBottom();
- ypos = ((paddedBottom - paddedTop) - mCustomNavView.getMeasuredHeight()) / 2;
+ ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2;
break;
case Gravity.TOP:
ypos = getPaddingTop() + topMargin;
break;
case Gravity.BOTTOM:
- ypos = getHeight() - getPaddingBottom() - mCustomNavView.getMeasuredHeight()
+ ypos = getHeight() - getPaddingBottom() - customView.getMeasuredHeight()
- bottomMargin;
break;
}
- x += positionChild(mCustomNavView, xpos, ypos, contentHeight);
+ x += positionChild(customView, xpos, ypos, contentHeight);
}
if (mProgressView != null) {
@@ -928,9 +968,83 @@
}
}
+ @Override
+ public LayoutParams generateLayoutParams(LayoutParams lp) {
+ if (lp == null) {
+ lp = generateDefaultLayoutParams();
+ }
+ return lp;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState state = new SavedState(superState);
+
+ if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) {
+ state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId();
+ }
+
+ state.isOverflowOpen = isOverflowMenuShowing();
+
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable p) {
+ SavedState state = (SavedState) p;
+
+ super.onRestoreInstanceState(state.getSuperState());
+
+ if (state.expandedMenuItemId != 0 &&
+ mExpandedMenuPresenter != null && mOptionsMenu != null) {
+ final MenuItem item = mOptionsMenu.findItem(state.expandedMenuItemId);
+ if (item != null) {
+ item.expandActionView();
+ }
+ }
+
+ if (state.isOverflowOpen) {
+ postShowOverflowMenu();
+ }
+ }
+
+ static class SavedState extends BaseSavedState {
+ int expandedMenuItemId;
+ boolean isOverflowOpen;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ expandedMenuItemId = in.readInt();
+ isOverflowOpen = in.readInt() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(expandedMenuItemId);
+ out.writeInt(isOverflowOpen ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
private static class HomeView extends FrameLayout {
private View mUpView;
- private View mIconView;
+ private ImageView mIconView;
public HomeView(Context context) {
this(context, null);
@@ -940,12 +1054,24 @@
super(context, attrs);
}
+ public void setUp(boolean isUp) {
+ mUpView.setVisibility(isUp ? VISIBLE : GONE);
+ }
+
+ public void setIcon(Drawable icon) {
+ mIconView.setImageDrawable(icon);
+ }
+
@Override
protected void onFinishInflate() {
mUpView = findViewById(com.android.internal.R.id.up);
mIconView = (ImageView) findViewById(com.android.internal.R.id.home);
}
+ public int getVerticalIconPadding() {
+ return mIconView.getPaddingTop() + mIconView.getPaddingBottom();
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0);
@@ -983,4 +1109,111 @@
mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
}
}
+
+ private class ExpandedActionViewMenuPresenter implements MenuPresenter {
+ MenuBuilder mMenu;
+ MenuItemImpl mCurrentExpandedItem;
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ // Clear the expanded action view when menus change.
+ mExpandedActionView = null;
+ if (mCurrentExpandedItem != null) {
+ mCurrentExpandedItem.collapseActionView();
+ }
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ return null;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ // Make sure the expanded item we have is still there.
+ if (mCurrentExpandedItem != null) {
+ boolean found = false;
+ final int count = mMenu.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItem item = mMenu.getItem(i);
+ if (item == mCurrentExpandedItem) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // The item we had expanded disappeared. Collapse.
+ collapseItemActionView(mMenu, mCurrentExpandedItem);
+ }
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ mExpandedActionView = item.getActionView();
+ mExpandedHomeLayout.setIcon(item.getIcon());
+ mCurrentExpandedItem = item;
+ if (mExpandedActionView.getParent() != ActionBarView.this) {
+ addView(mExpandedActionView);
+ }
+ if (mExpandedHomeLayout.getParent() != ActionBarView.this) {
+ addView(mExpandedHomeLayout);
+ }
+ mHomeLayout.setVisibility(GONE);
+ mTitleLayout.setVisibility(GONE);
+ if (mTabScrollView != null) mTabScrollView.setVisibility(GONE);
+ if (mSpinner != null) mSpinner.setVisibility(GONE);
+ if (mCustomNavView != null) mCustomNavView.setVisibility(GONE);
+ requestLayout();
+ item.setActionViewExpanded(true);
+ return true;
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ removeView(mExpandedActionView);
+ removeView(mExpandedHomeLayout);
+ if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
+ mHomeLayout.setVisibility(VISIBLE);
+ }
+ if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+ mTitleLayout.setVisibility(VISIBLE);
+ }
+ if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
+ mTabScrollView.setVisibility(VISIBLE);
+ }
+ if (mSpinner != null && mNavigationMode == ActionBar.NAVIGATION_MODE_LIST) {
+ mSpinner.setVisibility(VISIBLE);
+ }
+ if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
+ mCustomNavView.setVisibility(VISIBLE);
+ }
+ mExpandedActionView = null;
+ mExpandedHomeLayout.setIcon(null);
+ mCurrentExpandedItem = null;
+ requestLayout();
+ item.setActionViewExpanded(false);
+ return true;
+ }
+ }
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index db76211..b3520da 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4482,6 +4482,10 @@
<!-- When this item is shown as an action in the action bar, show a text
label with it even if it has an icon representation. -->
<flag name="withText" value="4" />
+ <!-- This item's action view collapses to a normal menu
+ item. When expanded, the action view takes over a
+ larger segment of its container. -->
+ <flag name="collapseActionView" value="8" />
</attr>
<!-- An optional layout to be used as an action view.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b8a4443..31924bc 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1852,7 +1852,7 @@
<!-- Do not translate. WebView User Agent string -->
<string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
- AppleWebKit/534.20 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.20</string>
+ AppleWebKit/534.24 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.24</string>
<!-- Do not translate. WebView User Agent targeted content -->
<string name="web_user_agent_target_content" translatable="false">"Mobile "</string>
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
new file mode 100644
index 0000000..eb63c0d
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.net;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkStatsHistory.UID_ALL;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.util.Random;
+
+@SmallTest
+public class NetworkStatsHistoryTest extends TestCase {
+ private static final String TAG = "NetworkStatsHistoryTest";
+
+ private static final long TEST_START = 1194220800000L;
+
+ private NetworkStatsHistory stats;
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ if (stats != null) {
+ assertConsistent(stats);
+ }
+ }
+
+ public void testRecordSingleBucket() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // record data into narrow window to get single bucket
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 1024L, 2048L);
+
+ assertEquals(1, stats.bucketCount);
+ assertBucket(stats, 0, 1024L, 2048L);
+ }
+
+ public void testRecordEqualBuckets() throws Exception {
+ final long bucketDuration = HOUR_IN_MILLIS;
+ stats = buildStats(bucketDuration);
+
+ // split equally across two buckets
+ final long recordStart = TEST_START + (bucketDuration / 2);
+ stats.recordData(recordStart, recordStart + bucketDuration, 1024L, 128L);
+
+ assertEquals(2, stats.bucketCount);
+ assertBucket(stats, 0, 512L, 64L);
+ assertBucket(stats, 1, 512L, 64L);
+ }
+
+ public void testRecordTouchingBuckets() throws Exception {
+ final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // split almost completely into middle bucket, but with a few minutes
+ // overlap into neighboring buckets. total record is 20 minutes.
+ final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS;
+ final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4);
+ stats.recordData(recordStart, recordEnd, 1000L, 5000L);
+
+ assertEquals(3, stats.bucketCount);
+ // first bucket should have (1/20 of value)
+ assertBucket(stats, 0, 50L, 250L);
+ // second bucket should have (15/20 of value)
+ assertBucket(stats, 1, 750L, 3750L);
+ // final bucket should have (4/20 of value)
+ assertBucket(stats, 2, 200L, 1000L);
+ }
+
+ public void testRecordGapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // record some data today and next week with large gap
+ final long firstStart = TEST_START;
+ final long lastStart = TEST_START + WEEK_IN_MILLIS;
+ stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS, 128L, 256L);
+ stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS, 64L, 512L);
+
+ // we should have two buckets, far apart from each other
+ assertEquals(2, stats.bucketCount);
+ assertBucket(stats, 0, 128L, 256L);
+ assertBucket(stats, 1, 64L, 512L);
+
+ // now record something in middle, spread across two buckets
+ final long middleStart = TEST_START + DAY_IN_MILLIS;
+ final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2);
+ stats.recordData(middleStart, middleEnd, 2048L, 2048L);
+
+ // now should have four buckets, with new record in middle two buckets
+ assertEquals(4, stats.bucketCount);
+ assertBucket(stats, 0, 128L, 256L);
+ assertBucket(stats, 1, 1024L, 1024L);
+ assertBucket(stats, 2, 1024L, 1024L);
+ assertBucket(stats, 3, 64L, 512L);
+ }
+
+ public void testRecordOverlapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // record some data in one bucket, and another overlapping buckets
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 256L, 256L);
+ final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
+ stats.recordData(midStart, midStart + HOUR_IN_MILLIS, 1024L, 1024L);
+
+ // should have two buckets, with some data mixed together
+ assertEquals(2, stats.bucketCount);
+ assertBucket(stats, 0, 768L, 768L);
+ assertBucket(stats, 1, 512L, 512L);
+ }
+
+ public void testRemove() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // record some data across 24 buckets
+ stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
+ assertEquals(24, stats.bucketCount);
+
+ // try removing far before buckets; should be no change
+ stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+ assertEquals(24, stats.bucketCount);
+
+ // try removing just moments into first bucket; should be no change
+ // since that bucket contains data beyond the cutoff
+ stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+ assertEquals(24, stats.bucketCount);
+
+ // try removing single bucket
+ stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+ assertEquals(23, stats.bucketCount);
+
+ // try removing multiple buckets
+ stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+ assertEquals(20, stats.bucketCount);
+
+ // try removing all buckets
+ stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+ assertEquals(0, stats.bucketCount);
+ }
+
+ @Suppress
+ public void testFuzzing() throws Exception {
+ try {
+ // fuzzing with random events, looking for crashes
+ final Random r = new Random();
+ for (int i = 0; i < 500; i++) {
+ stats = buildStats(r.nextLong());
+ for (int j = 0; j < 10000; j++) {
+ if (r.nextBoolean()) {
+ // add range
+ final long start = r.nextLong();
+ final long end = start + r.nextInt();
+ stats.recordData(start, end, r.nextLong(), r.nextLong());
+ } else {
+ // trim something
+ stats.removeBucketsBefore(r.nextLong());
+ }
+ }
+ assertConsistent(stats);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, String.valueOf(stats));
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static NetworkStatsHistory buildStats(long bucketSize) {
+ return new NetworkStatsHistory(TYPE_MOBILE, null, UID_ALL, bucketSize);
+ }
+
+ private static void assertConsistent(NetworkStatsHistory stats) {
+ // verify timestamps are monotonic
+ for (int i = 1; i < stats.bucketCount; i++) {
+ assertTrue(stats.bucketStart[i - 1] < stats.bucketStart[i]);
+ }
+ }
+
+ private static void assertBucket(NetworkStatsHistory stats, int index, long rx, long tx) {
+ assertEquals("unexpected rx", rx, stats.rx[index]);
+ assertEquals("unexpected tx", tx, stats.tx[index]);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 45719c2..23eb9cf 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -47,8 +47,9 @@
.addEntry(TEST_IFACE, 100, 1024, 0)
.addEntry(TEST_IFACE, 101, 0, 1024).build();
- final NetworkStats result = after.subtract(before);
+ final NetworkStats result = after.subtract(before, true);
+ // identical data should result in zero delta
assertEquals(0, result.rx[0]);
assertEquals(0, result.tx[0]);
assertEquals(0, result.rx[1]);
@@ -64,7 +65,7 @@
.addEntry(TEST_IFACE, 100, 1025, 2)
.addEntry(TEST_IFACE, 101, 3, 1028).build();
- final NetworkStats result = after.subtract(before);
+ final NetworkStats result = after.subtract(before, true);
// expect delta between measurements
assertEquals(1, result.rx[0]);
@@ -83,7 +84,7 @@
.addEntry(TEST_IFACE, 101, 0, 1024)
.addEntry(TEST_IFACE, 102, 1024, 1024).build();
- final NetworkStats result = after.subtract(before);
+ final NetworkStats result = after.subtract(before, true);
// its okay to have new rows
assertEquals(0, result.rx[0]);
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 072329d..20abd51 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -26,7 +26,6 @@
// ----------------------------------------------------------------------------
-#define THREAD_PRIORITY_AUDIO_CLIENT (ANDROID_PRIORITY_AUDIO)
// Maximum cumulated timeout milliseconds before restarting audioflinger thread
#define MAX_STARTUP_TIMEOUT_MS 3000 // Longer timeout period at startup to cope with A2DP init time
#define MAX_RUN_TIMEOUT_MS 1000
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index f6c4cc7..a41d7ab 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -315,9 +315,9 @@
cblk->bufferTimeoutMs = MAX_RUN_TIMEOUT_MS;
cblk->waitTimeMs = 0;
if (t != 0) {
- t->run("ClientRecordThread", THREAD_PRIORITY_AUDIO_CLIENT);
+ t->run("ClientRecordThread", ANDROID_PRIORITY_AUDIO);
} else {
- setpriority(PRIO_PROCESS, 0, THREAD_PRIORITY_AUDIO_CLIENT);
+ setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_AUDIO);
}
} else {
mActive = 0;
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index ea44f87..6b4391b 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -342,9 +342,9 @@
cblk->waitTimeMs = 0;
android_atomic_and(~CBLK_DISABLED_ON, &cblk->flags);
if (t != 0) {
- t->run("AudioTrackThread", THREAD_PRIORITY_AUDIO_CLIENT);
+ t->run("AudioTrackThread", ANDROID_PRIORITY_AUDIO);
} else {
- setpriority(PRIO_PROCESS, 0, THREAD_PRIORITY_AUDIO_CLIENT);
+ setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_AUDIO);
}
LOGV("start %p before lock cblk %p", this, mCblk);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
index 4c66a2d..7eb6d22 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
@@ -112,12 +112,11 @@
private final int NUM_OF_ITERATIONS=20;
- private float calculateTimeTaken(long beginTime, int numIterations)
+ private int calculateTimeTaken(long beginTime, int numIterations)
throws Exception {
final long duration2 = SystemClock.uptimeMillis();
final long durationToCreateMediaItem = (duration2 - beginTime);
- final float timeTaken1 = (float)durationToCreateMediaItem *
- 1.0f/(float)numIterations;
+ final int timeTaken1 = (int)(durationToCreateMediaItem / numIterations);
return (timeTaken1);
}
@@ -208,7 +207,7 @@
final String[] loggingInfo = new String[3];
final MediaVideoItem[] mediaVideoItem =
new MediaVideoItem[NUM_OF_ITERATIONS];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
long startTime = 0;
/** Time Take for creation of Media Video Item */
@@ -251,7 +250,7 @@
final String[] loggingInfo = new String[3];
final MediaImageItem[] mediaImageItem =
new MediaImageItem[NUM_OF_ITERATIONS];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
long beginTime = SystemClock.uptimeMillis();
createImageItems(mediaImageItem, imageItemFileName, renderingMode,
@@ -296,7 +295,7 @@
final int transitionDuration = 5000;
final int transitionBehavior = Transition.BEHAVIOR_MIDDLE_FAST;
final String[] loggingInfo = new String[3];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final MediaVideoItem[] mediaVideoItem =
new MediaVideoItem[(NUM_OF_ITERATIONS *10) + 1];
@@ -514,7 +513,7 @@
/** 18.Enable Looping for Audio Track.
* */
audioTrack.enableLoop();
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final long beginTime = SystemClock.uptimeMillis();
try {
mVideoEditor.export(outFilename, outHeight, outBitrate,
@@ -557,7 +556,7 @@
mediaVideoItem.setExtractBoundaries(videoItemStartTime,
videoItemEndTime);
- float timeTaken = 0.0f;
+ int timeTaken = 0;
long beginTime = SystemClock.uptimeMillis();
for (int i = 0; i < NUM_OF_ITERATIONS; i++) {
mediaVideoItem.getThumbnail(mediaVideoItem.getWidth() / 2,
@@ -603,7 +602,7 @@
final OverlayFrame overlayFrame[] = new OverlayFrame[NUM_OF_ITERATIONS];
final Bitmap mBitmap = mVideoEditorHelper.getBitmap(overlayFilename,
640, 480);
- float timeTaken = 0.0f;
+ int timeTaken = 0;
long beginTime = SystemClock.uptimeMillis();
for (int i = 0; i < NUM_OF_ITERATIONS; i++) {
overlayFrame[i] = new OverlayFrame(mediaVideoItem, "overlay" + i,
@@ -647,7 +646,7 @@
final int videoProfile = MediaProperties.H264_PROFILE_0_LEVEL_1_3;
final int width = 1080;
final int height = MediaProperties.HEIGHT_720;
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final String[] loggingInfo = new String[1];
final MediaVideoItem mediaVideoItem = new MediaVideoItem(mVideoEditor,
"m0", videoItemFileName1, renderingMode);
@@ -1006,7 +1005,7 @@
final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
final int audioVolume = 50;
final String[] loggingInfo = new String[2];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final MediaVideoItem mediaVideoItem = new MediaVideoItem(mVideoEditor,
"mediaItem1", videoItemFileName1, renderingMode);
@@ -1057,7 +1056,7 @@
final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
final String[] loggingInfo = new String[3];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final MediaImageItem[] mediaImageItem =
new MediaImageItem[NUM_OF_ITERATIONS];
diff --git a/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml b/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
index 109cfff..a4564e6 100644
--- a/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
+++ b/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
@@ -37,6 +37,7 @@
android:layout_marginBottom="30dp" />
<Button android:id="@+id/button_allow"
+ android:filterTouchesWhenObscured="true"
android:text="@string/allow_backup_button_label"
android:layout_below="@id/package_name"
android:layout_height="wrap_content"
diff --git a/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml b/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
index a1f9a4a..ca99ae1 100644
--- a/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
+++ b/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
@@ -37,6 +37,7 @@
android:layout_marginBottom="30dp" />
<Button android:id="@+id/button_allow"
+ android:filterTouchesWhenObscured="true"
android:text="@string/allow_restore_button_label"
android:layout_below="@id/package_name"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
index e9db998..339e3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
@@ -422,9 +422,8 @@
Log.d(TAG, "Get text from: " + imi.getPackageName() + subtype.getNameResId()
+ imi.getServiceInfo().applicationInfo);
}
- // TODO: Change the language of subtype name according to subtype's locale.
- return mPackageManager.getText(
- imi.getPackageName(), subtype.getNameResId(), imi.getServiceInfo().applicationInfo);
+ return subtype.getDisplayName(
+ mContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo);
}
private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index d997109..75f466a 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1446,7 +1446,9 @@
}
if (mActionBar != null) {
- outState.putBoolean(ACTION_BAR_TAG, mActionBar.isOverflowMenuShowing());
+ SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
+ mActionBar.saveHierarchyState(actionBarStates);
+ outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
}
return outState;
@@ -1484,8 +1486,10 @@
restorePanelState(panelStates);
}
- if (mActionBar != null && savedInstanceState.getBoolean(ACTION_BAR_TAG)) {
- mActionBar.postShowOverflowMenu();
+ if (mActionBar != null) {
+ SparseArray<Parcelable> actionBarStates =
+ savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
+ mActionBar.restoreHierarchyState(actionBarStates);
}
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index a564c2d..dd76eb8 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,6 +16,11 @@
package com.android.server;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,11 +31,13 @@
import android.net.DummyDataStateTracker;
import android.net.EthernetDataTracker;
import android.net.IConnectivityManager;
-import android.net.LinkAddress;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
import android.net.LinkProperties;
import android.net.MobileDataStateTracker;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
import android.net.NetworkStateTracker;
import android.net.NetworkUtils;
import android.net.Proxy;
@@ -54,6 +61,7 @@
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
+import android.util.SparseIntArray;
import com.android.internal.telephony.Phone;
import com.android.server.connectivity.Tethering;
@@ -62,13 +70,12 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
-import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.GregorianCalendar;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @hide
@@ -78,6 +85,8 @@
private static final boolean DBG = true;
private static final String TAG = "ConnectivityService";
+ private static final boolean LOGD_RULES = false;
+
// how long to wait before switching back to a radio's default network
private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
// system property that can override the above value
@@ -91,6 +100,9 @@
private Tethering mTethering;
private boolean mTetheringConfigValid = false;
+ /** Currently active network rules by UID. */
+ private SparseIntArray mUidRules = new SparseIntArray();
+
/**
* Sometimes we want to refer to the individual network state
* trackers separately, and sometimes we just want to treat them
@@ -128,6 +140,7 @@
private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true);
private INetworkManagementService mNetd;
+ private INetworkPolicyManager mPolicyManager;
private static final int ENABLED = 1;
private static final int DISABLED = 0;
@@ -250,14 +263,8 @@
}
RadioAttributes[] mRadioAttributes;
- public static synchronized ConnectivityService getInstance(Context context) {
- if (sServiceInstance == null) {
- sServiceInstance = new ConnectivityService(context);
- }
- return sServiceInstance;
- }
-
- private ConnectivityService(Context context) {
+ public ConnectivityService(
+ Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) {
if (DBG) log("ConnectivityService starting up");
HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
@@ -290,9 +297,19 @@
loge("Error setting defaultDns using " + dns);
}
- mContext = context;
+ mContext = checkNotNull(context, "missing Context");
+ mNetd = checkNotNull(netd, "missing INetworkManagementService");
+ mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
- PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ try {
+ mPolicyManager.registerListener(mPolicyListener);
+ } catch (RemoteException e) {
+ // ouch, no rules updates means some processes may never get network
+ Slog.e(TAG, "unable to register INetworkPolicyListener", e);
+ }
+
+ final PowerManager powerManager = (PowerManager) context.getSystemService(
+ Context.POWER_SERVICE);
mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_networkTransitionTimeout);
@@ -536,32 +553,92 @@
}
/**
+ * Check if UID is blocked from using the given {@link NetworkInfo}.
+ */
+ private boolean isNetworkBlocked(NetworkInfo info, int uid) {
+ synchronized (mUidRules) {
+ return isNetworkBlockedLocked(info, uid);
+ }
+ }
+
+ /**
+ * Check if UID is blocked from using the given {@link NetworkInfo}.
+ */
+ private boolean isNetworkBlockedLocked(NetworkInfo info, int uid) {
+ // TODO: expand definition of "paid" network to cover tethered or paid
+ // hotspot use cases.
+ final boolean networkIsPaid = info.getType() != ConnectivityManager.TYPE_WIFI;
+ final int uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+
+ if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) {
+ return true;
+ }
+
+ // no restrictive rules; network is visible
+ return false;
+ }
+
+ /**
* Return NetworkInfo for the active (i.e., connected) network interface.
* It is assumed that at most one network is active at a time. If more
* than one is active, it is indeterminate which will be returned.
* @return the info for the active network, or {@code null} if none is
* active
*/
+ @Override
public NetworkInfo getActiveNetworkInfo() {
- return getNetworkInfo(mActiveDefaultNetwork);
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
}
+ @Override
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ enforceConnectivityInternalPermission();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+
+ @Override
public NetworkInfo getNetworkInfo(int networkType) {
enforceAccessPermission();
- if (ConnectivityManager.isNetworkTypeValid(networkType)) {
- NetworkStateTracker t = mNetTrackers[networkType];
- if (t != null)
- return t.getNetworkInfo();
- }
- return null;
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(networkType, uid);
}
+ private NetworkInfo getNetworkInfo(int networkType, int uid) {
+ NetworkInfo info = null;
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ info = tracker.getNetworkInfo();
+ if (isNetworkBlocked(info, uid)) {
+ // network is blocked; clone and override state
+ info = new NetworkInfo(info);
+ info.setDetailedState(DetailedState.BLOCKED, null, null);
+ }
+ }
+ }
+ return info;
+ }
+
+ @Override
public NetworkInfo[] getAllNetworkInfo() {
enforceAccessPermission();
- NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
+ final int uid = Binder.getCallingUid();
+ final NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
int i = 0;
- for (NetworkStateTracker t : mNetTrackers) {
- if(t != null) result[i++] = t.getNetworkInfo();
+ synchronized (mUidRules) {
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ NetworkInfo info = tracker.getNetworkInfo();
+ if (isNetworkBlockedLocked(info, uid)) {
+ // network is blocked; clone and override state
+ info = new NetworkInfo(info);
+ info.setDetailedState(DetailedState.BLOCKED, null, null);
+ }
+ result[i++] = info;
+ }
+ }
}
return result;
}
@@ -574,15 +651,19 @@
* @return the ip properties for the active network, or {@code null} if
* none is active
*/
+ @Override
public LinkProperties getActiveLinkProperties() {
return getLinkProperties(mActiveDefaultNetwork);
}
+ @Override
public LinkProperties getLinkProperties(int networkType) {
enforceAccessPermission();
- if (ConnectivityManager.isNetworkTypeValid(networkType)) {
- NetworkStateTracker t = mNetTrackers[networkType];
- if (t != null) return t.getLinkProperties();
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ return tracker.getLinkProperties();
+ }
}
return null;
}
@@ -1027,6 +1108,30 @@
}
}
+ private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ @Override
+ public void onRulesChanged(int uid, int uidRules) {
+ // only someone like NPMS should only be calling us
+ // TODO: create permission for modifying data policy
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+
+ if (LOGD_RULES) {
+ Slog.d(TAG, "onRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+ }
+
+ synchronized (mUidRules) {
+ // skip update when we've already applied rules
+ final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ if (oldRules == uidRules) return;
+
+ mUidRules.put(uid, uidRules);
+ }
+
+ // TODO: dispatch into NMS to push rules towards kernel module
+ // TODO: notify UID when it has requested targeted updates
+ }
+ };
+
/**
* @see ConnectivityManager#setMobileDataEnabled(boolean)
*/
@@ -1284,9 +1389,6 @@
}
void systemReady() {
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- mNetd = INetworkManagementService.Stub.asInterface(b);
-
synchronized(this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
@@ -2255,4 +2357,11 @@
}
return networkType;
}
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
}
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index c96273b..f4308cd 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1885,8 +1885,8 @@
int nameResId = subtype.getNameResId();
String mode = subtype.getMode();
if (nameResId != 0) {
- title = TextUtils.concat(pm.getText(imi.getPackageName(),
- nameResId, imi.getServiceInfo().applicationInfo),
+ title = TextUtils.concat(subtype.getDisplayName(context,
+ imi.getPackageName(), imi.getServiceInfo().applicationInfo),
(TextUtils.isEmpty(label) ? "" : " (" + label + ")"));
} else {
CharSequence language = subtype.getLocale();
diff --git a/services/java/com/android/server/NetStatService.java b/services/java/com/android/server/NetStatService.java
deleted file mode 100644
index 7fe6743..0000000
--- a/services/java/com/android/server/NetStatService.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.content.Context;
-import android.net.TrafficStats;
-import android.os.INetStatService;
-import android.os.SystemClock;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-public class NetStatService extends INetStatService.Stub {
- private final Context mContext;
-
- public NetStatService(Context context) {
- mContext = context;
- }
-
- public long getMobileTxPackets() {
- return TrafficStats.getMobileTxPackets();
- }
-
- public long getMobileRxPackets() {
- return TrafficStats.getMobileRxPackets();
- }
-
- public long getMobileTxBytes() {
- return TrafficStats.getMobileTxBytes();
- }
-
- public long getMobileRxBytes() {
- return TrafficStats.getMobileRxBytes();
- }
-
- public long getTotalTxPackets() {
- return TrafficStats.getTotalTxPackets();
- }
-
- public long getTotalRxPackets() {
- return TrafficStats.getTotalRxPackets();
- }
-
- public long getTotalTxBytes() {
- return TrafficStats.getTotalTxBytes();
- }
-
- public long getTotalRxBytes() {
- return TrafficStats.getTotalRxBytes();
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- // This data is accessible to any app -- no permission check needed.
-
- pw.print("Elapsed: total=");
- pw.print(SystemClock.elapsedRealtime());
- pw.print("ms awake=");
- pw.print(SystemClock.uptimeMillis());
- pw.println("ms");
-
- pw.print("Mobile: Tx=");
- pw.print(getMobileTxBytes());
- pw.print("B/");
- pw.print(getMobileTxPackets());
- pw.print("Pkts Rx=");
- pw.print(getMobileRxBytes());
- pw.print("B/");
- pw.print(getMobileRxPackets());
- pw.println("Pkts");
-
- pw.print("Total: Tx=");
- pw.print(getTotalTxBytes());
- pw.print("B/");
- pw.print(getTotalTxPackets());
- pw.print("Pkts Rx=");
- pw.print(getTotalRxBytes());
- pw.print("B/");
- pw.print(getTotalRxPackets());
- pw.println("Pkts");
- }
-}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5355d44..596cbac 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,19 +16,6 @@
package com.android.server;
-import com.android.server.accessibility.AccessibilityManagerService;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.net.NetworkPolicyManagerService;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.usb.UsbService;
-import com.android.server.wm.WindowManagerService;
-import com.android.internal.app.ShutdownThread;
-import com.android.internal.os.BinderInternal;
-import com.android.internal.os.SamplingProfilerIntegration;
-
-import dalvik.system.VMRuntime;
-import dalvik.system.Zygote;
-
import android.accounts.AccountManagerService;
import android.app.ActivityManagerNative;
import android.bluetooth.BluetoothAdapter;
@@ -41,25 +28,35 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.media.AudioService;
-import android.os.Build;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.provider.Contacts.People;
import android.provider.Settings;
import android.server.BluetoothA2dpService;
import android.server.BluetoothService;
import android.server.search.SearchManagerService;
import android.util.DisplayMetrics;
import android.util.EventLog;
-import android.util.Log;
import android.util.Slog;
-import android.view.Display;
import android.view.WindowManager;
+import com.android.internal.app.ShutdownThread;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.SamplingProfilerIntegration;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.net.NetworkPolicyManagerService;
+import com.android.server.net.NetworkStatsService;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.usb.UsbService;
+import com.android.server.wm.WindowManagerService;
+
+import dalvik.system.VMRuntime;
+import dalvik.system.Zygote;
+
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
@@ -120,6 +117,9 @@
LightsService lights = null;
PowerManagerService power = null;
BatteryService battery = null;
+ AlarmManagerService alarm = null;
+ NetworkManagementService networkManagement = null;
+ NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
IPackageManager pm = null;
@@ -191,7 +191,7 @@
power.init(context, lights, ActivityManagerService.getDefault(), battery);
Slog.i(TAG, "Alarm Manager");
- AlarmManagerService alarm = new AlarmManagerService(context);
+ alarm = new AlarmManagerService(context);
ServiceManager.addService(Context.ALARM_SERVICE, alarm);
Slog.i(TAG, "Init Watchdog");
@@ -277,33 +277,33 @@
}
try {
- Slog.i(TAG, "NetStat Service");
- ServiceManager.addService("netstat", new NetStatService(context));
- } catch (Throwable e) {
- Slog.e(TAG, "Failure starting NetStat Service", e);
- }
-
- try {
- Slog.i(TAG, "NetworkPolicy Service");
- networkPolicy = new NetworkPolicyManagerService(
- context, ActivityManagerService.self(), power);
- ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
- } catch (Throwable e) {
- Slog.e(TAG, "Failure starting Connectivity Service", e);
- }
-
- try {
Slog.i(TAG, "NetworkManagement Service");
- ServiceManager.addService(
- Context.NETWORKMANAGEMENT_SERVICE,
- NetworkManagementService.create(context));
+ networkManagement = NetworkManagementService.create(context);
+ ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting NetworkManagement Service", e);
}
try {
+ Slog.i(TAG, "NetworkStats Service");
+ networkStats = new NetworkStatsService(context, networkManagement, alarm);
+ ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting NetworkStats Service", e);
+ }
+
+ try {
+ Slog.i(TAG, "NetworkPolicy Service");
+ networkPolicy = new NetworkPolicyManagerService(
+ context, ActivityManagerService.self(), power, networkStats);
+ ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting NetworkPolicy Service", e);
+ }
+
+ try {
Slog.i(TAG, "Connectivity Service");
- connectivity = ConnectivityService.getInstance(context);
+ connectivity = new ConnectivityService(context, networkManagement, networkPolicy);
ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Connectivity Service", e);
@@ -539,6 +539,7 @@
// These are needed to propagate to the runnable below.
final Context contextF = context;
final BatteryService batteryF = battery;
+ final NetworkStatsService networkStatsF = networkStats;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
final ConnectivityService connectivityF = connectivity;
final DockObserver dockF = dock;
@@ -565,6 +566,7 @@
startSystemUi(contextF);
if (batteryF != null) batteryF.systemReady();
+ if (networkStatsF != null) networkStatsF.systemReady();
if (networkPolicyF != null) networkPolicyF.systemReady();
if (connectivityF != null) connectivityF.systemReady();
if (dockF != null) dockF.systemReady();
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index cc9e532..7801aec 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -41,7 +41,6 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
-import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -59,7 +58,6 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -107,16 +105,18 @@
private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
- private final SparseArray<List<AccessibilityServiceInfo>> mFeedbackTypeToEnabledServicesMap =
- new SparseArray<List<AccessibilityServiceInfo>>();
-
private PackageManager mPackageManager;
private int mHandledFeedbackTypes = 0;
private boolean mIsEnabled;
+
private AccessibilityInputFilter mInputFilter;
+ private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>();
+
+ private boolean mHasInputFilter;
+
/**
* Handler for delayed event dispatch.
*/
@@ -259,6 +259,7 @@
unbindAllServicesLocked();
}
updateClientsLocked();
+ updateInputFilterLocked();
}
}
});
@@ -278,9 +279,19 @@
});
}
- public boolean addClient(IAccessibilityManagerClient client) {
+ public boolean addClient(IAccessibilityManagerClient client) throws RemoteException {
synchronized (mLock) {
- mClients.add(client);
+ final IAccessibilityManagerClient addedClient = client;
+ mClients.add(addedClient);
+ // Clients are registered all the time until their process is
+ // killed, therefore we do not have a corresponding unlinkToDeath.
+ client.asBinder().linkToDeath(new DeathRecipient() {
+ public void binderDied() {
+ synchronized (mLock) {
+ mClients.remove(addedClient);
+ }
+ }
+ }, 0);
return mIsEnabled;
}
}
@@ -307,21 +318,22 @@
}
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
- SparseArray<List<AccessibilityServiceInfo>> feedbackTypeToEnabledServicesMap =
- mFeedbackTypeToEnabledServicesMap;
- List<AccessibilityServiceInfo> enabledServices = new ArrayList<AccessibilityServiceInfo>();
+ List<AccessibilityServiceInfo> result = mEnabledServicesForFeedbackTempList;
+ List<Service> services = mServices;
synchronized (mLock) {
while (feedbackType != 0) {
final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType));
feedbackType &= ~feedbackTypeBit;
- List<AccessibilityServiceInfo> perFeedbackType =
- feedbackTypeToEnabledServicesMap.get(feedbackTypeBit);
- if (perFeedbackType != null) {
- enabledServices.addAll(perFeedbackType);
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ Service service = services.get(i);
+ if (service.mFeedbackType == feedbackType) {
+ result.add(service.mAccessibilityServiceInfo);
+ }
}
}
}
- return enabledServices;
+ return result;
}
public void interrupt() {
@@ -331,16 +343,8 @@
try {
service.mServiceInterface.onInterrupt();
} catch (RemoteException re) {
- if (re instanceof DeadObjectException) {
- Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
- if (removeDeadServiceLocked(service)) {
- count--;
- i--;
- }
- } else {
- Slog.e(LOG_TAG, "Error during sending interrupt request to "
- + service.mService, re);
- }
+ Slog.e(LOG_TAG, "Error during sending interrupt request to "
+ + service.mService, re);
}
}
}
@@ -364,8 +368,10 @@
service.setAccessibilityServiceInfo(oldInfo);
} else {
service.setAccessibilityServiceInfo(info);
+ tryAddServiceLocked(service);
}
- updateStateOnEnabledService(service);
+
+ updateInputFilterLocked();
}
return;
default:
@@ -490,28 +496,45 @@
Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
}
} catch (RemoteException re) {
- if (re instanceof DeadObjectException) {
- Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
- removeDeadServiceLocked(service);
- } else {
- Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
- }
+ Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
}
}
/**
- * Removes a dead service.
+ * Adds a service.
+ *
+ * @param service The service to add.
+ */
+ private void tryAddServiceLocked(Service service) {
+ try {
+ if (mServices.contains(service) || !service.isConfigured()) {
+ return;
+ }
+ service.linkToOwnDeath();
+ mServices.add(service);
+ mComponentNameToServiceMap.put(service.mComponentName, service);
+ updateInputFilterLocked();
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * Removes a service.
*
* @param service The service.
* @return True if the service was removed, false otherwise.
*/
- private boolean removeDeadServiceLocked(Service service) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Dead service " + service.mService + " removed");
+ private boolean tryRemoveServiceLocked(Service service) {
+ final boolean removed = mServices.remove(service);
+ if (!removed) {
+ return false;
}
+ mComponentNameToServiceMap.remove(service.mComponentName);
mHandler.removeMessages(service.mId);
- updateStateOnDisabledService(service);
- return mServices.remove(service);
+ service.unlinkToOwnDeath();
+ updateInputFilterLocked();
+ return removed;
}
/**
@@ -534,11 +557,6 @@
return false;
}
- if (!service.mService.isBinderAlive()) {
- removeDeadServiceLocked(service);
- return false;
- }
-
int eventType = event.getEventType();
if ((service.mEventTypes & eventType) != eventType) {
return false;
@@ -663,65 +681,36 @@
}
/**
- * Sets the input filter state. If the filter is in enabled it is registered
- * in the window manager, otherwise the filter is removed from the latter.
- *
- * @param enabled Whether the input filter is enabled.
+ * Updates the input filter state. The filter is enabled if accessibility
+ * is enabled and there is at least one accessibility service providing
+ * spoken feedback.
*/
- private void setInputFilterEnabledLocked(boolean enabled) {
+ private void updateInputFilterLocked() {
WindowManagerService wm = (WindowManagerService)ServiceManager.getService(
Context.WINDOW_SERVICE);
- if (wm != null) {
- if (enabled) {
+ if (mIsEnabled) {
+ final boolean hasSpokenFeedbackServices = !getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty();
+ if (hasSpokenFeedbackServices) {
+ if (mHasInputFilter) {
+ return;
+ }
if (mInputFilter == null) {
mInputFilter = new AccessibilityInputFilter(mContext);
}
wm.setInputFilter(mInputFilter);
+ mHasInputFilter = true;
} else {
- wm.setInputFilter(null);
+ if (mHasInputFilter) {
+ wm.setInputFilter(null);
+ mHasInputFilter = false;
+ }
}
- }
- }
-
- /**
- * Updates the set of enabled services for a given feedback type and
- * if more than one of them provides spoken feedback enables touch
- * exploration.
- *
- * @param service An enable service.
- */
- private void updateStateOnEnabledService(Service service) {
- int feedbackType = service.mFeedbackType;
- List<AccessibilityServiceInfo> enabledServices =
- mFeedbackTypeToEnabledServicesMap.get(feedbackType);
- if (enabledServices == null) {
- enabledServices = new ArrayList<AccessibilityServiceInfo>();
- mFeedbackTypeToEnabledServicesMap.put(feedbackType, enabledServices);
- }
- enabledServices.add(service.mAccessibilityServiceInfo);
-
- // We enable touch exploration if at least one
- // enabled service provides spoken feedback.
- if (enabledServices.size() > 0
- && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
- updateClientsLocked();
- setInputFilterEnabledLocked(true);
- }
- }
-
- private void updateStateOnDisabledService(Service service) {
- List<AccessibilityServiceInfo> enabledServices =
- mFeedbackTypeToEnabledServicesMap.get(service.mFeedbackType);
- if (enabledServices == null) {
- return;
- }
- enabledServices.remove(service.mAccessibilityServiceInfo);
- // We disable touch exploration if no
- // enabled service provides spoken feedback.
- if (enabledServices.isEmpty()
- && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
- updateClientsLocked();
- setInputFilterEnabledLocked(false);
+ } else {
+ if (mHasInputFilter) {
+ wm.setInputFilter(null);
+ mHasInputFilter = false;
+ }
}
}
@@ -733,7 +722,8 @@
* passed to the service it represents as soon it is bound. It also serves as the
* connection for the service.
*/
- class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection {
+ class Service extends IAccessibilityServiceConnection.Stub
+ implements ServiceConnection, DeathRecipient {
int mId = 0;
AccessibilityServiceInfo mAccessibilityServiceInfo;
@@ -752,8 +742,6 @@
long mNotificationTimeout;
- boolean mIsActive;
-
ComponentName mComponentName;
Intent mIntent;
@@ -806,11 +794,9 @@
*/
public boolean unbind() {
if (mService != null) {
- mService = null;
+ tryRemoveServiceLocked(this);
mContext.unbindService(this);
- mComponentNameToServiceMap.remove(mComponentName);
- mServices.remove(this);
- updateStateOnDisabledService(this);
+ mService = null;
return true;
}
return false;
@@ -833,17 +819,10 @@
public void onServiceConnected(ComponentName componentName, IBinder service) {
mService = service;
mServiceInterface = IEventListener.Stub.asInterface(service);
-
try {
mServiceInterface.setConnection(this);
synchronized (mLock) {
- if (!mServices.contains(this)) {
- mServices.add(this);
- mComponentNameToServiceMap.put(componentName, this);
- if (isConfigured()) {
- updateStateOnEnabledService(this);
- }
- }
+ tryAddServiceLocked(this);
}
} catch (RemoteException re) {
Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re);
@@ -851,9 +830,20 @@
}
public void onServiceDisconnected(ComponentName componentName) {
+ /* do nothing - #binderDied takes care */
+ }
+
+ public void linkToOwnDeath() throws RemoteException {
+ mService.linkToDeath(this, 0);
+ }
+
+ public void unlinkToOwnDeath() {
+ mService.unlinkToDeath(this, 0);
+ }
+
+ public void binderDied() {
synchronized (mLock) {
- Service service = mComponentNameToServiceMap.remove(componentName);
- mServices.remove(service);
+ tryRemoveServiceLocked(this);
}
}
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index b463e56..5f52056 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -25,7 +25,6 @@
import com.android.server.SystemServer;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.wm.WindowManagerService;
import dalvik.system.Zygote;
@@ -1301,15 +1300,16 @@
break;
}
case DISPATCH_FOREGROUND_ACTIVITIES_CHANGED: {
- final ProcessRecord app = (ProcessRecord) msg.obj;
- final boolean foregroundActivities = msg.arg1 != 0;
- dispatchForegroundActivitiesChanged(
- app.pid, app.info.uid, foregroundActivities);
+ final int pid = msg.arg1;
+ final int uid = msg.arg2;
+ final boolean foregroundActivities = (Boolean) msg.obj;
+ dispatchForegroundActivitiesChanged(pid, uid, foregroundActivities);
break;
}
case DISPATCH_PROCESS_DIED: {
- final ProcessRecord app = (ProcessRecord) msg.obj;
- dispatchProcessDied(app.pid, app.info.uid);
+ final int pid = msg.arg1;
+ final int uid = msg.arg2;
+ dispatchProcessDied(pid, uid);
break;
}
}
@@ -9260,7 +9260,7 @@
}
}
- mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app).sendToTarget();
+ mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
// If the caller is restarting this app, then leave it in its
// current lists and let the caller take care of it.
@@ -12821,8 +12821,8 @@
app.curSchedGroup = schedGroup;
if (hadForegroundActivities != app.foregroundActivities) {
- mHandler.obtainMessage(DISPATCH_FOREGROUND_ACTIVITIES_CHANGED,
- app.foregroundActivities ? 1 : 0, 0, app).sendToTarget();
+ mHandler.obtainMessage(DISPATCH_FOREGROUND_ACTIVITIES_CHANGED, app.pid, app.info.uid,
+ app.foregroundActivities).sendToTarget();
}
return adj;
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index d083d01..17c7161 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -16,11 +16,15 @@
package com.android.server.net;
+import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_BACKGROUND;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static android.net.NetworkPolicyManager.dumpPolicy;
+import static android.net.NetworkPolicyManager.dumpRules;
import android.app.IActivityManager;
import android.app.IProcessObserver;
@@ -28,51 +32,73 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
import android.os.IPowerManager;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* Service that maintains low-level network policy rules and collects usage
* statistics to drive those rules.
+ * <p>
+ * Derives active rules by combining a given policy with other system status,
+ * and delivers to listeners, such as {@link ConnectivityManager}, for
+ * enforcement.
*/
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String TAG = "NetworkPolicy";
private static final boolean LOGD = true;
- private Context mContext;
- private IActivityManager mActivityManager;
- private IPowerManager mPowerManager;
+ private final Context mContext;
+ private final IActivityManager mActivityManager;
+ private final IPowerManager mPowerManager;
+ private final INetworkStatsService mNetworkStats;
- private Object mRulesLock = new Object();
+ private final Object mRulesLock = new Object();
- private boolean mScreenOn = false;
+ private boolean mScreenOn;
/** Current network policy for each UID. */
private SparseIntArray mUidPolicy = new SparseIntArray();
+ /** Current derived network rules for each UID. */
+ private SparseIntArray mUidRules = new SparseIntArray();
/** Foreground at both UID and PID granularity. */
private SparseBooleanArray mUidForeground = new SparseBooleanArray();
private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
SparseBooleanArray>();
- // TODO: periodically poll network stats and write to disk
+ private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
+ INetworkPolicyListener>();
+
// TODO: save/restore policy information from disk
- public NetworkPolicyManagerService(
- Context context, IActivityManager activityManager, IPowerManager powerManager) {
+ // TODO: keep whitelist of system-critical services that should never have
+ // rules enforced, such as system, phone, and radio UIDs.
+
+ // TODO: keep record of billing cycle details, and limit rules
+ // TODO: keep map of interfaces-to-billing-relationship
+
+ public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
+ IPowerManager powerManager, INetworkStatsService networkStats) {
mContext = checkNotNull(context, "missing context");
mActivityManager = checkNotNull(activityManager, "missing activityManager");
mPowerManager = checkNotNull(powerManager, "missing powerManager");
+ mNetworkStats = checkNotNull(networkStats, "missing networkStats");
}
public void systemReady() {
- // TODO: read current policy+stats from disk and generate NMS rules
+ // TODO: read current policy from disk
updateScreenOn();
@@ -92,18 +118,13 @@
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenReceiver, screenFilter);
- final IntentFilter shutdownFilter = new IntentFilter();
- shutdownFilter.addAction(Intent.ACTION_SHUTDOWN);
- mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
-
}
private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
// only someone like AMS should only be calling us
- mContext.enforceCallingOrSelfPermission(
- MANAGE_APP_TOKENS, "requires MANAGE_APP_TOKENS permission");
+ mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
synchronized (mRulesLock) {
// because a uid can have multiple pids running inside, we need to
@@ -123,8 +144,7 @@
@Override
public void onProcessDied(int pid, int uid) {
// only someone like AMS should only be calling us
- mContext.enforceCallingOrSelfPermission(
- MANAGE_APP_TOKENS, "requires MANAGE_APP_TOKENS permission");
+ mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
synchronized (mRulesLock) {
// clear records and recompute, when they exist
@@ -148,22 +168,19 @@
}
};
- private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // TODO: persist any pending stats during clean shutdown
- Log.d(TAG, "persisting stats");
- }
- };
-
@Override
public void setUidPolicy(int uid, int policy) {
- mContext.enforceCallingOrSelfPermission(
- UPDATE_DEVICE_STATS, "requires UPDATE_DEVICE_STATS permission");
+ // TODO: create permission for modifying data policy
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+ final int oldPolicy;
synchronized (mRulesLock) {
+ oldPolicy = getUidPolicy(uid);
mUidPolicy.put(uid, policy);
+ updateRulesForUidL(uid);
}
+
+ // TODO: consider dispatching BACKGROUND_DATA_SETTING broadcast
}
@Override
@@ -173,6 +190,86 @@
}
}
+ @Override
+ public void registerListener(INetworkPolicyListener listener) {
+ mListeners.register(listener);
+
+ synchronized (mRulesLock) {
+ // dispatch any existing rules to new listeners
+ final int size = mUidRules.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = mUidRules.keyAt(i);
+ final int uidRules = mUidRules.valueAt(i);
+ if (uidRules != RULE_ALLOW_ALL) {
+ try {
+ listener.onRulesChanged(uid, uidRules);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void unregisterListener(INetworkPolicyListener listener) {
+ mListeners.unregister(listener);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+ synchronized (mRulesLock) {
+ fout.println("Policy status for known UIDs:");
+
+ final SparseBooleanArray knownUids = new SparseBooleanArray();
+ collectKeys(mUidPolicy, knownUids);
+ collectKeys(mUidForeground, knownUids);
+ collectKeys(mUidRules, knownUids);
+
+ final int size = knownUids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = knownUids.keyAt(i);
+ fout.print(" UID=");
+ fout.print(uid);
+
+ fout.print(" policy=");
+ final int policyIndex = mUidPolicy.indexOfKey(uid);
+ if (policyIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpPolicy(fout, mUidPolicy.valueAt(policyIndex));
+ }
+
+ fout.print(" foreground=");
+ final int foregroundIndex = mUidPidForeground.indexOfKey(uid);
+ if (foregroundIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpSparseBooleanArray(fout, mUidPidForeground.valueAt(foregroundIndex));
+ }
+
+ fout.print(" rules=");
+ final int rulesIndex = mUidRules.indexOfKey(uid);
+ if (rulesIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpRules(fout, mUidRules.valueAt(rulesIndex));
+ }
+
+ fout.println();
+ }
+ }
+ }
+
+ @Override
+ public boolean isUidForeground(int uid) {
+ synchronized (mRulesLock) {
+ // only really in foreground when screen is also on
+ return mUidForeground.get(uid, false) && mScreenOn;
+ }
+ }
+
/**
* Foreground for PID changed; recompute foreground at UID level. If
* changed, will trigger {@link #updateRulesForUidL(int)}.
@@ -223,22 +320,33 @@
}
private void updateRulesForUidL(int uid) {
- // only really in foreground when screen on
- final boolean uidForeground = mUidForeground.get(uid, false) && mScreenOn;
final int uidPolicy = getUidPolicy(uid);
+ final boolean uidForeground = isUidForeground(uid);
- if (LOGD) {
- Log.d(TAG, "updateRulesForUid(uid=" + uid + ") found foreground=" + uidForeground
- + " and policy=" + uidPolicy);
+ // derive active rules based on policy and active state
+ int uidRules = RULE_ALLOW_ALL;
+ if (!uidForeground && (uidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+ // uid in background, and policy says to block paid data
+ uidRules = RULE_REJECT_PAID;
}
- if (!uidForeground && (uidPolicy & POLICY_REJECT_BACKGROUND) != 0) {
- // TODO: build updated rules and push to NMS
- } else if ((uidPolicy & POLICY_REJECT_PAID) != 0) {
- // TODO: build updated rules and push to NMS
- } else {
- // TODO: build updated rules and push to NMS
+ // TODO: only dispatch when rules actually change
+
+ // record rule locally to dispatch to new listeners
+ mUidRules.put(uid, uidRules);
+
+ // dispatch changed rule to existing listeners
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ if (listener != null) {
+ try {
+ listener.onRulesChanged(uid, uidRules);
+ } catch (RemoteException e) {
+ }
+ }
}
+ mListeners.finishBroadcast();
}
private static <T> T checkNotNull(T value, String message) {
@@ -247,4 +355,28 @@
}
return value;
}
+
+ private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
+ final int size = source.size();
+ for (int i = 0; i < size; i++) {
+ target.put(source.keyAt(i), true);
+ }
+ }
+
+ private static void collectKeys(SparseBooleanArray source, SparseBooleanArray target) {
+ final int size = source.size();
+ for (int i = 0; i < size; i++) {
+ target.put(source.keyAt(i), true);
+ }
+ }
+
+ private static void dumpSparseBooleanArray(PrintWriter fout, SparseBooleanArray value) {
+ fout.print("[");
+ final int size = value.size();
+ for (int i = 0; i < size; i++) {
+ fout.print(value.keyAt(i) + "=" + value.valueAt(i));
+ if (i < size - 1) fout.print(",");
+ }
+ fout.print("]");
+ }
}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
new file mode 100644
index 0000000..d9c1f25
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.SHUTDOWN;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkStats.UID_ALL;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.INetworkStatsService;
+import android.net.LinkProperties;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.text.format.DateUtils;
+import android.util.NtpTrustedTime;
+import android.util.Slog;
+import android.util.TrustedTime;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyIntents;
+import com.google.android.collect.Maps;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+/**
+ * Collect and persist detailed network statistics, and provide this data to
+ * other system services.
+ */
+public class NetworkStatsService extends INetworkStatsService.Stub {
+ private static final String TAG = "NetworkStatsService";
+ private static final boolean LOGD = true;
+
+ private final Context mContext;
+ private final INetworkManagementService mNetworkManager;
+ private final IAlarmManager mAlarmManager;
+ private final TrustedTime mTime;
+
+ private static final String ACTION_NETWORK_STATS_POLL =
+ "com.android.server.action.NETWORK_STATS_POLL";
+
+ private PendingIntent mPollIntent;
+
+ // TODO: move tweakable params to Settings.Secure
+ // TODO: listen for kernel push events through netd instead of polling
+
+ private static final long KB_IN_BYTES = 1024;
+
+ private static final long POLL_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
+ private static final long SUMMARY_BUCKET_DURATION = 6 * DateUtils.HOUR_IN_MILLIS;
+ private static final long SUMMARY_MAX_HISTORY = 90 * DateUtils.DAY_IN_MILLIS;
+
+ // TODO: remove these high-frequency testing values
+// private static final long POLL_INTERVAL = 5 * DateUtils.SECOND_IN_MILLIS;
+// private static final long SUMMARY_BUCKET_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
+// private static final long SUMMARY_MAX_HISTORY = 2 * DateUtils.MINUTE_IN_MILLIS;
+
+ /** Minimum delta required to persist to disk. */
+ private static final long SUMMARY_PERSIST_THRESHOLD = 64 * KB_IN_BYTES;
+
+ private static final long TIME_CACHE_MAX_AGE = DateUtils.DAY_IN_MILLIS;
+
+ private final Object mStatsLock = new Object();
+
+ /** Set of active ifaces during this boot. */
+ private HashMap<String, InterfaceInfo> mActiveIface = Maps.newHashMap();
+ /** Set of historical stats for known ifaces. */
+ private HashMap<InterfaceInfo, NetworkStatsHistory> mIfaceStats = Maps.newHashMap();
+
+ private NetworkStats mLastPollStats;
+ private NetworkStats mLastPersistStats;
+
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+
+ // TODO: collect detailed uid stats, storing tag-granularity data until next
+ // dropbox, and uid summary for a specific bucket count.
+
+ // TODO: periodically compile statistics and send to dropbox.
+
+ public NetworkStatsService(
+ Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
+ // TODO: move to using cached NtpTrustedTime
+ this(context, networkManager, alarmManager, new NtpTrustedTime());
+ }
+
+ public NetworkStatsService(Context context, INetworkManagementService networkManager,
+ IAlarmManager alarmManager, TrustedTime time) {
+ mContext = checkNotNull(context, "missing Context");
+ mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
+ mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
+ mTime = checkNotNull(time, "missing TrustedTime");
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ public void systemReady() {
+ // read historical stats from disk
+ readStatsLocked();
+
+ // watch other system services that claim interfaces
+ // TODO: protect incoming broadcast with permissions check.
+ // TODO: consider migrating this to ConnectivityService, but it might
+ // cause a circular dependency.
+ final IntentFilter interfaceFilter = new IntentFilter();
+ interfaceFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ interfaceFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(mInterfaceReceiver, interfaceFilter);
+
+ // listen for periodic polling events
+ final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
+ mContext.registerReceiver(mPollReceiver, pollFilter, UPDATE_DEVICE_STATS, mHandler);
+
+ // persist stats during clean shutdown
+ final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+ mContext.registerReceiver(mShutdownReceiver, shutdownFilter, SHUTDOWN, null);
+
+ try {
+ registerPollAlarmLocked();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "unable to register poll alarm");
+ }
+ }
+
+ /**
+ * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
+ * reschedule based on current {@link #POLL_INTERVAL} value.
+ */
+ private void registerPollAlarmLocked() throws RemoteException {
+ if (mPollIntent != null) {
+ mAlarmManager.remove(mPollIntent);
+ }
+
+ mPollIntent = PendingIntent.getBroadcast(
+ mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
+
+ final long currentRealtime = SystemClock.elapsedRealtime();
+ mAlarmManager.setInexactRepeating(
+ AlarmManager.ELAPSED_REALTIME, currentRealtime, POLL_INTERVAL, mPollIntent);
+ }
+
+ @Override
+ public NetworkStatsHistory[] getNetworkStatsSummary(int networkType) {
+ // TODO: return history for requested types
+ return null;
+ }
+
+ @Override
+ public NetworkStatsHistory getNetworkStatsUid(int uid) {
+ // TODO: return history for requested uid
+ return null;
+ }
+
+ /**
+ * Receiver that watches for other system components that claim network
+ * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
+ * with mobile interfaces.
+ */
+ private BroadcastReceiver mInterfaceReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED.equals(action)) {
+ final LinkProperties prop = intent.getParcelableExtra(
+ Phone.DATA_LINK_PROPERTIES_KEY);
+ final String iface = prop != null ? prop.getInterfaceName() : null;
+ if (iface != null) {
+ final TelephonyManager teleManager = (TelephonyManager) context
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ final InterfaceInfo info = new InterfaceInfo(
+ iface, TYPE_MOBILE, teleManager.getSubscriberId());
+ reportActiveInterface(info);
+ }
+ } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ final LinkProperties prop = intent.getParcelableExtra(
+ WifiManager.EXTRA_LINK_PROPERTIES);
+ final String iface = prop != null ? prop.getInterfaceName() : null;
+ if (iface != null) {
+ final InterfaceInfo info = new InterfaceInfo(iface, TYPE_WIFI, null);
+ reportActiveInterface(info);
+ }
+ }
+ }
+ };
+
+ private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // already running on background handler, network/io is safe, and
+ // caller verified to have UPDATE_DEVICE_STATS permission above.
+ synchronized (mStatsLock) {
+ // TODO: acquire wakelock while performing poll
+ performPollLocked();
+ }
+ }
+ };
+
+ private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // persist stats during clean shutdown
+ synchronized (mStatsLock) {
+ writeStatsLocked();
+ }
+ }
+ };
+
+ private void performPollLocked() {
+ if (LOGD) Slog.v(TAG, "performPollLocked()");
+
+ // try refreshing time source when stale
+ if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+ mTime.forceRefresh();
+ }
+
+ // TODO: consider marking "untrusted" times in historical stats
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
+ final NetworkStats current;
+ try {
+ current = mNetworkManager.getNetworkStatsSummary();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem reading network stats");
+ return;
+ }
+
+ // update historical usage with delta since last poll
+ final NetworkStats pollDelta = computeStatsDelta(mLastPollStats, current);
+ final long timeStart = currentTime - pollDelta.elapsedRealtime;
+ for (String iface : pollDelta.getKnownIfaces()) {
+ final InterfaceInfo info = mActiveIface.get(iface);
+ if (info == null) {
+ if (LOGD) Slog.w(TAG, "unknown interface " + iface + ", ignoring stats");
+ continue;
+ }
+
+ final int index = pollDelta.findIndex(iface, UID_ALL);
+ final long rx = pollDelta.rx[index];
+ final long tx = pollDelta.tx[index];
+
+ final NetworkStatsHistory history = findOrCreateHistoryLocked(info);
+ history.recordData(timeStart, currentTime, rx, tx);
+ history.removeBucketsBefore(currentTime - SUMMARY_MAX_HISTORY);
+ }
+
+ mLastPollStats = current;
+
+ // decide if enough has changed to trigger persist
+ final NetworkStats persistDelta = computeStatsDelta(mLastPersistStats, current);
+ for (String iface : persistDelta.getKnownIfaces()) {
+ final int index = persistDelta.findIndex(iface, UID_ALL);
+ if (persistDelta.rx[index] > SUMMARY_PERSIST_THRESHOLD
+ || persistDelta.tx[index] > SUMMARY_PERSIST_THRESHOLD) {
+ writeStatsLocked();
+ mLastPersistStats = current;
+ break;
+ }
+ }
+ }
+
+ private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceInfo info) {
+ NetworkStatsHistory stats = mIfaceStats.get(info);
+ if (stats == null) {
+ stats = new NetworkStatsHistory(
+ info.networkType, info.identity, UID_ALL, SUMMARY_BUCKET_DURATION);
+ mIfaceStats.put(info, stats);
+ }
+ return stats;
+ }
+
+ private void readStatsLocked() {
+ if (LOGD) Slog.v(TAG, "readStatsLocked()");
+ // TODO: read historical stats from disk using AtomicFile
+ }
+
+ private void writeStatsLocked() {
+ if (LOGD) Slog.v(TAG, "writeStatsLocked()");
+ // TODO: persist historical stats to disk using AtomicFile
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+ pw.println("Active interfaces:");
+ for (InterfaceInfo info : mActiveIface.values()) {
+ info.dump(" ", pw);
+ }
+
+ pw.println("Known historical stats:");
+ for (NetworkStatsHistory stats : mIfaceStats.values()) {
+ stats.dump(" ", pw);
+ }
+ }
+
+ /**
+ * Details for a well-known network interface, including its name, network
+ * type, and billing relationship identity (such as IMSI).
+ */
+ private static class InterfaceInfo {
+ public final String iface;
+ public final int networkType;
+ public final String identity;
+
+ public InterfaceInfo(String iface, int networkType, String identity) {
+ this.iface = iface;
+ this.networkType = networkType;
+ this.identity = identity;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((identity == null) ? 0 : identity.hashCode());
+ result = prime * result + ((iface == null) ? 0 : iface.hashCode());
+ result = prime * result + networkType;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof InterfaceInfo) {
+ final InterfaceInfo info = (InterfaceInfo) obj;
+ return equal(iface, info.iface) && networkType == info.networkType
+ && equal(identity, info.identity);
+ }
+ return false;
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("InterfaceInfo: iface="); pw.print(iface);
+ pw.print(" networkType="); pw.print(networkType);
+ pw.print(" identity="); pw.println(identity);
+ }
+ }
+
+ private void reportActiveInterface(InterfaceInfo info) {
+ synchronized (mStatsLock) {
+ // TODO: when interface redefined, port over historical stats
+ mActiveIface.put(info.iface, info);
+ }
+ }
+
+ /**
+ * Return the delta between two {@link NetworkStats} snapshots, where {@code
+ * before} can be {@code null}.
+ */
+ private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) {
+ if (before != null) {
+ return current.subtract(before, false);
+ } else {
+ return current;
+ }
+ }
+
+ private static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
+
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index f8d1426..295d569 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -24,7 +24,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+
<application>
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
new file mode 100644
index 0000000..fe88793
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
@@ -0,0 +1,132 @@
+/*
+ * 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.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AbstractFuture;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Future;
+
+/**
+ * {@link ContextWrapper} that can attach listeners for upcoming
+ * {@link Context#sendBroadcast(Intent)}.
+ */
+public class BroadcastInterceptingContext extends ContextWrapper {
+ private static final String TAG = "WatchingContext";
+
+ private final List<BroadcastInterceptor> mInterceptors = Lists.newArrayList();
+
+ public class BroadcastInterceptor extends AbstractFuture<Intent> {
+ private final BroadcastReceiver mReceiver;
+ private final IntentFilter mFilter;
+
+ public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) {
+ mReceiver = receiver;
+ mFilter = filter;
+ }
+
+ public boolean dispatchBroadcast(Intent intent) {
+ if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
+ if (mReceiver != null) {
+ final Context context = BroadcastInterceptingContext.this;
+ mReceiver.onReceive(context, intent);
+ return false;
+ } else {
+ set(intent);
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ public BroadcastInterceptingContext(Context base) {
+ super(base);
+ }
+
+ public Future<Intent> nextBroadcastIntent(String action) {
+ return nextBroadcastIntent(new IntentFilter(action));
+ }
+
+ public Future<Intent> nextBroadcastIntent(IntentFilter filter) {
+ final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter);
+ synchronized (mInterceptors) {
+ mInterceptors.add(interceptor);
+ }
+ return interceptor;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ synchronized (mInterceptors) {
+ mInterceptors.add(new BroadcastInterceptor(receiver, filter));
+ }
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ return registerReceiver(receiver, filter);
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ synchronized (mInterceptors) {
+ final Iterator<BroadcastInterceptor> i = mInterceptors.iterator();
+ while (i.hasNext()) {
+ final BroadcastInterceptor interceptor = i.next();
+ if (receiver.equals(interceptor.mReceiver)) {
+ i.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ synchronized (mInterceptors) {
+ final Iterator<BroadcastInterceptor> i = mInterceptors.iterator();
+ while (i.hasNext()) {
+ final BroadcastInterceptor interceptor = i.next();
+ if (interceptor.dispatchBroadcast(intent)) {
+ i.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent intent) {
+ sendBroadcast(intent);
+ }
+
+ @Override
+ public void removeStickyBroadcast(Intent intent) {
+ // ignored
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
new file mode 100644
index 0000000..6552cdf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.server;
+
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+
+import android.app.IActivityManager;
+import android.app.IProcessObserver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkStatsService;
+import android.os.Binder;
+import android.os.IPowerManager;
+import android.test.AndroidTestCase;
+import android.test.mock.MockPackageManager;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+import com.android.server.net.NetworkPolicyManagerService;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+
+import java.util.concurrent.Future;
+
+/**
+ * Tests for {@link NetworkPolicyManagerService}.
+ */
+@LargeTest
+public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
+ private static final String TAG = "NetworkPolicyManagerServiceTest";
+
+ private BroadcastInterceptingContext mServiceContext;
+
+ private IActivityManager mActivityManager;
+ private IPowerManager mPowerManager;
+ private INetworkStatsService mStatsService;
+ private INetworkPolicyListener mPolicyListener;
+
+ private NetworkPolicyManagerService mService;
+ private IProcessObserver mProcessObserver;
+
+ private Binder mStubBinder = new Binder();
+
+ private static final int UID_A = 800;
+ private static final int UID_B = 801;
+
+ private static final int PID_1 = 400;
+ private static final int PID_2 = 401;
+ private static final int PID_3 = 402;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // intercept various broadcasts, and pretend that uids have packages
+ mServiceContext = new BroadcastInterceptingContext(getContext()) {
+ @Override
+ public PackageManager getPackageManager() {
+ return new MockPackageManager() {
+ @Override
+ public String[] getPackagesForUid(int uid) {
+ return new String[] { "com.example" };
+ }
+ };
+ }
+ };
+
+ mActivityManager = createMock(IActivityManager.class);
+ mPowerManager = createMock(IPowerManager.class);
+ mStatsService = createMock(INetworkStatsService.class);
+ mPolicyListener = createMock(INetworkPolicyListener.class);
+
+ mService = new NetworkPolicyManagerService(
+ mServiceContext, mActivityManager, mPowerManager, mStatsService);
+
+ // RemoteCallbackList needs a binder to use as key
+ expect(mPolicyListener.asBinder()).andReturn(mStubBinder).atLeastOnce();
+ replay();
+ mService.registerListener(mPolicyListener);
+ verifyAndReset();
+
+ // catch the registered IProcessObserver during systemReady()
+ final Capture<IProcessObserver> processObserver = new Capture<IProcessObserver>();
+ mActivityManager.registerProcessObserver(capture(processObserver));
+ expectLastCall().atLeastOnce();
+
+ // expect to answer screen status during systemReady()
+ expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
+
+ replay();
+ mService.systemReady();
+ verifyAndReset();
+
+ mProcessObserver = processObserver.getValue();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mServiceContext = null;
+
+ mActivityManager = null;
+ mPowerManager = null;
+ mStatsService = null;
+ mPolicyListener = null;
+
+ mService = null;
+ mProcessObserver = null;
+
+ super.tearDown();
+ }
+
+ @Suppress
+ public void testPolicyChangeTriggersBroadcast() throws Exception {
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+
+ // change background policy and expect broadcast
+ final Future<Intent> backgroundChanged = mServiceContext.nextBroadcastIntent(
+ ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+
+ mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+
+ backgroundChanged.get();
+ }
+
+ public void testPidForegroundCombined() throws Exception {
+ // push all uid into background
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+ mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false);
+ assertFalse(mService.isUidForeground(UID_A));
+ assertFalse(mService.isUidForeground(UID_B));
+
+ // push one of the shared pids into foreground
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+ assertTrue(mService.isUidForeground(UID_A));
+ assertFalse(mService.isUidForeground(UID_B));
+
+ // and swap another uid into foreground
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+ mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true);
+ assertFalse(mService.isUidForeground(UID_A));
+ assertTrue(mService.isUidForeground(UID_B));
+
+ // push both pid into foreground
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+ assertTrue(mService.isUidForeground(UID_A));
+
+ // pull one out, should still be foreground
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ assertTrue(mService.isUidForeground(UID_A));
+
+ // pull final pid out, should now be background
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+ assertFalse(mService.isUidForeground(UID_A));
+ }
+
+ public void testScreenChangesRules() throws Exception {
+ // push strict policy for foreground uid, verify ALLOW rule
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+ verifyAndReset();
+
+ // now turn screen off and verify REJECT rule
+ expect(mPowerManager.isScreenOn()).andReturn(false).atLeastOnce();
+ expectRulesChanged(UID_A, RULE_REJECT_PAID);
+ replay();
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+ verifyAndReset();
+
+ // and turn screen back on, verify ALLOW rule restored
+ expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+ verifyAndReset();
+ }
+
+ public void testPolicyNone() throws Exception {
+ // POLICY_NONE should RULE_ALLOW in foreground
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ verifyAndReset();
+
+ // POLICY_NONE should RULE_ALLOW in background
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ verifyAndReset();
+ }
+
+ public void testPolicyReject() throws Exception {
+ // POLICY_REJECT should RULE_ALLOW in background
+ expectRulesChanged(UID_A, RULE_REJECT_PAID);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+ verifyAndReset();
+
+ // POLICY_REJECT should RULE_ALLOW in foreground
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ verifyAndReset();
+
+ // POLICY_REJECT should RULE_REJECT in background
+ expectRulesChanged(UID_A, RULE_REJECT_PAID);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ verifyAndReset();
+ }
+
+ public void testPolicyRejectAddRemove() throws Exception {
+ // POLICY_NONE should have RULE_ALLOW in background
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ verifyAndReset();
+
+ // adding POLICY_REJECT should cause RULE_REJECT
+ expectRulesChanged(UID_A, RULE_REJECT_PAID);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+ verifyAndReset();
+
+ // removing POLICY_REJECT should return us to RULE_ALLOW
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+ verifyAndReset();
+ }
+
+ private void expectRulesChanged(int uid, int policy) throws Exception {
+ mPolicyListener.onRulesChanged(eq(uid), eq(policy));
+ expectLastCall().atLeastOnce();
+ }
+
+ private void replay() {
+ EasyMock.replay(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
+ }
+
+ private void verifyAndReset() {
+ EasyMock.verify(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
+ EasyMock.reset(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
index ca33d32..d1ee4f6 100644
--- a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
@@ -25,14 +25,9 @@
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.AbstractFuture;
-
import android.content.ContentResolver;
import android.content.Context;
-import android.content.ContextWrapper;
import android.content.Intent;
-import android.content.IntentFilter;
import android.net.INetworkManagementEventObserver;
import android.net.NetworkStats;
import android.net.ThrottleManager;
@@ -48,8 +43,6 @@
import android.util.Log;
import android.util.TrustedTime;
-import java.util.Iterator;
-import java.util.List;
import java.util.concurrent.Future;
/**
@@ -66,7 +59,7 @@
private static final String TEST_IFACE = "test0";
- private WatchingContext mWatchingContext;
+ private BroadcastInterceptingContext mWatchingContext;
private INetworkManagementService mMockNMService;
private TrustedTime mMockTime;
@@ -76,7 +69,7 @@
public void setUp() throws Exception {
super.setUp();
- mWatchingContext = new WatchingContext(getContext());
+ mWatchingContext = new BroadcastInterceptingContext(getContext());
mMockNMService = createMock(INetworkManagementService.class);
mMockTime = createMock(TrustedTime.class);
@@ -354,69 +347,4 @@
pollAction.get();
}
-
-
- /**
- * {@link ContextWrapper} that can attach listeners for upcoming
- * {@link Context#sendBroadcast(Intent)}.
- */
- private static class WatchingContext extends ContextWrapper {
- private List<LocalBroadcastReceiver> mReceivers = Lists.newArrayList();
-
- public class LocalBroadcastReceiver extends AbstractFuture<Intent> {
- private IntentFilter mFilter;
-
- public LocalBroadcastReceiver(IntentFilter filter) {
- mFilter = filter;
- }
-
- public boolean dispatchBroadcast(Intent intent) {
- if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
- set(intent);
- return true;
- } else {
- return false;
- }
- }
- }
-
- public WatchingContext(Context base) {
- super(base);
- }
-
- public Future<Intent> nextBroadcastIntent(String action) {
- return nextBroadcastIntent(new IntentFilter(action));
- }
-
- public Future<Intent> nextBroadcastIntent(IntentFilter filter) {
- final LocalBroadcastReceiver receiver = new LocalBroadcastReceiver(filter);
- synchronized (mReceivers) {
- mReceivers.add(receiver);
- }
- return receiver;
- }
-
- @Override
- public void sendBroadcast(Intent intent) {
- synchronized (mReceivers) {
- final Iterator<LocalBroadcastReceiver> i = mReceivers.iterator();
- while (i.hasNext()) {
- final LocalBroadcastReceiver receiver = i.next();
- if (receiver.dispatchBroadcast(intent)) {
- i.remove();
- }
- }
- }
- }
-
- @Override
- public void sendStickyBroadcast(Intent intent) {
- sendBroadcast(intent);
- }
-
- @Override
- public void removeStickyBroadcast(Intent intent) {
- // ignored
- }
- }
}
diff --git a/tests/BiDiTests/AndroidManifest.xml b/tests/BiDiTests/AndroidManifest.xml
index e54194c..b3d8893 100644
--- a/tests/BiDiTests/AndroidManifest.xml
+++ b/tests/BiDiTests/AndroidManifest.xml
@@ -57,6 +57,20 @@
</intent-filter>
</activity>
+ <activity android:name=".BiDiTestFrameLayoutLtrActivity"
+ android:windowSoftInputMode="stateAlwaysHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".BiDiTestFrameLayoutRtlActivity"
+ android:windowSoftInputMode="stateAlwaysHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/BiDiTests/res/layout/frame_layout_ltr.xml b/tests/BiDiTests/res/layout/frame_layout_ltr.xml
new file mode 100644
index 0000000..c27b260
--- /dev/null
+++ b/tests/BiDiTests/res/layout/frame_layout_ltr.xml
@@ -0,0 +1,81 @@
+<?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:id="@+id/frame_layout_ltr"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:horizontalDirection="ltr"
+ android:background="#FF000000">
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="right|center_vertical"
+ android:background="#FFFF0000">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="left|center_vertical"
+ android:background="#FF00FF00">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="top|center_horizontal"
+ android:background="#FF0000FF">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="bottom|center_horizontal"
+ android:background="#FF00FFFF">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="top|before"
+ android:background="#FFFFFFFF">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="top|after"
+ android:background="#FFFFFF00">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="bottom|before"
+ android:background="#FFFFFFFF">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="bottom|after"
+ android:background="#FFFFFF00">
+ </RelativeLayout>
+
+</FrameLayout>
+
diff --git a/tests/BiDiTests/res/layout/frame_layout_rtl.xml b/tests/BiDiTests/res/layout/frame_layout_rtl.xml
new file mode 100644
index 0000000..33ca40a
--- /dev/null
+++ b/tests/BiDiTests/res/layout/frame_layout_rtl.xml
@@ -0,0 +1,81 @@
+<?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:id="@+id/frame_layout_rtl"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:horizontalDirection="rtl"
+ android:background="#FF000000">
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="right|center_vertical"
+ android:background="#FFFF0000">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="left|center_vertical"
+ android:background="#FF00FF00">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="top|center_horizontal"
+ android:background="#FF0000FF">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="bottom|center_horizontal"
+ android:background="#FF00FFFF">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="top|before"
+ android:background="#FFFFFFFF">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="top|after"
+ android:background="#FFFFFF00">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="bottom|before"
+ android:background="#FFFFFFFF">
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_gravity="bottom|after"
+ android:background="#FFFFFF00">
+ </RelativeLayout>
+
+</FrameLayout>
+
diff --git a/tests/BiDiTests/res/layout/linear_layout_ltr.xml b/tests/BiDiTests/res/layout/linear_layout_ltr.xml
index a95fb0e..1370ae1c 100644
--- a/tests/BiDiTests/res/layout/linear_layout_ltr.xml
+++ b/tests/BiDiTests/res/layout/linear_layout_ltr.xml
@@ -15,7 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/layouttest"
+ android:id="@+id/linear_layout_ltr"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/tests/BiDiTests/res/layout/linear_layout_rtl.xml b/tests/BiDiTests/res/layout/linear_layout_rtl.xml
index 0d60950..6044f16 100644
--- a/tests/BiDiTests/res/layout/linear_layout_rtl.xml
+++ b/tests/BiDiTests/res/layout/linear_layout_rtl.xml
@@ -15,7 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/layouttest"
+ android:id="@+id/linear_layout_rtl"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
index 0d9b4f7..d89b8fd 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
@@ -37,20 +37,34 @@
intent = new Intent().setClass(this, BiDiTestBasicActivity.class);
// Initialize a TabSpec for each tab and add it to the TabHost
- spec = tabHost.newTabSpec("basic").setIndicator("Basic").setContent(intent);
+ spec = tabHost.newTabSpec("basic").setIndicator("Basic").
+ setContent(intent);
tabHost.addTab(spec);
// Do the same for the other tabs
intent = new Intent().setClass(this, BiDiTestCanvasActivity.class);
- spec = tabHost.newTabSpec("canvas").setIndicator("Canvas").setContent(intent);
+ spec = tabHost.newTabSpec("canvas").setIndicator("Canvas").
+ setContent(intent);
tabHost.addTab(spec);
intent = new Intent().setClass(this, BiDiTestLinearLayoutLtrActivity.class);
- spec = tabHost.newTabSpec("layout-ltr").setIndicator("LinearLayout LTR").setContent(intent);
+ spec = tabHost.newTabSpec("linear-layout-ltr").setIndicator("LinearLayout LTR").
+ setContent(intent);
tabHost.addTab(spec);
intent = new Intent().setClass(this, BiDiTestLinearLayoutRtlActivity.class);
- spec = tabHost.newTabSpec("layout-rtl").setIndicator("LinearLayout RTL").setContent(intent);
+ spec = tabHost.newTabSpec("linear-layout-rtl").setIndicator("LinearLayout RTL").
+ setContent(intent);
+ tabHost.addTab(spec);
+
+ intent = new Intent().setClass(this, BiDiTestFrameLayoutLtrActivity.class);
+ spec = tabHost.newTabSpec("frame-layout-ltr").setIndicator("FrameLayout LTR").
+ setContent(intent);
+ tabHost.addTab(spec);
+
+ intent = new Intent().setClass(this, BiDiTestFrameLayoutRtlActivity.class);
+ spec = tabHost.newTabSpec("frame-layout-rtl").setIndicator("FrameLayout RTL").
+ setContent(intent);
tabHost.addTab(spec);
tabHost.setCurrentTab(0);
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutLtrActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutLtrActivity.java
new file mode 100644
index 0000000..6ce4fe4
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutLtrActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.bidi;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class BiDiTestFrameLayoutLtrActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.frame_layout_ltr);
+ }
+}
+
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutRtlActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutRtlActivity.java
new file mode 100644
index 0000000..6012a5c
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestFrameLayoutRtlActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.bidi;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class BiDiTestFrameLayoutRtlActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.frame_layout_rtl);
+ }
+}
+
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutLtrActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutLtrActivity.java
index 6d8f11d..280af6a 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutLtrActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutLtrActivity.java
@@ -18,18 +18,13 @@
import android.app.Activity;
import android.os.Bundle;
-import android.widget.LinearLayout;
public class BiDiTestLinearLayoutLtrActivity extends Activity {
- private LinearLayout layout;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.linear_layout_ltr);
-
- layout = (LinearLayout) findViewById(R.id.layouttest);
}
}
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutRtlActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutRtlActivity.java
index 0130793..7121a62 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutRtlActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestLinearLayoutRtlActivity.java
@@ -18,18 +18,13 @@
import android.app.Activity;
import android.os.Bundle;
-import android.widget.LinearLayout;
public class BiDiTestLinearLayoutRtlActivity extends Activity {
- private LinearLayout layout;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.linear_layout_rtl);
-
- layout = (LinearLayout) findViewById(R.id.layouttest);
}
}