Merge change I36785a9d into eclair-mr2
* changes:
Telephony: Make resetting radio on a radio technology change optional
diff --git a/api/current.xml b/api/current.xml
index 41714ef..af9c67d 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -23672,6 +23672,17 @@
</implements>
<implements name="android.content.DialogInterface.OnDismissListener">
</implements>
+<method name="getSearchablesInGlobalSearch"
+ return="java.util.List<android.app.SearchableInfo>"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="onCancel"
return="void"
abstract="false"
@@ -24131,6 +24142,444 @@
>
</method>
</interface>
+<class name="SearchableInfo"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="autoUrlDetect"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="findActionKey"
+ return="android.app.SearchableInfo.ActionKeyInfo"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+</method>
+<method name="getHintId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getIconId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getImeOptions"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getInputType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getLabelId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSearchActivity"
+ return="android.content.ComponentName"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSearchButtonText"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSettingsDescription"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestAuthority"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestIntentAction"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestIntentData"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestPackage"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestPath"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestSelection"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestThreshold"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceLanguageId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceLanguageModeId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceMaxResults"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoicePromptTextId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceSearchEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceSearchLaunchRecognizer"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceSearchLaunchWebSearch"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="queryAfterZeroResults"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="shouldIncludeInGlobalSearch"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="shouldRewriteQueryFromData"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="shouldRewriteQueryFromText"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="SearchableInfo.ActionKeyInfo"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getKeyCode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getQueryActionMsg"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestActionMsg"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestActionMsgColumn"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+</class>
<class name="Service"
extends="android.content.ContextWrapper"
abstract="true"
@@ -31903,6 +32352,17 @@
visibility="public"
>
</field>
+<field name="BIND_NOT_FOREGROUND"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="CLIPBOARD_SERVICE"
type="java.lang.String"
transient="false"
@@ -83994,6 +84454,148 @@
>
</method>
</class>
+<class name="TrafficStats"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="TrafficStats"
+ type="android.net.TrafficStats"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getMobileRxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMobileRxPkts"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMobileTxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMobileTxPkts"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTotalRxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTotalRxPkts"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTotalTxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTotalTxPkts"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getUidRxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uid" type="int">
+</parameter>
+</method>
+<method name="getUidTxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uid" type="int">
+</parameter>
+</method>
+<field name="UNSUPPORTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="Uri"
extends="java.lang.Object"
abstract="true"
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 467812e..1115f92 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -691,6 +691,20 @@
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
ordered, sticky);
+ } else {
+ // The activity manager dispatched a broadcast to a registered
+ // receiver in this process, but before it could be delivered the
+ // receiver was unregistered. Acknowledge the broadcast on its
+ // behalf so that the system's broadcast sequence can continue.
+ if (DEBUG_BROADCAST) {
+ Log.i(TAG, "Broadcast to unregistered receiver");
+ }
+ IActivityManager mgr = ActivityManagerNative.getDefault();
+ try {
+ mgr.finishReceiver(this, resultCode, data, extras, false);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Couldn't finish broadcast to unregistered receiver");
+ }
}
}
}
@@ -716,8 +730,8 @@
BroadcastReceiver receiver = mReceiver;
if (DEBUG_BROADCAST) {
int seq = mCurIntent.getIntExtra("seq", -1);
- Log.i(TAG, "Dispathing broadcast " + mCurIntent.getAction() + " seq=" + seq
- + " to " + mReceiver);
+ Log.i(TAG, "Dispatching broadcast " + mCurIntent.getAction()
+ + " seq=" + seq + " to " + mReceiver);
}
if (receiver == null) {
return;
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index ab5e102..1f17476 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -2038,10 +2038,8 @@
* Returns a list of the searchable activities that can be included in global search.
*
* @return a list containing searchable information for all searchable activities
- * that have the <code>exported</code> attribute set in their searchable
- * meta-data.
- *
- * @hide because SearchableInfo is not part of the API.
+ * that have the <code>android:includeInGlobalSearch</code> attribute set
+ * in their searchable meta-data.
*/
public List<SearchableInfo> getSearchablesInGlobalSearch() {
try {
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index cbf7b3d..9897742 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -38,8 +38,9 @@
import java.util.HashMap;
/**
- *
- * @hide Pending API council approval
+ * Searchability meta-data for an activity.
+ * See <a href="SearchManager.html#SearchabilityMetadata">Searchability meta-data</a>
+ * for more information.
*/
public final class SearchableInfo implements Parcelable {
@@ -87,9 +88,9 @@
private final String mSuggestProviderPackage;
// Flag values for Searchable_voiceSearchMode
- private static int VOICE_SEARCH_SHOW_BUTTON = 1;
- private static int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
- private static int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
+ private static final int VOICE_SEARCH_SHOW_BUTTON = 1;
+ private static final int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
+ private static final int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
private final int mVoiceSearchMode;
private final int mVoiceLanguageModeId; // voiceLanguageModel
private final int mVoicePromptTextId; // voicePromptText
@@ -123,6 +124,8 @@
/**
* Checks whether the badge should be a text label.
+ *
+ * @hide This feature is deprecated, no need to add it to the API.
*/
public boolean useBadgeLabel() {
return 0 != (mSearchMode & SEARCH_MODE_BADGE_LABEL);
@@ -130,6 +133,8 @@
/**
* Checks whether the badge should be an icon.
+ *
+ * @hide This feature is deprecated, no need to add it to the API.
*/
public boolean useBadgeIcon() {
return (0 != (mSearchMode & SEARCH_MODE_BADGE_ICON)) && (mIconId != 0);
@@ -220,6 +225,7 @@
*
* @param context You need to supply a context to start with
* @return Returns a context related to the searchable activity
+ * @hide
*/
public Context getActivityContext(Context context) {
return createActivityContext(context, mSearchActivity);
@@ -251,6 +257,7 @@
* @param activityContext If we can determine that the provider and the activity are the
* same, we'll just return this one.
* @return Returns a context related to the context provider
+ * @hide
*/
public Context getProviderContext(Context context, Context activityContext) {
Context theirContext = null;
@@ -351,7 +358,11 @@
}
/**
- * Private class used to hold the "action key" configuration
+ * Information about an action key in searchability meta-data.
+ * See <a href="SearchManager.html#SearchabilityMetadata">Searchability meta-data</a>
+ * for more information.
+ *
+ * @see SearchableInfo#findActionKey(int)
*/
public static class ActionKeyInfo implements Parcelable {
@@ -368,7 +379,7 @@
* construct the object.
* @throws IllegalArgumentException if the action key configuration is invalid
*/
- public ActionKeyInfo(Context activityContext, AttributeSet attr) {
+ ActionKeyInfo(Context activityContext, AttributeSet attr) {
TypedArray a = activityContext.obtainStyledAttributes(attr,
com.android.internal.R.styleable.SearchableActionKey);
@@ -399,7 +410,7 @@
* @param in The Parcel containing the previously written ActionKeyInfo,
* positioned at the location in the buffer where it was written.
*/
- public ActionKeyInfo(Parcel in) {
+ private ActionKeyInfo(Parcel in) {
mKeyCode = in.readInt();
mQueryActionMsg = in.readString();
mSuggestActionMsg = in.readString();
@@ -461,6 +472,8 @@
* @param activityInfo Activity to get search information from.
* @return Search information about the given activity, or {@code null} if
* the activity has no or invalid searchability meta-data.
+ *
+ * @hide For use by SearchManagerService.
*/
public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo) {
// for each component, try to find metadata
@@ -720,7 +733,7 @@
* @param in The Parcel containing the previously written SearchableInfo,
* positioned at the location in the buffer where it was written.
*/
- public SearchableInfo(Parcel in) {
+ SearchableInfo(Parcel in) {
mLabelId = in.readInt();
mSearchActivity = ComponentName.readFromParcel(in);
mHintId = in.readInt();
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index 0db6155..091d44e 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -17,10 +17,10 @@
package android.content;
import android.accounts.Account;
+import android.net.TrafficStats;
import android.os.Bundle;
-import android.os.Process;
-import android.os.NetStat;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.util.EventLog;
@@ -157,8 +157,8 @@
SyncResult syncResult = new SyncResult();
int uid = Process.myUid();
- mInitialTxBytes = NetStat.getUidTxBytes(uid);
- mInitialRxBytes = NetStat.getUidRxBytes(uid);
+ mInitialTxBytes = TrafficStats.getUidTxBytes(uid);
+ mInitialRxBytes = TrafficStats.getUidRxBytes(uid);
ContentProviderClient provider = null;
try {
provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
@@ -175,8 +175,8 @@
if (!isCanceled()) {
mSyncContext.onFinished(syncResult);
}
- onLogSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, syncResult);
+ onLogSyncDetails(TrafficStats.getUidTxBytes(uid) - mInitialTxBytes,
+ TrafficStats.getUidRxBytes(uid) - mInitialRxBytes, syncResult);
// synchronize so that the assignment will be seen by other threads
// that also synchronize accesses to mSyncThread
synchronized (mSyncThreadLock) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3344158..2ab5357 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -104,6 +104,18 @@
*/
public static final int BIND_DEBUG_UNBIND = 0x0002;
+ /**
+ * Flag for {@link #bindService}: don't allow this binding to raise
+ * the target service's process to the foreground scheduling priority.
+ * It will still be raised to the at least the same memory priority
+ * as the client (so that its process will not be killable in any
+ * situation where the client is not killable), but for CPU scheduling
+ * purposes it may be left in the background. This only has an impact
+ * in the situation where the binding client is a foreground process
+ * and the target service is in a background process.
+ */
+ public static final int BIND_NOT_FOREGROUND = 0x0004;
+
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 0589ce6..d436365 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -557,7 +557,7 @@
intent.setAction("android.content.SyncAdapter");
intent.setComponent(syncAdapterInfo.componentName);
mContext.bindService(intent, new InitializerServiceConnection(account, authority),
- Context.BIND_AUTO_CREATE);
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
}
private class InitializerServiceConnection implements ServiceConnection {
@@ -1145,7 +1145,8 @@
com.android.internal.R.string.sync_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
- return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ return mContext.bindService(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
}
void unBindFromSyncAdapter() {
diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java
index b46c545..5ccaa26 100644
--- a/core/java/android/content/TempProviderSyncAdapter.java
+++ b/core/java/android/content/TempProviderSyncAdapter.java
@@ -1,9 +1,12 @@
package android.content;
+import android.accounts.Account;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
import android.database.SQLException;
+import android.net.TrafficStats;
import android.os.Bundle;
import android.os.Debug;
-import android.os.NetStat;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemProperties;
@@ -12,9 +15,6 @@
import android.util.EventLog;
import android.util.Log;
import android.util.TimingLogger;
-import android.accounts.Account;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
import java.io.IOException;
@@ -203,8 +203,8 @@
if (mProviderSyncStarted) mProvider.onSyncCanceled();
// We may lose the last few sync events when canceling. Oh well.
int uid = Process.myUid();
- logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
+ logSyncDetails(TrafficStats.getUidTxBytes(uid) - mInitialTxBytes,
+ TrafficStats.getUidRxBytes(uid) - mInitialRxBytes, mResult);
}
@Override
@@ -212,8 +212,8 @@
Process.setThreadPriority(Process.myTid(),
Process.THREAD_PRIORITY_BACKGROUND);
int uid = Process.myUid();
- mInitialTxBytes = NetStat.getUidTxBytes(uid);
- mInitialRxBytes = NetStat.getUidRxBytes(uid);
+ mInitialTxBytes = TrafficStats.getUidTxBytes(uid);
+ mInitialRxBytes = TrafficStats.getUidRxBytes(uid);
try {
sync(mSyncContext, mAccount, mAuthority, mExtras);
} catch (SQLException e) {
@@ -222,8 +222,8 @@
} finally {
mSyncThread = null;
if (!mIsCanceled) {
- logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
+ logSyncDetails(TrafficStats.getUidTxBytes(uid) - mInitialTxBytes,
+ TrafficStats.getUidRxBytes(uid) - mInitialRxBytes, mResult);
mSyncContext.onFinished(mResult);
}
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 0f7ef22..76131fc 100755
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -1127,7 +1127,7 @@
// Needs to be called after the gesture detector gets a turn, as it may have
// displayed the mini keyboard
- if (mMiniKeyboardOnScreen) {
+ if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
return true;
}
@@ -1222,6 +1222,7 @@
break;
case MotionEvent.ACTION_CANCEL:
removeMessages();
+ dismissPopupKeyboard();
mAbortKey = true;
showPreview(NOT_A_KEY);
invalidateKey(mCurrentKey);
diff --git a/core/java/android/os/NetStat.java b/core/java/android/net/TrafficStats.java
similarity index 71%
rename from core/java/android/os/NetStat.java
rename to core/java/android/net/TrafficStats.java
index e294cdf..62e9f1f 100644
--- a/core/java/android/os/NetStat.java
+++ b/core/java/android/net/TrafficStats.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.os;
+package android.net;
import android.util.Log;
@@ -22,11 +22,22 @@
import java.io.RandomAccessFile;
import java.io.IOException;
-/** @hide */
-public class NetStat {
+/**
+ * Class that provides network traffic statistics. These statistics include bytes transmitted and
+ * received and network packets transmitted and received, over all interfaces, over the mobile
+ * interface, and on a per-UID basis.
+ * <p>
+ * These statistics may not be available on all platforms. If the statistics are not supported
+ * by this device, {@link #UNSUPPORTED} will be returned.
+ */
+public class TrafficStats {
+ /**
+ * The return value to indicate that the device does not support the statistic.
+ */
+ public final static int UNSUPPORTED = -1;
// Logging tag.
- private final static String TAG = "netstat";
+ private final static String TAG = "trafficstats";
// We pre-create all the File objects so we don't spend a lot of
// CPU at runtime converting from Java Strings to byte[] for the
@@ -38,36 +49,40 @@
private final static File SYS_CLASS_NET_DIR = new File("/sys/class/net");
/**
- * Get total number of tx packets sent through rmnet0 or ppp0
+ * Get the total number of packets transmitted through the mobile interface.
*
- * @return number of Tx packets through rmnet0 or ppp0
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getMobileTxPkts() {
return getMobileStat(MOBILE_TX_PACKETS);
}
/**
- * Get total number of rx packets received through rmnet0 or ppp0
+ * Get the total number of packets received through the mobile interface.
*
- * @return number of Rx packets through rmnet0 or ppp0
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getMobileRxPkts() {
return getMobileStat(MOBILE_RX_PACKETS);
}
/**
- * Get total number of tx bytes received through rmnet0 or ppp0
+ * Get the total number of bytes transmitted through the mobile interface.
*
- * @return number of Tx bytes through rmnet0 or ppp0
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getMobileTxBytes() {
return getMobileStat(MOBILE_TX_BYTES);
}
/**
- * Get total number of rx bytes received through rmnet0 or ppp0
+ * Get the total number of bytes received through the mobile interface.
*
- * @return number of Rx bytes through rmnet0 or ppp0
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getMobileRxBytes() {
return getMobileStat(MOBILE_RX_BYTES);
@@ -76,7 +91,8 @@
/**
* Get the total number of packets sent through all network interfaces.
*
- * @return the number of packets sent through all network interfaces
+ * @return the number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getTotalTxPkts() {
return getTotalStat("tx_packets");
@@ -85,7 +101,8 @@
/**
* Get the total number of packets received through all network interfaces.
*
- * @return the number of packets received through all network interfaces
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getTotalRxPkts() {
return getTotalStat("rx_packets");
@@ -94,7 +111,8 @@
/**
* Get the total number of bytes sent through all network interfaces.
*
- * @return the number of bytes sent through all network interfaces
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getTotalTxBytes() {
return getTotalStat("tx_bytes");
@@ -103,35 +121,35 @@
/**
* Get the total number of bytes received through all network interfaces.
*
- * @return the number of bytes received through all network interfaces
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getTotalRxBytes() {
return getTotalStat("rx_bytes");
}
/**
- * Gets network bytes sent for this UID.
+ * Get the number of bytes sent through the network for this UID.
* The statistics are across all interfaces.
- * The statistics come from /proc/uid_stat.
*
* {@see android.os.Process#myUid()}.
*
- * @param uid
- * @return byte count
+ * @param uid The UID of the process to examine.
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getUidTxBytes(int uid) {
return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_snd");
}
/**
- * Gets network bytes received for this UID.
+ * Get the number of bytes received through the network for this UID.
* The statistics are across all interfaces.
- * The statistics come from /proc/uid_stat.
*
* {@see android.os.Process#myUid()}.
*
- * @param uid
- * @return byte count
+ * @param uid The UID of the process to examine.
+ * @return number of bytes
*/
public static long getUidRxBytes(int uid) {
return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_rcv");
@@ -159,7 +177,7 @@
File[] nets = SYS_CLASS_NET_DIR.listFiles();
if (nets == null) {
- return 0;
+ return UNSUPPORTED;
}
long total = 0;
StringBuffer strbuf = new StringBuffer();
@@ -187,14 +205,14 @@
e);
}
}
- return 0L;
+ return UNSUPPORTED;
}
// File will have format <number><newline>
private static long getNumberFromFilePath(String filename) {
RandomAccessFile raf = getFile(filename);
if (raf == null) {
- return 0L;
+ return UNSUPPORTED;
}
return getNumberFromFile(raf, filename);
}
@@ -209,7 +227,7 @@
raf.close();
} catch (IOException e) {
Log.w(TAG, "Exception getting TCP bytes from " + filename, e);
- return 0L;
+ return UNSUPPORTED;
} finally {
if (raf != null) {
try {
diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java
deleted file mode 100644
index 8bb7adf..0000000
--- a/core/java/android/provider/Gmail.java
+++ /dev/null
@@ -1,2468 +0,0 @@
-/*
- * Copyright (C) 2007 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.provider;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentQueryMap;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.text.Html;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.text.style.CharacterStyle;
-import android.util.Log;
-
-import com.android.common.Patterns;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A thin wrapper over the content resolver for accessing the gmail provider.
- *
- * @hide
- */
-public final class Gmail {
- // Set to true to enable extra debugging.
- private static final boolean DEBUG = false;
-
- public static final String GMAIL_AUTH_SERVICE = "mail";
- // These constants come from google3/java/com/google/caribou/backend/MailLabel.java.
- public static final String LABEL_SENT = "^f";
- public static final String LABEL_INBOX = "^i";
- public static final String LABEL_DRAFT = "^r";
- public static final String LABEL_UNREAD = "^u";
- public static final String LABEL_TRASH = "^k";
- public static final String LABEL_SPAM = "^s";
- public static final String LABEL_STARRED = "^t";
- public static final String LABEL_CHAT = "^b"; // 'b' for 'buzz'
- public static final String LABEL_VOICEMAIL = "^vm";
- public static final String LABEL_IGNORED = "^g";
- public static final String LABEL_ALL = "^all";
- // These constants (starting with "^^") are only used locally and are not understood by the
- // server.
- public static final String LABEL_VOICEMAIL_INBOX = "^^vmi";
- public static final String LABEL_CACHED = "^^cached";
- public static final String LABEL_OUTBOX = "^^out";
-
- public static final String AUTHORITY = "gmail-ls";
- private static final String TAG = "Gmail";
- private static final String AUTHORITY_PLUS_CONVERSATIONS =
- "content://" + AUTHORITY + "/conversations/";
- private static final String AUTHORITY_PLUS_LABELS =
- "content://" + AUTHORITY + "/labels/";
- private static final String AUTHORITY_PLUS_MESSAGES =
- "content://" + AUTHORITY + "/messages/";
- private static final String AUTHORITY_PLUS_SETTINGS =
- "content://" + AUTHORITY + "/settings/";
-
- public static final Uri BASE_URI = Uri.parse(
- "content://" + AUTHORITY);
- private static final Uri LABELS_URI =
- Uri.parse(AUTHORITY_PLUS_LABELS);
- private static final Uri CONVERSATIONS_URI =
- Uri.parse(AUTHORITY_PLUS_CONVERSATIONS);
- private static final Uri SETTINGS_URI =
- Uri.parse(AUTHORITY_PLUS_SETTINGS);
-
- /** Separates email addresses in strings in the database. */
- public static final String EMAIL_SEPARATOR = "\n";
- public static final Pattern EMAIL_SEPARATOR_PATTERN = Pattern.compile(EMAIL_SEPARATOR);
-
- /**
- * Space-separated lists have separators only between items.
- */
- private static final char SPACE_SEPARATOR = ' ';
- public static final Pattern SPACE_SEPARATOR_PATTERN = Pattern.compile(" ");
-
- /**
- * Comma-separated lists have separators between each item, before the first and after the last
- * item. The empty list is <tt>,</tt>.
- *
- * <p>This makes them easier to modify with SQL since it is not a special case to add or
- * remove the last item. Having a separator on each side of each value also makes it safe to use
- * SQL's REPLACE to remove an item from a string by using REPLACE(',value,', ',').
- *
- * <p>We could use the same separator for both lists but this makes it easier to remember which
- * kind of list one is dealing with.
- */
- private static final char COMMA_SEPARATOR = ',';
- public static final Pattern COMMA_SEPARATOR_PATTERN = Pattern.compile(",");
-
- /** Separates attachment info parts in strings in the database. */
- public static final String ATTACHMENT_INFO_SEPARATOR = "\n";
- public static final Pattern ATTACHMENT_INFO_SEPARATOR_PATTERN =
- Pattern.compile(ATTACHMENT_INFO_SEPARATOR);
-
- public static final Character SENDER_LIST_SEPARATOR = '\n';
- public static final String SENDER_LIST_TOKEN_ELIDED = "e";
- public static final String SENDER_LIST_TOKEN_NUM_MESSAGES = "n";
- public static final String SENDER_LIST_TOKEN_NUM_DRAFTS = "d";
- public static final String SENDER_LIST_TOKEN_LITERAL = "l";
- public static final String SENDER_LIST_TOKEN_SENDING = "s";
- public static final String SENDER_LIST_TOKEN_SEND_FAILED = "f";
-
- /** Used for finding status in a cursor's extras. */
- public static final String EXTRA_STATUS = "status";
-
- public static final String RESPOND_INPUT_COMMAND = "command";
- public static final String COMMAND_RETRY = "retry";
- public static final String COMMAND_ACTIVATE = "activate";
- public static final String COMMAND_SET_VISIBLE = "setVisible";
- public static final String SET_VISIBLE_PARAM_VISIBLE = "visible";
- public static final String RESPOND_OUTPUT_COMMAND_RESPONSE = "commandResponse";
- public static final String COMMAND_RESPONSE_OK = "ok";
- public static final String COMMAND_RESPONSE_UNKNOWN = "unknownCommand";
-
- public static final String INSERT_PARAM_ATTACHMENT_ORIGIN = "origin";
- public static final String INSERT_PARAM_ATTACHMENT_ORIGIN_EXTRAS = "originExtras";
-
- private static final Pattern NAME_ADDRESS_PATTERN = Pattern.compile("\"(.*)\"");
- private static final Pattern UNNAMED_ADDRESS_PATTERN = Pattern.compile("([^<]+)@");
-
- private static final Map<Integer, Integer> sPriorityToLength = Maps.newHashMap();
- public static final SimpleStringSplitter sSenderListSplitter =
- new SimpleStringSplitter(SENDER_LIST_SEPARATOR);
- public static String[] sSenderFragments = new String[8];
-
- /**
- * Returns the name in an address string
- * @param addressString such as "bobby" <bob@example.com>
- * @return returns the quoted name in the addressString, otherwise the username from the email
- * address
- */
- public static String getNameFromAddressString(String addressString) {
- Matcher namedAddressMatch = NAME_ADDRESS_PATTERN.matcher(addressString);
- if (namedAddressMatch.find()) {
- String name = namedAddressMatch.group(1);
- if (name.length() > 0) return name;
- addressString =
- addressString.substring(namedAddressMatch.end(), addressString.length());
- }
-
- Matcher unnamedAddressMatch = UNNAMED_ADDRESS_PATTERN.matcher(addressString);
- if (unnamedAddressMatch.find()) {
- return unnamedAddressMatch.group(1);
- }
-
- return addressString;
- }
-
- /**
- * Returns the email address in an address string
- * @param addressString such as "bobby" <bob@example.com>
- * @return returns the email address, such as bob@example.com from the example above
- */
- public static String getEmailFromAddressString(String addressString) {
- String result = addressString;
- Matcher match = Patterns.EMAIL_ADDRESS.matcher(addressString);
- if (match.find()) {
- result = addressString.substring(match.start(), match.end());
- }
-
- return result;
- }
-
- /**
- * Returns whether the label is user-defined (versus system-defined labels such as inbox, whose
- * names start with "^").
- */
- public static boolean isLabelUserDefined(String label) {
- // TODO: label should never be empty so we should be able to say [label.charAt(0) != '^'].
- // However, it's a release week and I'm too scared to make that change.
- return !label.startsWith("^");
- }
-
- private static final Set<String> USER_SETTABLE_BUILTIN_LABELS = Sets.newHashSet(
- Gmail.LABEL_INBOX,
- Gmail.LABEL_UNREAD,
- Gmail.LABEL_TRASH,
- Gmail.LABEL_SPAM,
- Gmail.LABEL_STARRED,
- Gmail.LABEL_IGNORED);
-
- /**
- * Returns whether the label is user-settable. For example, labels such as LABEL_DRAFT should
- * only be set internally.
- */
- public static boolean isLabelUserSettable(String label) {
- return USER_SETTABLE_BUILTIN_LABELS.contains(label) || isLabelUserDefined(label);
- }
-
- /**
- * Returns the set of labels using the raw labels from a previous getRawLabels()
- * as input.
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public static Set<Long> getLabelIdsFromLabelIdsString(
- TextUtils.StringSplitter splitter) {
- Set<Long> labelIds = Sets.newHashSet();
- for (String labelIdString : splitter) {
- labelIds.add(Long.valueOf(labelIdString));
- }
- return labelIds;
- }
-
- /**
- * @deprecated remove when the activities stop using canonical names to identify labels
- */
- public static Set<String> getCanonicalNamesFromLabelIdsString(
- LabelMap labelMap, TextUtils.StringSplitter splitter) {
- Set<String> canonicalNames = Sets.newHashSet();
- for (long labelId : getLabelIdsFromLabelIdsString(splitter)) {
- final String canonicalName = labelMap.getCanonicalName(labelId);
- // We will sometimes see labels that the label map does not yet know about or that
- // do not have names yet.
- if (!TextUtils.isEmpty(canonicalName)) {
- canonicalNames.add(canonicalName);
- } else {
- Log.w(TAG, "getCanonicalNamesFromLabelIdsString skipping label id: " + labelId);
- }
- }
- return canonicalNames;
- }
-
- /**
- * @return a StringSplitter that is configured to split message label id strings
- */
- public static TextUtils.StringSplitter newMessageLabelIdsSplitter() {
- return new TextUtils.SimpleStringSplitter(SPACE_SEPARATOR);
- }
-
- /**
- * @return a StringSplitter that is configured to split conversation label id strings
- */
- public static TextUtils.StringSplitter newConversationLabelIdsSplitter() {
- return new CommaStringSplitter();
- }
-
- /**
- * A splitter for strings of the form described in the docs for COMMA_SEPARATOR.
- */
- private static class CommaStringSplitter extends TextUtils.SimpleStringSplitter {
-
- public CommaStringSplitter() {
- super(COMMA_SEPARATOR);
- }
-
- @Override
- public void setString(String string) {
- // The string should always be at least a single comma.
- super.setString(string.substring(1));
- }
- }
-
- /**
- * Creates a single string of the form that getLabelIdsFromLabelIdsString can split.
- */
- public static String getLabelIdsStringFromLabelIds(Set<Long> labelIds) {
- StringBuilder sb = new StringBuilder();
- sb.append(COMMA_SEPARATOR);
- for (Long labelId : labelIds) {
- sb.append(labelId);
- sb.append(COMMA_SEPARATOR);
- }
- return sb.toString();
- }
-
- public static final class ConversationColumns {
- public static final String ID = "_id";
- public static final String SUBJECT = "subject";
- public static final String SNIPPET = "snippet";
- public static final String FROM = "fromAddress";
- public static final String DATE = "date";
- public static final String PERSONAL_LEVEL = "personalLevel";
- /** A list of label names with a space after each one (including the last one). This makes
- * it easier remove individual labels from this list using SQL. */
- public static final String LABEL_IDS = "labelIds";
- public static final String NUM_MESSAGES = "numMessages";
- public static final String MAX_MESSAGE_ID = "maxMessageId";
- public static final String HAS_ATTACHMENTS = "hasAttachments";
- public static final String HAS_MESSAGES_WITH_ERRORS = "hasMessagesWithErrors";
- public static final String FORCE_ALL_UNREAD = "forceAllUnread";
-
- private ConversationColumns() {}
- }
-
- public static final class MessageColumns {
-
- public static final String ID = "_id";
- public static final String MESSAGE_ID = "messageId";
- public static final String CONVERSATION_ID = "conversation";
- public static final String SUBJECT = "subject";
- public static final String SNIPPET = "snippet";
- public static final String FROM = "fromAddress";
- public static final String TO = "toAddresses";
- public static final String CC = "ccAddresses";
- public static final String BCC = "bccAddresses";
- public static final String REPLY_TO = "replyToAddresses";
- public static final String DATE_SENT_MS = "dateSentMs";
- public static final String DATE_RECEIVED_MS = "dateReceivedMs";
- public static final String LIST_INFO = "listInfo";
- public static final String PERSONAL_LEVEL = "personalLevel";
- public static final String BODY = "body";
- public static final String EMBEDS_EXTERNAL_RESOURCES = "bodyEmbedsExternalResources";
- public static final String LABEL_IDS = "labelIds";
- public static final String JOINED_ATTACHMENT_INFOS = "joinedAttachmentInfos";
- public static final String ERROR = "error";
- // TODO: add a method for accessing this
- public static final String REF_MESSAGE_ID = "refMessageId";
-
- // Fake columns used only for saving or sending messages.
- public static final String FAKE_SAVE = "save";
- public static final String FAKE_REF_MESSAGE_ID = "refMessageId";
-
- private MessageColumns() {}
- }
-
- public static final class LabelColumns {
- public static final String CANONICAL_NAME = "canonicalName";
- public static final String NAME = "name";
- public static final String NUM_CONVERSATIONS = "numConversations";
- public static final String NUM_UNREAD_CONVERSATIONS =
- "numUnreadConversations";
-
- private LabelColumns() {}
- }
-
- public static final class SettingsColumns {
- public static final String LABELS_INCLUDED = "labelsIncluded";
- public static final String LABELS_PARTIAL = "labelsPartial";
- public static final String CONVERSATION_AGE_DAYS =
- "conversationAgeDays";
- public static final String MAX_ATTACHMENET_SIZE_MB =
- "maxAttachmentSize";
- }
-
- /**
- * These flags can be included as Selection Arguments when
- * querying the provider.
- */
- public static class SelectionArguments {
- private SelectionArguments() {
- // forbid instantiation
- }
-
- /**
- * Specifies that you do NOT wish the returned cursor to
- * become the Active Network Cursor. If you do not include
- * this flag as a selectionArg, the new cursor will become the
- * Active Network Cursor by default.
- */
- public static final String DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR =
- "SELECTION_ARGUMENT_DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR";
- }
-
- // These are the projections that we need when getting cursors from the
- // content provider.
- private static String[] CONVERSATION_PROJECTION = {
- ConversationColumns.ID,
- ConversationColumns.SUBJECT,
- ConversationColumns.SNIPPET,
- ConversationColumns.FROM,
- ConversationColumns.DATE,
- ConversationColumns.PERSONAL_LEVEL,
- ConversationColumns.LABEL_IDS,
- ConversationColumns.NUM_MESSAGES,
- ConversationColumns.MAX_MESSAGE_ID,
- ConversationColumns.HAS_ATTACHMENTS,
- ConversationColumns.HAS_MESSAGES_WITH_ERRORS,
- ConversationColumns.FORCE_ALL_UNREAD};
- private static String[] MESSAGE_PROJECTION = {
- MessageColumns.ID,
- MessageColumns.MESSAGE_ID,
- MessageColumns.CONVERSATION_ID,
- MessageColumns.SUBJECT,
- MessageColumns.SNIPPET,
- MessageColumns.FROM,
- MessageColumns.TO,
- MessageColumns.CC,
- MessageColumns.BCC,
- MessageColumns.REPLY_TO,
- MessageColumns.DATE_SENT_MS,
- MessageColumns.DATE_RECEIVED_MS,
- MessageColumns.LIST_INFO,
- MessageColumns.PERSONAL_LEVEL,
- MessageColumns.BODY,
- MessageColumns.EMBEDS_EXTERNAL_RESOURCES,
- MessageColumns.LABEL_IDS,
- MessageColumns.JOINED_ATTACHMENT_INFOS,
- MessageColumns.ERROR};
- private static String[] LABEL_PROJECTION = {
- BaseColumns._ID,
- LabelColumns.CANONICAL_NAME,
- LabelColumns.NAME,
- LabelColumns.NUM_CONVERSATIONS,
- LabelColumns.NUM_UNREAD_CONVERSATIONS};
- private static String[] SETTINGS_PROJECTION = {
- SettingsColumns.LABELS_INCLUDED,
- SettingsColumns.LABELS_PARTIAL,
- SettingsColumns.CONVERSATION_AGE_DAYS,
- SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
- };
-
- private ContentResolver mContentResolver;
-
- public Gmail(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
- }
-
- /**
- * Returns source if source is non-null. Returns the empty string otherwise.
- */
- private static String toNonnullString(String source) {
- if (source == null) {
- return "";
- } else {
- return source;
- }
- }
-
- /**
- * Behavior for a new cursor: should it become the Active Network
- * Cursor? This could potentially lead to bad behavior if someone
- * else is using the Active Network Cursor, since theirs will stop
- * being the Active Network Cursor.
- */
- public static enum BecomeActiveNetworkCursor {
- /**
- * The new cursor should become the one and only Active
- * Network Cursor. Any other cursor that might already be the
- * Active Network Cursor will cease to be so.
- */
- YES,
-
- /**
- * The new cursor should not become the Active Network
- * Cursor. Any other cursor that might already be the Active
- * Network Cursor will continue to be so.
- */
- NO
- }
-
- /**
- * Wraps a Cursor in a ConversationCursor
- *
- * @param account the account the cursor is associated with
- * @param cursor The Cursor to wrap
- * @return a new ConversationCursor
- */
- public ConversationCursor getConversationCursorForCursor(String account, Cursor cursor) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- return new ConversationCursor(this, account, cursor);
- }
-
- /**
- * Creates an array of SelectionArguments suitable for passing to the provider's query.
- * Currently this only handles one flag, but it could be expanded in the future.
- */
- private static String[] getSelectionArguments(
- BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- if (BecomeActiveNetworkCursor.NO == becomeActiveNetworkCursor) {
- return new String[] {SelectionArguments.DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR};
- } else {
- // Default behavior; no args required.
- return null;
- }
- }
-
- /**
- * Asynchronously gets a cursor over all conversations matching a query. The
- * query is in Gmail's query syntax. When the operation is complete the handler's
- * onQueryComplete() method is called with the resulting Cursor.
- *
- * @param account run the query on this account
- * @param handler An AsyncQueryHanlder that will be used to run the query
- * @param token The token to pass to startQuery, which will be passed back to onQueryComplete
- * @param query a query in Gmail's query syntax
- * @param becomeActiveNetworkCursor whether or not the returned
- * cursor should become the Active Network Cursor
- */
- public void runQueryForConversations(String account, AsyncQueryHandler handler, int token,
- String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
- handler.startQuery(token, null, Uri.withAppendedPath(CONVERSATIONS_URI, account),
- CONVERSATION_PROJECTION, query, selectionArgs, null);
- }
-
- /**
- * Synchronously gets a cursor over all conversations matching a query. The
- * query is in Gmail's query syntax.
- *
- * @param account run the query on this account
- * @param query a query in Gmail's query syntax
- * @param becomeActiveNetworkCursor whether or not the returned
- * cursor should become the Active Network Cursor
- */
- public ConversationCursor getConversationCursorForQuery(
- String account, String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
- Cursor cursor = mContentResolver.query(
- Uri.withAppendedPath(CONVERSATIONS_URI, account), CONVERSATION_PROJECTION,
- query, selectionArgs, null);
- return new ConversationCursor(this, account, cursor);
- }
-
- /**
- * Gets a message cursor over the single message with the given id.
- *
- * @param account get the cursor for messages in this account
- * @param messageId the id of the message
- * @return a cursor over the message
- */
- public MessageCursor getMessageCursorForMessageId(String account, long messageId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, null, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Gets a message cursor over the messages that match the query. Note that
- * this simply finds all of the messages that match and returns them. It
- * does not return all messages in conversations where any message matches.
- *
- * @param account get the cursor for messages in this account
- * @param query a query in GMail's query syntax. Currently only queries of
- * the form [label:<label>] are supported
- * @return a cursor over the messages
- */
- public MessageCursor getLocalMessageCursorForQuery(String account, String query) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
- Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, query, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Gets a cursor over all of the messages in a conversation.
- *
- * @param account get the cursor for messages in this account
- * @param conversationId the id of the converstion to fetch messages for
- * @return a cursor over messages in the conversation
- */
- public MessageCursor getMessageCursorForConversationId(String account, long conversationId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/messages");
- Cursor cursor = mContentResolver.query(
- uri, MESSAGE_PROJECTION, null, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Expunge the indicated message. One use of this is to discard drafts.
- *
- * @param account the account of the message id
- * @param messageId the id of the message to expunge
- */
- public void expungeMessage(String account, long messageId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- mContentResolver.delete(uri, null, null);
- }
-
- /**
- * Adds or removes the label on the conversation.
- *
- * @param account the account of the conversation
- * @param conversationId the conversation
- * @param maxServerMessageId the highest message id to whose labels should be changed. Note that
- * everywhere else in this file messageId means local message id but here you need to use a
- * server message id.
- * @param label the label to add or remove
- * @param add true to add the label, false to remove it
- */
- public void addOrRemoveLabelOnConversation(
- String account, long conversationId, long maxServerMessageId, String label,
- boolean add) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- if (add) {
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/labels");
- ContentValues values = new ContentValues();
- values.put(LabelColumns.CANONICAL_NAME, label);
- values.put(ConversationColumns.MAX_MESSAGE_ID, maxServerMessageId);
- mContentResolver.insert(uri, values);
- } else {
- String encodedLabel;
- try {
- encodedLabel = URLEncoder.encode(label, "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/"
- + conversationId + "/labels/" + encodedLabel);
- mContentResolver.delete(
- uri, ConversationColumns.MAX_MESSAGE_ID, new String[]{"" + maxServerMessageId});
- }
- }
-
- /**
- * Adds or removes the label on the message.
- *
- * @param contentResolver the content resolver.
- * @param account the account of the message
- * @param conversationId the conversation containing the message
- * @param messageId the id of the message to whose labels should be changed
- * @param label the label to add or remove
- * @param add true to add the label, false to remove it
- */
- public static void addOrRemoveLabelOnMessage(ContentResolver contentResolver, String account,
- long conversationId, long messageId, String label, boolean add) {
-
- // conversationId is unused but we want to start passing it whereever we pass a message id.
- if (add) {
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId + "/labels");
- ContentValues values = new ContentValues();
- values.put(LabelColumns.CANONICAL_NAME, label);
- contentResolver.insert(uri, values);
- } else {
- String encodedLabel;
- try {
- encodedLabel = URLEncoder.encode(label, "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId
- + "/labels/" + encodedLabel);
- contentResolver.delete(uri, null, null);
- }
- }
-
- /**
- * The mail provider will send an intent when certain changes happen in certain labels.
- * Currently those labels are inbox and voicemail.
- *
- * <p>The intent will have the action ACTION_PROVIDER_CHANGED and the extras mentioned below.
- * The data for the intent will be content://gmail-ls/unread/<name of label>.
- *
- * <p>The goal is to support the following user experience:<ul>
- * <li>When present the new mail indicator reports the number of unread conversations in the
- * inbox (or some other label).</li>
- * <li>When the user views the inbox the indicator is removed immediately. They do not have to
- * read all of the conversations.</li>
- * <li>If more mail arrives the indicator reappears and shows the total number of unread
- * conversations in the inbox.</li>
- * <li>If the user reads the new conversations on the web the indicator disappears on the
- * phone since there is no unread mail in the inbox that the user hasn't seen.</li>
- * <li>The phone should vibrate/etc when it transitions from having no unseen unread inbox
- * mail to having some.</li>
- */
-
- /** The account in which the change occurred. */
- static public final String PROVIDER_CHANGED_EXTRA_ACCOUNT = "account";
-
- /** The number of unread conversations matching the label. */
- static public final String PROVIDER_CHANGED_EXTRA_COUNT = "count";
-
- /** Whether to get the user's attention, perhaps by vibrating. */
- static public final String PROVIDER_CHANGED_EXTRA_GET_ATTENTION = "getAttention";
-
- /**
- * A label that is attached to all of the conversations being notified about. This enables the
- * receiver of a notification to get a list of matching conversations.
- */
- static public final String PROVIDER_CHANGED_EXTRA_TAG_LABEL = "tagLabel";
-
- /**
- * Settings for which conversations should be synced to the phone.
- * Conversations are synced if any message matches any of the following
- * criteria:
- *
- * <ul>
- * <li>the message has a label in the include set</li>
- * <li>the message is no older than conversationAgeDays and has a label in the partial set.
- * </li>
- * <li>also, pending changes on the server: the message has no user-controllable labels.</li>
- * </ul>
- *
- * <p>A user-controllable label is a user-defined label or star, inbox,
- * trash, spam, etc. LABEL_UNREAD is not considered user-controllable.
- */
- public static class Settings {
- public long conversationAgeDays;
- public long maxAttachmentSizeMb;
- public String[] labelsIncluded;
- public String[] labelsPartial;
- }
-
- /**
- * Returns the settings.
- * @param account the account whose setting should be retrieved
- */
- public Settings getSettings(String account) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Settings settings = new Settings();
- Cursor cursor = mContentResolver.query(
- Uri.withAppendedPath(SETTINGS_URI, account), SETTINGS_PROJECTION, null, null, null);
- cursor.moveToNext();
- settings.labelsIncluded = TextUtils.split(cursor.getString(0), SPACE_SEPARATOR_PATTERN);
- settings.labelsPartial = TextUtils.split(cursor.getString(1), SPACE_SEPARATOR_PATTERN);
- settings.conversationAgeDays = Long.parseLong(cursor.getString(2));
- settings.maxAttachmentSizeMb = Long.parseLong(cursor.getString(3));
- cursor.close();
- return settings;
- }
-
- /**
- * Sets the settings. A sync will be scheduled automatically.
- */
- public void setSettings(String account, Settings settings) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- ContentValues values = new ContentValues();
- values.put(
- SettingsColumns.LABELS_INCLUDED,
- TextUtils.join(" ", settings.labelsIncluded));
- values.put(
- SettingsColumns.LABELS_PARTIAL,
- TextUtils.join(" ", settings.labelsPartial));
- values.put(
- SettingsColumns.CONVERSATION_AGE_DAYS,
- settings.conversationAgeDays);
- values.put(
- SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
- settings.maxAttachmentSizeMb);
- mContentResolver.update(Uri.withAppendedPath(SETTINGS_URI, account), values, null, null);
- }
-
- /**
- * Uses sender instructions to build a formatted string.
- *
- * <p>Sender list instructions contain compact information about the sender list. Most work that
- * can be done without knowing how much room will be availble for the sender list is done when
- * creating the instructions.
- *
- * <p>The instructions string consists of tokens separated by SENDER_LIST_SEPARATOR. Here are
- * the tokens, one per line:<ul>
- * <li><tt>n</tt></li>
- * <li><em>int</em>, the number of non-draft messages in the conversation</li>
- * <li><tt>d</tt</li>
- * <li><em>int</em>, the number of drafts in the conversation</li>
- * <li><tt>l</tt></li>
- * <li><em>literal html to be included in the output</em></li>
- * <li><tt>s</tt> indicates that the message is sending (in the outbox without errors)</li>
- * <li><tt>f</tt> indicates that the message failed to send (in the outbox with errors)</li>
- * <li><em>for each message</em><ul>
- * <li><em>int</em>, 0 for read, 1 for unread</li>
- * <li><em>int</em>, the priority of the message. Zero is the most important</li>
- * <li><em>text</em>, the sender text or blank for messages from 'me'</li>
- * </ul></li>
- * <li><tt>e</tt> to indicate that one or more messages have been elided</li>
- *
- * <p>The instructions indicate how many messages and drafts are in the conversation and then
- * describe the most important messages in order, indicating the priority of each message and
- * whether the message is unread.
- *
- * @param instructions instructions as described above
- * @param sb the SpannableStringBuilder to append to
- * @param maxChars the number of characters available to display the text
- * @param unreadStyle the CharacterStyle for unread messages, or null
- * @param draftsStyle the CharacterStyle for draft messages, or null
- * @param sendingString the string to use when there are messages scheduled to be sent
- * @param sendFailedString the string to use when there are messages that mailed to send
- * @param meString the string to use for messages sent by this user
- * @param draftString the string to use for "Draft"
- * @param draftPluralString the string to use for "Drafts"
- */
- public static void getSenderSnippet(
- String instructions, SpannableStringBuilder sb, int maxChars,
- CharacterStyle unreadStyle,
- CharacterStyle draftsStyle,
- CharSequence meString, CharSequence draftString, CharSequence draftPluralString,
- CharSequence sendingString, CharSequence sendFailedString,
- boolean forceAllUnread, boolean forceAllRead) {
- assert !(forceAllUnread && forceAllRead);
- boolean unreadStatusIsForced = forceAllUnread || forceAllRead;
- boolean forcedUnreadStatus = forceAllUnread;
-
- // Measure each fragment. It's ok to iterate over the entire set of fragments because it is
- // never a long list, even if there are many senders.
- final Map<Integer, Integer> priorityToLength = sPriorityToLength;
- priorityToLength.clear();
-
- int maxFoundPriority = Integer.MIN_VALUE;
- int numMessages = 0;
- int numDrafts = 0;
- CharSequence draftsFragment = "";
- CharSequence sendingFragment = "";
- CharSequence sendFailedFragment = "";
-
- sSenderListSplitter.setString(instructions);
- int numFragments = 0;
- String[] fragments = sSenderFragments;
- int currentSize = fragments.length;
- while (sSenderListSplitter.hasNext()) {
- fragments[numFragments++] = sSenderListSplitter.next();
- if (numFragments == currentSize) {
- sSenderFragments = new String[2 * currentSize];
- System.arraycopy(fragments, 0, sSenderFragments, 0, currentSize);
- currentSize *= 2;
- fragments = sSenderFragments;
- }
- }
-
- for (int i = 0; i < numFragments;) {
- String fragment0 = fragments[i++];
- if ("".equals(fragment0)) {
- // This should be the final fragment.
- } else if (Gmail.SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
- // ignore
- } else if (Gmail.SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
- numMessages = Integer.valueOf(fragments[i++]);
- } else if (Gmail.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
- String numDraftsString = fragments[i++];
- numDrafts = Integer.parseInt(numDraftsString);
- draftsFragment = numDrafts == 1 ? draftString :
- draftPluralString + " (" + numDraftsString + ")";
- } else if (Gmail.SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) {
- sb.append(Html.fromHtml(fragments[i++]));
- return;
- } else if (Gmail.SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
- sendingFragment = sendingString;
- } else if (Gmail.SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
- sendFailedFragment = sendFailedString;
- } else {
- String priorityString = fragments[i++];
- CharSequence nameString = fragments[i++];
- if (nameString.length() == 0) nameString = meString;
- int priority = Integer.parseInt(priorityString);
- priorityToLength.put(priority, nameString.length());
- maxFoundPriority = Math.max(maxFoundPriority, priority);
- }
- }
- String numMessagesFragment =
- (numMessages != 0) ? " (" + Integer.toString(numMessages + numDrafts) + ")" : "";
-
- // Don't allocate fixedFragment unless we need it
- SpannableStringBuilder fixedFragment = null;
- int fixedFragmentLength = 0;
- if (draftsFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- fixedFragment.append(draftsFragment);
- if (draftsStyle != null) {
- fixedFragment.setSpan(
- CharacterStyle.wrap(draftsStyle),
- 0, fixedFragment.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- if (sendingFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- if (fixedFragment.length() != 0) fixedFragment.append(", ");
- fixedFragment.append(sendingFragment);
- }
- if (sendFailedFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- if (fixedFragment.length() != 0) fixedFragment.append(", ");
- fixedFragment.append(sendFailedFragment);
- }
-
- if (fixedFragment != null) {
- fixedFragmentLength = fixedFragment.length();
- }
-
- final boolean normalMessagesExist =
- numMessagesFragment.length() != 0 || maxFoundPriority != Integer.MIN_VALUE;
- String preFixedFragement = "";
- if (normalMessagesExist && fixedFragmentLength != 0) {
- preFixedFragement = ", ";
- }
- int maxPriorityToInclude = -1; // inclusive
- int numCharsUsed =
- numMessagesFragment.length() + preFixedFragement.length() + fixedFragmentLength;
- int numSendersUsed = 0;
- while (maxPriorityToInclude < maxFoundPriority) {
- if (priorityToLength.containsKey(maxPriorityToInclude + 1)) {
- int length = numCharsUsed + priorityToLength.get(maxPriorityToInclude + 1);
- if (numCharsUsed > 0) length += 2;
- // We must show at least two senders if they exist. If we don't have space for both
- // then we will truncate names.
- if (length > maxChars && numSendersUsed >= 2) {
- break;
- }
- numCharsUsed = length;
- numSendersUsed++;
- }
- maxPriorityToInclude++;
- }
-
- int numCharsToRemovePerWord = 0;
- if (numCharsUsed > maxChars) {
- numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed;
- }
-
- boolean elided = false;
- for (int i = 0; i < numFragments;) {
- String fragment0 = fragments[i++];
- if ("".equals(fragment0)) {
- // This should be the final fragment.
- } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
- elided = true;
- } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
- i++;
- } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
- i++;
- } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
- } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
- } else {
- final String unreadString = fragment0;
- final String priorityString = fragments[i++];
- String nameString = fragments[i++];
- if (nameString.length() == 0) nameString = meString.toString();
- if (numCharsToRemovePerWord != 0) {
- nameString = nameString.substring(
- 0, Math.max(nameString.length() - numCharsToRemovePerWord, 0));
- }
- final boolean unread = unreadStatusIsForced
- ? forcedUnreadStatus : Integer.parseInt(unreadString) != 0;
- final int priority = Integer.parseInt(priorityString);
- if (priority <= maxPriorityToInclude) {
- if (sb.length() != 0) {
- sb.append(elided ? " .. " : ", ");
- }
- elided = false;
- int pos = sb.length();
- sb.append(nameString);
- if (unread && unreadStyle != null) {
- sb.setSpan(CharacterStyle.wrap(unreadStyle),
- pos, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- } else {
- elided = true;
- }
- }
- }
- sb.append(numMessagesFragment);
- if (fixedFragmentLength != 0) {
- sb.append(preFixedFragement);
- sb.append(fixedFragment);
- }
- }
-
- /**
- * This is a cursor that only defines methods to move throught the results
- * and register to hear about changes. All access to the data is left to
- * subinterfaces.
- */
- public static class MailCursor extends ContentObserver {
-
- // A list of observers of this cursor.
- private Set<MailCursorObserver> mObservers;
-
- // Updated values are accumulated here before being written out if the
- // cursor is asked to persist the changes.
- private ContentValues mUpdateValues;
-
- protected Cursor mCursor;
- protected String mAccount;
-
- public Cursor getCursor() {
- return mCursor;
- }
-
- /**
- * Constructs the MailCursor given a regular cursor, registering as a
- * change observer of the cursor.
- * @param account the account the cursor is associated with
- * @param cursor the underlying cursor
- */
- protected MailCursor(String account, Cursor cursor) {
- super(new Handler());
- mObservers = new HashSet<MailCursorObserver>();
- mCursor = cursor;
- mAccount = account;
- if (mCursor != null) mCursor.registerContentObserver(this);
- }
-
- /**
- * Gets the account associated with this cursor.
- * @return the account.
- */
- public String getAccount() {
- return mAccount;
- }
-
- protected void checkThread() {
- // Turn this on when activity code no longer runs in the sync thread
- // after notifications of changes.
-// Thread currentThread = Thread.currentThread();
-// if (currentThread != mThread) {
-// throw new RuntimeException("Accessed from the wrong thread");
-// }
- }
-
- /**
- * Lazily constructs a map of update values to apply to the database
- * if requested. This map is cleared out when we move to a different
- * item in the result set.
- *
- * @return a map of values to be applied by an update.
- */
- protected ContentValues getUpdateValues() {
- if (mUpdateValues == null) {
- mUpdateValues = new ContentValues();
- }
- return mUpdateValues;
- }
-
- /**
- * Called whenever mCursor is changed to point to a different row.
- * Subclasses should override this if they need to clear out state
- * when this happens.
- *
- * Subclasses must call the inherited version if they override this.
- */
- protected void onCursorPositionChanged() {
- mUpdateValues = null;
- }
-
- // ********* MailCursor
-
- /**
- * Returns the numbers of rows in the cursor.
- *
- * @return the number of rows in the cursor.
- */
- final public int count() {
- if (mCursor != null) {
- return mCursor.getCount();
- } else {
- return 0;
- }
- }
-
- /**
- * @return the current position of this cursor, or -1 if this cursor
- * has not been initialized.
- */
- final public int position() {
- if (mCursor != null) {
- return mCursor.getPosition();
- } else {
- return -1;
- }
- }
-
- /**
- * Move the cursor to an absolute position. The valid
- * range of vaues is -1 <= position <= count.
- *
- * <p>This method will return true if the request destination was
- * reachable, otherwise it returns false.
- *
- * @param position the zero-based position to move to.
- * @return whether the requested move fully succeeded.
- */
- final public boolean moveTo(int position) {
- checkCursor();
- checkThread();
- boolean moved = mCursor.moveToPosition(position);
- if (moved) onCursorPositionChanged();
- return moved;
- }
-
- /**
- * Move the cursor to the next row.
- *
- * <p>This method will return false if the cursor is already past the
- * last entry in the result set.
- *
- * @return whether the move succeeded.
- */
- final public boolean next() {
- checkCursor();
- checkThread();
- boolean moved = mCursor.moveToNext();
- if (moved) onCursorPositionChanged();
- return moved;
- }
-
- /**
- * Release all resources and locks associated with the cursor. The
- * cursor will not be valid after this function is called.
- */
- final public void release() {
- if (mCursor != null) {
- mCursor.unregisterContentObserver(this);
- mCursor.deactivate();
- }
- }
-
- final public void registerContentObserver(ContentObserver observer) {
- mCursor.registerContentObserver(observer);
- }
-
- final public void unregisterContentObserver(ContentObserver observer) {
- mCursor.unregisterContentObserver(observer);
- }
-
- final public void registerDataSetObserver(DataSetObserver observer) {
- mCursor.registerDataSetObserver(observer);
- }
-
- final public void unregisterDataSetObserver(DataSetObserver observer) {
- mCursor.unregisterDataSetObserver(observer);
- }
-
- /**
- * Register an observer to hear about changes to the cursor.
- *
- * @param observer the observer to register
- */
- final public void registerObserver(MailCursorObserver observer) {
- mObservers.add(observer);
- }
-
- /**
- * Unregister an observer.
- *
- * @param observer the observer to unregister
- */
- final public void unregisterObserver(MailCursorObserver observer) {
- mObservers.remove(observer);
- }
-
- // ********* ContentObserver
-
- @Override
- final public boolean deliverSelfNotifications() {
- return false;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- if (DEBUG) {
- Log.d(TAG, "MailCursor is notifying " + mObservers.size() + " observers");
- }
- for (MailCursorObserver o: mObservers) {
- o.onCursorChanged(this);
- }
- }
-
- protected void checkCursor() {
- if (mCursor == null) {
- throw new IllegalStateException(
- "cannot read from an insertion cursor");
- }
- }
-
- /**
- * Returns the string value of the column, or "" if the value is null.
- */
- protected String getStringInColumn(int columnIndex) {
- checkCursor();
- return toNonnullString(mCursor.getString(columnIndex));
- }
- }
-
- /**
- * A MailCursor observer is notified of changes to the result set of a
- * cursor.
- */
- public interface MailCursorObserver {
-
- /**
- * Called when the result set of a cursor has changed.
- *
- * @param cursor the cursor whose result set has changed.
- */
- void onCursorChanged(MailCursor cursor);
- }
-
- /**
- * A cursor over labels.
- */
- public final class LabelCursor extends MailCursor {
-
- private int mNameIndex;
- private int mNumConversationsIndex;
- private int mNumUnreadConversationsIndex;
-
- private LabelCursor(String account, Cursor cursor) {
- super(account, cursor);
-
- mNameIndex = mCursor.getColumnIndexOrThrow(LabelColumns.CANONICAL_NAME);
- mNumConversationsIndex =
- mCursor.getColumnIndexOrThrow(LabelColumns.NUM_CONVERSATIONS);
- mNumUnreadConversationsIndex = mCursor.getColumnIndexOrThrow(
- LabelColumns.NUM_UNREAD_CONVERSATIONS);
- }
-
- /**
- * Gets the canonical name of the current label.
- *
- * @return the current label's name.
- */
- public String getName() {
- return getStringInColumn(mNameIndex);
- }
-
- /**
- * Gets the number of conversations with this label.
- *
- * @return the number of conversations with this label.
- */
- public int getNumConversations() {
- return mCursor.getInt(mNumConversationsIndex);
- }
-
- /**
- * Gets the number of unread conversations with this label.
- *
- * @return the number of unread conversations with this label.
- */
- public int getNumUnreadConversations() {
- return mCursor.getInt(mNumUnreadConversationsIndex);
- }
- }
-
- /**
- * This is a map of labels. TODO: make it observable.
- */
- public static final class LabelMap extends Observable {
- private final static ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
-
- private ContentQueryMap mQueryMap;
- private SortedSet<String> mSortedUserLabels;
- private Map<String, Long> mCanonicalNameToId;
-
- private long mLabelIdSent;
- private long mLabelIdInbox;
- private long mLabelIdDraft;
- private long mLabelIdUnread;
- private long mLabelIdTrash;
- private long mLabelIdSpam;
- private long mLabelIdStarred;
- private long mLabelIdChat;
- private long mLabelIdVoicemail;
- private long mLabelIdIgnored;
- private long mLabelIdVoicemailInbox;
- private long mLabelIdCached;
- private long mLabelIdOutbox;
-
- private boolean mLabelsSynced = false;
-
- public LabelMap(ContentResolver contentResolver, String account, boolean keepUpdated) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Cursor cursor = contentResolver.query(
- Uri.withAppendedPath(LABELS_URI, account), LABEL_PROJECTION, null, null, null);
- init(cursor, keepUpdated);
- }
-
- public LabelMap(Cursor cursor, boolean keepUpdated) {
- init(cursor, keepUpdated);
- }
-
- private void init(Cursor cursor, boolean keepUpdated) {
- mQueryMap = new ContentQueryMap(cursor, BaseColumns._ID, keepUpdated, null);
- mSortedUserLabels = new TreeSet<String>(java.text.Collator.getInstance());
- mCanonicalNameToId = Maps.newHashMap();
- updateDataStructures();
- mQueryMap.addObserver(new Observer() {
- public void update(Observable observable, Object data) {
- updateDataStructures();
- setChanged();
- notifyObservers();
- }
- });
- }
-
- /**
- * @return whether at least some labels have been synced.
- */
- public boolean labelsSynced() {
- return mLabelsSynced;
- }
-
- /**
- * Updates the data structures that are maintained separately from mQueryMap after the query
- * map has changed.
- */
- private void updateDataStructures() {
- mSortedUserLabels.clear();
- mCanonicalNameToId.clear();
- for (Map.Entry<String, ContentValues> row : mQueryMap.getRows().entrySet()) {
- long labelId = Long.valueOf(row.getKey());
- String canonicalName = row.getValue().getAsString(LabelColumns.CANONICAL_NAME);
- if (isLabelUserDefined(canonicalName)) {
- mSortedUserLabels.add(canonicalName);
- }
- mCanonicalNameToId.put(canonicalName, labelId);
-
- if (LABEL_SENT.equals(canonicalName)) {
- mLabelIdSent = labelId;
- } else if (LABEL_INBOX.equals(canonicalName)) {
- mLabelIdInbox = labelId;
- } else if (LABEL_DRAFT.equals(canonicalName)) {
- mLabelIdDraft = labelId;
- } else if (LABEL_UNREAD.equals(canonicalName)) {
- mLabelIdUnread = labelId;
- } else if (LABEL_TRASH.equals(canonicalName)) {
- mLabelIdTrash = labelId;
- } else if (LABEL_SPAM.equals(canonicalName)) {
- mLabelIdSpam = labelId;
- } else if (LABEL_STARRED.equals(canonicalName)) {
- mLabelIdStarred = labelId;
- } else if (LABEL_CHAT.equals(canonicalName)) {
- mLabelIdChat = labelId;
- } else if (LABEL_IGNORED.equals(canonicalName)) {
- mLabelIdIgnored = labelId;
- } else if (LABEL_VOICEMAIL.equals(canonicalName)) {
- mLabelIdVoicemail = labelId;
- } else if (LABEL_VOICEMAIL_INBOX.equals(canonicalName)) {
- mLabelIdVoicemailInbox = labelId;
- } else if (LABEL_CACHED.equals(canonicalName)) {
- mLabelIdCached = labelId;
- } else if (LABEL_OUTBOX.equals(canonicalName)) {
- mLabelIdOutbox = labelId;
- }
- mLabelsSynced = mLabelIdSent != 0
- && mLabelIdInbox != 0
- && mLabelIdDraft != 0
- && mLabelIdUnread != 0
- && mLabelIdTrash != 0
- && mLabelIdSpam != 0
- && mLabelIdStarred != 0
- && mLabelIdChat != 0
- && mLabelIdIgnored != 0
- && mLabelIdVoicemail != 0;
- }
- }
-
- public long getLabelIdSent() {
- checkLabelsSynced();
- return mLabelIdSent;
- }
-
- public long getLabelIdInbox() {
- checkLabelsSynced();
- return mLabelIdInbox;
- }
-
- public long getLabelIdDraft() {
- checkLabelsSynced();
- return mLabelIdDraft;
- }
-
- public long getLabelIdUnread() {
- checkLabelsSynced();
- return mLabelIdUnread;
- }
-
- public long getLabelIdTrash() {
- checkLabelsSynced();
- return mLabelIdTrash;
- }
-
- public long getLabelIdSpam() {
- checkLabelsSynced();
- return mLabelIdSpam;
- }
-
- public long getLabelIdStarred() {
- checkLabelsSynced();
- return mLabelIdStarred;
- }
-
- public long getLabelIdChat() {
- checkLabelsSynced();
- return mLabelIdChat;
- }
-
- public long getLabelIdIgnored() {
- checkLabelsSynced();
- return mLabelIdIgnored;
- }
-
- public long getLabelIdVoicemail() {
- checkLabelsSynced();
- return mLabelIdVoicemail;
- }
-
- public long getLabelIdVoicemailInbox() {
- checkLabelsSynced();
- return mLabelIdVoicemailInbox;
- }
-
- public long getLabelIdCached() {
- checkLabelsSynced();
- return mLabelIdCached;
- }
-
- public long getLabelIdOutbox() {
- checkLabelsSynced();
- return mLabelIdOutbox;
- }
-
- private void checkLabelsSynced() {
- if (!labelsSynced()) {
- throw new IllegalStateException("LabelMap not initalized");
- }
- }
-
- /** Returns the list of user-defined labels in alphabetical order. */
- public SortedSet<String> getSortedUserLabels() {
- return mSortedUserLabels;
- }
-
- private static final List<String> SORTED_USER_MEANINGFUL_SYSTEM_LABELS =
- Lists.newArrayList(
- LABEL_INBOX, LABEL_STARRED, LABEL_CHAT, LABEL_SENT,
- LABEL_OUTBOX, LABEL_DRAFT, LABEL_ALL,
- LABEL_SPAM, LABEL_TRASH);
-
-
- private static final Set<String> USER_MEANINGFUL_SYSTEM_LABELS_SET =
- Sets.newHashSet(
- SORTED_USER_MEANINGFUL_SYSTEM_LABELS.toArray(
- new String[]{}));
-
- public static List<String> getSortedUserMeaningfulSystemLabels() {
- return SORTED_USER_MEANINGFUL_SYSTEM_LABELS;
- }
-
- public static Set<String> getUserMeaningfulSystemLabelsSet() {
- return USER_MEANINGFUL_SYSTEM_LABELS_SET;
- }
-
- /**
- * If you are ever tempted to remove outbox or draft from this set make sure you have a
- * way to stop draft and outbox messages from getting purged before they are sent to the
- * server.
- */
- private static final Set<String> FORCED_INCLUDED_LABELS =
- Sets.newHashSet(LABEL_OUTBOX, LABEL_DRAFT);
-
- public static Set<String> getForcedIncludedLabels() {
- return FORCED_INCLUDED_LABELS;
- }
-
- private static final Set<String> FORCED_INCLUDED_OR_PARTIAL_LABELS =
- Sets.newHashSet(LABEL_INBOX);
-
- public static Set<String> getForcedIncludedOrPartialLabels() {
- return FORCED_INCLUDED_OR_PARTIAL_LABELS;
- }
-
- private static final Set<String> FORCED_UNSYNCED_LABELS =
- Sets.newHashSet(LABEL_ALL, LABEL_CHAT, LABEL_SPAM, LABEL_TRASH);
-
- public static Set<String> getForcedUnsyncedLabels() {
- return FORCED_UNSYNCED_LABELS;
- }
-
- /**
- * Returns the number of conversation with a given label.
- * @deprecated Use {@link #getLabelId} instead.
- */
- @Deprecated
- public int getNumConversations(String label) {
- return getNumConversations(getLabelId(label));
- }
-
- /** Returns the number of conversation with a given label. */
- public int getNumConversations(long labelId) {
- return getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_CONVERSATIONS);
- }
-
- /**
- * Returns the number of unread conversation with a given label.
- * @deprecated Use {@link #getLabelId} instead.
- */
- @Deprecated
- public int getNumUnreadConversations(String label) {
- return getNumUnreadConversations(getLabelId(label));
- }
-
- /** Returns the number of unread conversation with a given label. */
- public int getNumUnreadConversations(long labelId) {
- Integer unreadConversations =
- getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_UNREAD_CONVERSATIONS);
- // There seems to be a race condition here that can get the label maps into a bad
- // state and lose state on a particular label.
- int result = 0;
- if (unreadConversations != null) {
- result = unreadConversations < 0 ? 0 : unreadConversations;
- }
-
- return result;
- }
-
- /**
- * @return the canonical name for a label
- */
- public String getCanonicalName(long labelId) {
- return getLabelIdValues(labelId).getAsString(LabelColumns.CANONICAL_NAME);
- }
-
- /**
- * @return the human name for a label
- */
- public String getName(long labelId) {
- return getLabelIdValues(labelId).getAsString(LabelColumns.NAME);
- }
-
- /**
- * @return whether a given label is known
- */
- public boolean hasLabel(long labelId) {
- return mQueryMap.getRows().containsKey(Long.toString(labelId));
- }
-
- /**
- * @return returns the id of a label given the canonical name
- * @deprecated this is only needed because most of the UI uses label names instead of ids
- */
- public long getLabelId(String canonicalName) {
- if (mCanonicalNameToId.containsKey(canonicalName)) {
- return mCanonicalNameToId.get(canonicalName);
- } else {
- throw new IllegalArgumentException("Unknown canonical name: " + canonicalName);
- }
- }
-
- private ContentValues getLabelIdValues(long labelId) {
- final ContentValues values = mQueryMap.getValues(Long.toString(labelId));
- if (values != null) {
- return values;
- } else {
- return EMPTY_CONTENT_VALUES;
- }
- }
-
- /** Force the map to requery. This should not be necessary outside tests. */
- public void requery() {
- mQueryMap.requery();
- }
-
- public void close() {
- mQueryMap.close();
- }
- }
-
- private Map<String, Gmail.LabelMap> mLabelMaps = Maps.newHashMap();
-
- public LabelMap getLabelMap(String account) {
- Gmail.LabelMap labelMap = mLabelMaps.get(account);
- if (labelMap == null) {
- labelMap = new Gmail.LabelMap(mContentResolver, account, true /* keepUpdated */);
- mLabelMaps.put(account, labelMap);
- }
- return labelMap;
- }
-
- public enum PersonalLevel {
- NOT_TO_ME(0),
- TO_ME_AND_OTHERS(1),
- ONLY_TO_ME(2);
-
- private int mLevel;
-
- PersonalLevel(int level) {
- mLevel = level;
- }
-
- public int toInt() {
- return mLevel;
- }
-
- public static PersonalLevel fromInt(int level) {
- switch (level) {
- case 0: return NOT_TO_ME;
- case 1: return TO_ME_AND_OTHERS;
- case 2: return ONLY_TO_ME;
- default:
- throw new IllegalArgumentException(
- level + " is not a personal level");
- }
- }
- }
-
- /**
- * Indicates a version of an attachment.
- */
- public enum AttachmentRendition {
- /**
- * The full version of an attachment if it can be handled on the device, otherwise the
- * preview.
- */
- BEST,
-
- /** A smaller or simpler version of the attachment, such as a scaled-down image or an HTML
- * version of a document. Not always available.
- */
- SIMPLE,
- }
-
- /**
- * The columns that can be requested when querying an attachment's download URI. See
- * getAttachmentDownloadUri.
- */
- public static final class AttachmentColumns implements BaseColumns {
-
- /** Contains a STATUS value from {@link android.provider.Downloads} */
- public static final String STATUS = "status";
-
- /**
- * The name of the file to open (with ContentProvider.open). If this is empty then continue
- * to use the attachment's URI.
- *
- * TODO: I'm not sure that we need this. See the note in CL 66853-p9.
- */
- public static final String FILENAME = "filename";
- }
-
- /**
- * We track where an attachment came from so that we know how to download it and include it
- * in new messages.
- */
- public enum AttachmentOrigin {
- /** Extras are "<conversationId>-<messageId>-<partId>". */
- SERVER_ATTACHMENT,
- /** Extras are "<path>". */
- LOCAL_FILE;
-
- private static final String SERVER_EXTRAS_SEPARATOR = "_";
-
- public static String serverExtras(
- long conversationId, long messageId, String partId) {
- return conversationId + SERVER_EXTRAS_SEPARATOR
- + messageId + SERVER_EXTRAS_SEPARATOR + partId;
- }
-
- /**
- * @param extras extras as returned by serverExtras
- * @return an array of conversationId, messageId, partId (all as strings)
- */
- public static String[] splitServerExtras(String extras) {
- return TextUtils.split(extras, SERVER_EXTRAS_SEPARATOR);
- }
-
- public static String localFileExtras(Uri path) {
- return path.toString();
- }
- }
-
- public static final class Attachment {
- /** Identifies the attachment uniquely when combined wih a message id.*/
- public String partId;
-
- /** The intended filename of the attachment.*/
- public String name;
-
- /** The native content type.*/
- public String contentType;
-
- /** The size of the attachment in its native form.*/
- public int size;
-
- /**
- * The content type of the simple version of the attachment. Blank if no simple version is
- * available.
- */
- public String simpleContentType;
-
- public AttachmentOrigin origin;
-
- public String originExtras;
-
- public String toJoinedString() {
- return TextUtils.join(
- "|", Lists.newArrayList(partId == null ? "" : partId,
- name.replace("|", ""), contentType,
- size, simpleContentType,
- origin.toString(), originExtras));
- }
-
- public static Attachment parseJoinedString(String joinedString) {
- String[] fragments = TextUtils.split(joinedString, "\\|");
- int i = 0;
- Attachment attachment = new Attachment();
- attachment.partId = fragments[i++];
- if (TextUtils.isEmpty(attachment.partId)) {
- attachment.partId = null;
- }
- attachment.name = fragments[i++];
- attachment.contentType = fragments[i++];
- attachment.size = Integer.parseInt(fragments[i++]);
- attachment.simpleContentType = fragments[i++];
- attachment.origin = AttachmentOrigin.valueOf(fragments[i++]);
- attachment.originExtras = fragments[i++];
- return attachment;
- }
- }
-
- /**
- * Any given attachment can come in two different renditions (see
- * {@link android.provider.Gmail.AttachmentRendition}) and can be saved to the sd card or to a
- * cache. The gmail provider automatically syncs some attachments to the cache. Other
- * attachments can be downloaded on demand. Attachments in the cache will be purged as needed to
- * save space. Attachments on the SD card must be managed by the user or other software.
- *
- * @param account which account to use
- * @param messageId the id of the mesage with the attachment
- * @param attachment the attachment
- * @param rendition the desired rendition
- * @param saveToSd whether the attachment should be saved to (or loaded from) the sd card or
- * @return the URI to ask the content provider to open in order to open an attachment.
- */
- public static Uri getAttachmentUri(
- String account, long messageId, Attachment attachment,
- AttachmentRendition rendition, boolean saveToSd) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- if (attachment.origin == AttachmentOrigin.LOCAL_FILE) {
- return Uri.parse(attachment.originExtras);
- } else {
- return Uri.parse(
- AUTHORITY_PLUS_MESSAGES).buildUpon()
- .appendPath(account).appendPath(Long.toString(messageId))
- .appendPath("attachments").appendPath(attachment.partId)
- .appendPath(rendition.toString())
- .appendPath(Boolean.toString(saveToSd))
- .build();
- }
- }
-
- /**
- * Return the URI to query in order to find out whether an attachment is downloaded.
- *
- * <p>Querying this will also start a download if necessary. The cursor returned by querying
- * this URI can contain the columns in {@link android.provider.Gmail.AttachmentColumns}.
- *
- * <p>Deleting this URI will cancel the download if it was not started automatically by the
- * provider. It will also remove bookkeeping for saveToSd downloads.
- *
- * @param attachmentUri the attachment URI as returned by getAttachmentUri. The URI's authority
- * Gmail.AUTHORITY. If it is not then you should open the file directly.
- */
- public static Uri getAttachmentDownloadUri(Uri attachmentUri) {
- if (!"content".equals(attachmentUri.getScheme())) {
- throw new IllegalArgumentException("Uri's scheme must be 'content': " + attachmentUri);
- }
- return attachmentUri.buildUpon().appendPath("download").build();
- }
-
- public enum CursorStatus {
- LOADED,
- LOADING,
- ERROR, // A network error occurred.
- }
-
- /**
- * A cursor over messages.
- */
- public static final class MessageCursor extends MailCursor {
-
- private LabelMap mLabelMap;
-
- private ContentResolver mContentResolver;
-
- /**
- * Only valid if mCursor == null, in which case we are inserting a new
- * message.
- */
- long mInReplyToLocalMessageId;
- boolean mPreserveAttachments;
-
- private int mIdIndex;
- private int mConversationIdIndex;
- private int mSubjectIndex;
- private int mSnippetIndex;
- private int mFromIndex;
- private int mToIndex;
- private int mCcIndex;
- private int mBccIndex;
- private int mReplyToIndex;
- private int mDateSentMsIndex;
- private int mDateReceivedMsIndex;
- private int mListInfoIndex;
- private int mPersonalLevelIndex;
- private int mBodyIndex;
- private int mBodyEmbedsExternalResourcesIndex;
- private int mLabelIdsIndex;
- private int mJoinedAttachmentInfosIndex;
- private int mErrorIndex;
-
- private TextUtils.StringSplitter mLabelIdsSplitter = newMessageLabelIdsSplitter();
-
- public MessageCursor(Gmail gmail, ContentResolver cr, String account, Cursor cursor) {
- super(account, cursor);
- mLabelMap = gmail.getLabelMap(account);
- if (cursor == null) {
- throw new IllegalArgumentException(
- "null cursor passed to MessageCursor()");
- }
-
- mContentResolver = cr;
-
- mIdIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ID);
- mConversationIdIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.CONVERSATION_ID);
- mSubjectIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SUBJECT);
- mSnippetIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SNIPPET);
- mFromIndex = mCursor.getColumnIndexOrThrow(MessageColumns.FROM);
- mToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.TO);
- mCcIndex = mCursor.getColumnIndexOrThrow(MessageColumns.CC);
- mBccIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BCC);
- mReplyToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.REPLY_TO);
- mDateSentMsIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.DATE_SENT_MS);
- mDateReceivedMsIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.DATE_RECEIVED_MS);
- mListInfoIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LIST_INFO);
- mPersonalLevelIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.PERSONAL_LEVEL);
- mBodyIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BODY);
- mBodyEmbedsExternalResourcesIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.EMBEDS_EXTERNAL_RESOURCES);
- mLabelIdsIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LABEL_IDS);
- mJoinedAttachmentInfosIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.JOINED_ATTACHMENT_INFOS);
- mErrorIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ERROR);
-
- mInReplyToLocalMessageId = 0;
- mPreserveAttachments = false;
- }
-
- protected MessageCursor(ContentResolver cr, String account, long inReplyToMessageId,
- boolean preserveAttachments) {
- super(account, null);
- mContentResolver = cr;
- mInReplyToLocalMessageId = inReplyToMessageId;
- mPreserveAttachments = preserveAttachments;
- }
-
- @Override
- protected void onCursorPositionChanged() {
- super.onCursorPositionChanged();
- }
-
- public CursorStatus getStatus() {
- Bundle extras = mCursor.getExtras();
- String stringStatus = extras.getString(EXTRA_STATUS);
- return CursorStatus.valueOf(stringStatus);
- }
-
- /** Retry a network request after errors. */
- public void retry() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Gets the message id of the current message. Note that this is an
- * immutable local message (not, for example, GMail's message id, which
- * is immutable).
- *
- * @return the message's id
- */
- public long getMessageId() {
- checkCursor();
- return mCursor.getLong(mIdIndex);
- }
-
- /**
- * Gets the message's conversation id. This must be immutable. (For
- * example, with GMail this should be the original conversation id
- * rather than the default notion of converation id.)
- *
- * @return the message's conversation id
- */
- public long getConversationId() {
- checkCursor();
- return mCursor.getLong(mConversationIdIndex);
- }
-
- /**
- * Gets the message's subject.
- *
- * @return the message's subject
- */
- public String getSubject() {
- return getStringInColumn(mSubjectIndex);
- }
-
- /**
- * Gets the message's snippet (the short piece of the body). The snippet
- * is generated from the body and cannot be set directly.
- *
- * @return the message's snippet
- */
- public String getSnippet() {
- return getStringInColumn(mSnippetIndex);
- }
-
- /**
- * Gets the message's from address.
- *
- * @return the message's from address
- */
- public String getFromAddress() {
- return getStringInColumn(mFromIndex);
- }
-
- /**
- * Returns the addresses for the key, if it has been updated, or index otherwise.
- */
- private String[] getAddresses(String key, int index) {
- ContentValues updated = getUpdateValues();
- String addresses;
- if (updated.containsKey(key)) {
- addresses = (String)getUpdateValues().get(key);
- } else {
- addresses = getStringInColumn(index);
- }
-
- return TextUtils.split(addresses, EMAIL_SEPARATOR_PATTERN);
- }
-
- /**
- * Gets the message's to addresses.
- * @return the message's to addresses
- */
- public String[] getToAddresses() {
- return getAddresses(MessageColumns.TO, mToIndex);
- }
-
- /**
- * Gets the message's cc addresses.
- * @return the message's cc addresses
- */
- public String[] getCcAddresses() {
- return getAddresses(MessageColumns.CC, mCcIndex);
- }
-
- /**
- * Gets the message's bcc addresses.
- * @return the message's bcc addresses
- */
- public String[] getBccAddresses() {
- return getAddresses(MessageColumns.BCC, mBccIndex);
- }
-
- /**
- * Gets the message's replyTo address.
- *
- * @return the message's replyTo address
- */
- public String[] getReplyToAddress() {
- return TextUtils.split(getStringInColumn(mReplyToIndex), EMAIL_SEPARATOR_PATTERN);
- }
-
- public long getDateSentMs() {
- checkCursor();
- return mCursor.getLong(mDateSentMsIndex);
- }
-
- public long getDateReceivedMs() {
- checkCursor();
- return mCursor.getLong(mDateReceivedMsIndex);
- }
-
- public String getListInfo() {
- return getStringInColumn(mListInfoIndex);
- }
-
- public PersonalLevel getPersonalLevel() {
- checkCursor();
- int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
- return PersonalLevel.fromInt(personalLevelInt);
- }
-
- /**
- * @deprecated Always returns true.
- */
- @Deprecated
- public boolean getExpanded() {
- return true;
- }
-
- /**
- * Gets the message's body.
- *
- * @return the message's body
- */
- public String getBody() {
- return getStringInColumn(mBodyIndex);
- }
-
- /**
- * @return whether the message's body contains embedded references to external resources. In
- * that case the resources should only be displayed if the user explicitly asks for them to
- * be
- */
- public boolean getBodyEmbedsExternalResources() {
- checkCursor();
- return mCursor.getInt(mBodyEmbedsExternalResourcesIndex) != 0;
- }
-
- /**
- * @return a copy of the set of label ids
- */
- public Set<Long> getLabelIds() {
- String labelNames = mCursor.getString(mLabelIdsIndex);
- mLabelIdsSplitter.setString(labelNames);
- return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
- }
-
- /**
- * @return a joined string of labels separated by spaces.
- */
- public String getRawLabelIds() {
- return mCursor.getString(mLabelIdsIndex);
- }
-
- /**
- * Adds a label to a message (if add is true) or removes it (if add is
- * false).
- *
- * @param label the label to add or remove
- * @param add whether to add or remove the label
- */
- public void addOrRemoveLabel(String label, boolean add) {
- addOrRemoveLabelOnMessage(mContentResolver, mAccount, getConversationId(),
- getMessageId(), label, add);
- }
-
- public ArrayList<Attachment> getAttachmentInfos() {
- ArrayList<Attachment> attachments = Lists.newArrayList();
-
- String joinedAttachmentInfos = mCursor.getString(mJoinedAttachmentInfosIndex);
- if (joinedAttachmentInfos != null) {
- for (String joinedAttachmentInfo :
- TextUtils.split(joinedAttachmentInfos, ATTACHMENT_INFO_SEPARATOR_PATTERN)) {
-
- Attachment attachment = Attachment.parseJoinedString(joinedAttachmentInfo);
- attachments.add(attachment);
- }
- }
- return attachments;
- }
-
- /**
- * @return the error text for the message. Error text gets set if the server rejects a
- * message that we try to save or send. If there is error text then the message is no longer
- * scheduled to be saved or sent. Calling save() or send() will clear any error as well as
- * scheduling another atempt to save or send the message.
- */
- public String getErrorText() {
- return mCursor.getString(mErrorIndex);
- }
- }
-
- /**
- * A helper class for creating or updating messags. Use the putXxx methods to provide initial or
- * new values for the message. Then save or send the message. To save or send an existing
- * message without making other changes to it simply provide an emty ContentValues.
- */
- public static class MessageModification {
-
- /**
- * Sets the message's subject. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param subject the new subject
- */
- public static void putSubject(ContentValues values, String subject) {
- values.put(MessageColumns.SUBJECT, subject);
- }
-
- /**
- * Sets the message's to address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param toAddresses the new to addresses
- */
- public static void putToAddresses(ContentValues values, String[] toAddresses) {
- values.put(MessageColumns.TO, TextUtils.join(EMAIL_SEPARATOR, toAddresses));
- }
-
- /**
- * Sets the message's cc address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param ccAddresses the new cc addresses
- */
- public static void putCcAddresses(ContentValues values, String[] ccAddresses) {
- values.put(MessageColumns.CC, TextUtils.join(EMAIL_SEPARATOR, ccAddresses));
- }
-
- /**
- * Sets the message's bcc address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param bccAddresses the new bcc addresses
- */
- public static void putBccAddresses(ContentValues values, String[] bccAddresses) {
- values.put(MessageColumns.BCC, TextUtils.join(EMAIL_SEPARATOR, bccAddresses));
- }
-
- /**
- * Saves a new body for the message. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param body the new body of the message
- */
- public static void putBody(ContentValues values, String body) {
- values.put(MessageColumns.BODY, body);
- }
-
- /**
- * Sets the attachments on a message. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param attachments
- */
- public static void putAttachments(ContentValues values, List<Attachment> attachments) {
- values.put(
- MessageColumns.JOINED_ATTACHMENT_INFOS, joinedAttachmentsString(attachments));
- }
-
- /**
- * Create a new message and save it as a draft or send it.
- *
- * @param contentResolver the content resolver to use
- * @param account the account to use
- * @param values the values for the new message
- * @param refMessageId the message that is being replied to or forwarded
- * @param save whether to save or send the message
- * @return the id of the new message
- */
- public static long sendOrSaveNewMessage(
- ContentResolver contentResolver, String account,
- ContentValues values, long refMessageId, boolean save) {
- values.put(MessageColumns.FAKE_SAVE, save);
- values.put(MessageColumns.FAKE_REF_MESSAGE_ID, refMessageId);
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
- Uri result = contentResolver.insert(uri, values);
- return ContentUris.parseId(result);
- }
-
- /**
- * Update an existing draft and save it as a new draft or send it.
- *
- * @param contentResolver the content resolver to use
- * @param account the account to use
- * @param messageId the id of the message to update
- * @param updateValues the values to change. Unspecified fields will not be altered
- * @param save whether to resave the message as a draft or send it
- */
- public static void sendOrSaveExistingMessage(
- ContentResolver contentResolver, String account, long messageId,
- ContentValues updateValues, boolean save) {
- updateValues.put(MessageColumns.FAKE_SAVE, save);
- updateValues.put(MessageColumns.FAKE_REF_MESSAGE_ID, 0);
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- contentResolver.update(uri, updateValues, null, null);
- }
-
- /**
- * The string produced here is parsed by Gmail.MessageCursor#getAttachmentInfos.
- */
- public static String joinedAttachmentsString(List<Gmail.Attachment> attachments) {
- StringBuilder attachmentsSb = new StringBuilder();
- for (Gmail.Attachment attachment : attachments) {
- if (attachmentsSb.length() != 0) {
- attachmentsSb.append(Gmail.ATTACHMENT_INFO_SEPARATOR);
- }
- attachmentsSb.append(attachment.toJoinedString());
- }
- return attachmentsSb.toString();
- }
-
- }
-
- /**
- * A cursor over conversations.
- *
- * "Conversation" refers to the information needed to populate a list of
- * conversations, not all of the messages in a conversation.
- */
- public static final class ConversationCursor extends MailCursor {
-
- private LabelMap mLabelMap;
-
- private int mConversationIdIndex;
- private int mSubjectIndex;
- private int mSnippetIndex;
- private int mFromIndex;
- private int mDateIndex;
- private int mPersonalLevelIndex;
- private int mLabelIdsIndex;
- private int mNumMessagesIndex;
- private int mMaxMessageIdIndex;
- private int mHasAttachmentsIndex;
- private int mHasMessagesWithErrorsIndex;
- private int mForceAllUnreadIndex;
-
- private TextUtils.StringSplitter mLabelIdsSplitter = newConversationLabelIdsSplitter();
-
- private ConversationCursor(Gmail gmail, String account, Cursor cursor) {
- super(account, cursor);
- mLabelMap = gmail.getLabelMap(account);
-
- mConversationIdIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.ID);
- mSubjectIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SUBJECT);
- mSnippetIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SNIPPET);
- mFromIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.FROM);
- mDateIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.DATE);
- mPersonalLevelIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.PERSONAL_LEVEL);
- mLabelIdsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.LABEL_IDS);
- mNumMessagesIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.NUM_MESSAGES);
- mMaxMessageIdIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.MAX_MESSAGE_ID);
- mHasAttachmentsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_ATTACHMENTS);
- mHasMessagesWithErrorsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_MESSAGES_WITH_ERRORS);
- mForceAllUnreadIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.FORCE_ALL_UNREAD);
- }
-
- @Override
- protected void onCursorPositionChanged() {
- super.onCursorPositionChanged();
- }
-
- public CursorStatus getStatus() {
- Bundle extras = mCursor.getExtras();
- String stringStatus = extras.getString(EXTRA_STATUS);
- return CursorStatus.valueOf(stringStatus);
- }
-
- /** Retry a network request after errors. */
- public void retry() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * When a conversation cursor is created it becomes the active network cursor, which means
- * that it will fetch results from the network if it needs to in order to show all mail that
- * matches its query. If you later want to requery an older cursor and would like that
- * cursor to be the active cursor you need to call this method before requerying.
- */
- public void becomeActiveNetworkCursor() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_ACTIVATE);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Tells the cursor whether its contents are visible to the user. The cursor will
- * automatically broadcast intents to remove any matching new-mail notifications when this
- * cursor's results become visible and, if they are visible, when the cursor is requeried.
- *
- * Note that contents shown in an activity that is resumed but not focused
- * (onWindowFocusChanged/hasWindowFocus) then results shown in that activity do not count
- * as visible. (This happens when the activity is behind the lock screen or a dialog.)
- *
- * @param visible whether the contents of this cursor are visible to the user.
- */
- public void setContentsVisibleToUser(boolean visible) {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_SET_VISIBLE);
- input.putBoolean(SET_VISIBLE_PARAM_VISIBLE, visible);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Gets the conversation id. This is immutable. (The server calls it the original
- * conversation id.)
- *
- * @return the conversation id
- */
- public long getConversationId() {
- return mCursor.getLong(mConversationIdIndex);
- }
-
- /**
- * Returns the instructions for building from snippets. Pass this to getFromSnippetHtml
- * in order to actually build the snippets.
- * @return snippet instructions for use by getFromSnippetHtml()
- */
- public String getFromSnippetInstructions() {
- return getStringInColumn(mFromIndex);
- }
-
- /**
- * Gets the conversation's subject.
- *
- * @return the subject
- */
- public String getSubject() {
- return getStringInColumn(mSubjectIndex);
- }
-
- /**
- * Gets the conversation's snippet.
- *
- * @return the snippet
- */
- public String getSnippet() {
- return getStringInColumn(mSnippetIndex);
- }
-
- /**
- * Get's the conversation's personal level.
- *
- * @return the personal level.
- */
- public PersonalLevel getPersonalLevel() {
- int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
- return PersonalLevel.fromInt(personalLevelInt);
- }
-
- /**
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- * @deprecated use getLabelIds
- */
- public Set<String> getLabels() {
- return getLabels(getRawLabelIds(), mLabelMap);
- }
-
- /**
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public Set<Long> getLabelIds() {
- mLabelIdsSplitter.setString(getRawLabelIds());
- return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
- }
-
- /**
- * Returns the set of labels using the raw labels from a previous getRawLabels()
- * as input.
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public Set<String> getLabels(String rawLabelIds, LabelMap labelMap) {
- mLabelIdsSplitter.setString(rawLabelIds);
- return getCanonicalNamesFromLabelIdsString(labelMap, mLabelIdsSplitter);
- }
-
- /**
- * @return a joined string of labels separated by spaces. Use
- * getLabels(rawLabels) to convert this to a Set of labels.
- */
- public String getRawLabelIds() {
- return mCursor.getString(mLabelIdsIndex);
- }
-
- /**
- * @return the number of messages in the conversation
- */
- public int getNumMessages() {
- return mCursor.getInt(mNumMessagesIndex);
- }
-
- /**
- * @return the max message id in the conversation
- */
- public long getMaxServerMessageId() {
- return mCursor.getLong(mMaxMessageIdIndex);
- }
-
- public long getDateMs() {
- return mCursor.getLong(mDateIndex);
- }
-
- public boolean hasAttachments() {
- return mCursor.getInt(mHasAttachmentsIndex) != 0;
- }
-
- public boolean hasMessagesWithErrors() {
- return mCursor.getInt(mHasMessagesWithErrorsIndex) != 0;
- }
-
- public boolean getForceAllUnread() {
- return !mCursor.isNull(mForceAllUnreadIndex)
- && mCursor.getInt(mForceAllUnreadIndex) != 0;
- }
- }
-}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1fc3678..d3f46b8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2589,8 +2589,8 @@
* @param gainFocus True if the View has focus; false otherwise.
* @param direction The direction focus has moved when requestFocus()
* is called to give this view focus. Values are
- * View.FOCUS_UP, View.FOCUS_DOWN, View.FOCUS_LEFT or
- * View.FOCUS_RIGHT. It may not always apply, in which
+ * {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT} or
+ * {@link #FOCUS_RIGHT}. It may not always apply, in which
* case use the default.
* @param previouslyFocusedRect The rectangle, in this view's coordinate
* system, of the previously focused view. If applicable, this will be
@@ -5961,7 +5961,11 @@
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= SAVE_STATE_CALLED;
if (state != BaseSavedState.EMPTY_STATE && state != null) {
- throw new IllegalArgumentException("Wrong state class -- expecting View State");
+ throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ + "received " + state.getClass().toString() + " instead. This usually happens "
+ + "when two views of different type have the same id in the same hierarchy. "
+ + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ + "other views do not use the same id.");
}
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 4baf612..2fd974e 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1300,7 +1300,7 @@
}
}
- private static Object resolveId(Context context, int id) {
+ static Object resolveId(Context context, int id) {
Object fieldValue;
final Resources resources = context.getResources();
if (id >= 0) {
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 924398e..608c8a24 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -789,7 +789,6 @@
// Set up a measure spec so a layout can always be recreated.
mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
- requestFocus();
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 3be6a3f..7c79fd9 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -3205,6 +3205,8 @@
// Note that sendOurVisibleRect calls viewToContent, so the coordinates
// should be in content coordinates.
Rect bounds = nativeFocusCandidateNodeBounds();
+ Rect vBox = contentToViewRect(bounds);
+ mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height());
if (!Rect.intersects(bounds, visibleRect)) {
mWebTextView.bringIntoView();
}
@@ -3215,25 +3217,14 @@
// i.e. In the case of opening/closing the screen.
// In that case, we need to set the dimensions, but not the other
// aspects.
- // We also need to restore the selection, which gets wrecked by
- // calling setTextEntryRect.
- Spannable spannable = (Spannable) mWebTextView.getText();
- int start = Selection.getSelectionStart(spannable);
- int end = Selection.getSelectionEnd(spannable);
// If the text has been changed by webkit, update it. However, if
// there has been more UI text input, ignore it. We will receive
// another update when that text is recognized.
- if (text != null && !text.equals(spannable.toString())
+ if (text != null && !text.equals(mWebTextView.getText().toString())
&& nativeTextGeneration() == mTextGeneration) {
mWebTextView.setTextAndKeepSelection(text);
- } else {
- // FIXME: Determine whether this is necessary.
- Selection.setSelection(spannable, start, end);
}
} else {
- Rect vBox = contentToViewRect(bounds);
- mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
- vBox.height());
mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ?
Gravity.RIGHT : Gravity.NO_GRAVITY);
// This needs to be called before setType, which may call
@@ -3247,8 +3238,8 @@
text = "";
}
mWebTextView.setTextAndKeepSelection(text);
- mWebTextView.requestFocus();
}
+ mWebTextView.requestFocus();
}
/**
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 6079773..5ded240 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1592,9 +1592,6 @@
// Used to avoid posting more than one split picture message.
private boolean mSplitPictureIsScheduled;
- // Used to suspend drawing.
- private boolean mDrawIsPaused;
-
// mRestoreState is set in didFirstLayout(), and reset in the next
// webkitDraw after passing it to the UI thread.
private RestoreState mRestoreState = null;
@@ -1713,17 +1710,6 @@
sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
.obtainMessage(WebCoreThread.REDUCE_PRIORITY));
- // Note: there is one possible failure mode. If pauseUpdate() is called
- // from UI thread while in webcore thread WEBKIT_DRAW is just pulled out
- // of the queue and about to be executed. mDrawIsScheduled may be set to
- // false in webkitDraw(). So update won't be blocked. But at least the
- // webcore thread priority is still lowered.
- if (core != null) {
- synchronized (core) {
- core.mDrawIsPaused = true;
- core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
- }
- }
}
static void resumeUpdate(WebViewCore core) {
@@ -1732,14 +1718,6 @@
sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
.obtainMessage(WebCoreThread.RESUME_PRIORITY));
- if (core != null) {
- synchronized (core) {
- core.mDrawIsScheduled = false;
- core.mDrawIsPaused = false;
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "resumeUpdate");
- core.contentDraw();
- }
- }
}
static void startCacheTransaction() {
@@ -1778,9 +1756,7 @@
}
// only fire an event if this is our first request
synchronized (this) {
- if (mDrawIsPaused || mDrawIsScheduled) {
- return;
- }
+ if (mDrawIsScheduled) return;
mDrawIsScheduled = true;
mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5199ada..e964a8f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -17,8 +17,8 @@
package com.android.internal.os;
import android.bluetooth.BluetoothHeadset;
+import android.net.TrafficStats;
import android.os.BatteryStats;
-import android.os.NetStat;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
@@ -1022,8 +1022,8 @@
public void doUnplug(long batteryUptime, long batteryRealtime) {
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
Uid u = mUidStats.valueAt(iu);
- u.mStartedTcpBytesReceived = NetStat.getUidRxBytes(u.mUid);
- u.mStartedTcpBytesSent = NetStat.getUidTxBytes(u.mUid);
+ u.mStartedTcpBytesReceived = TrafficStats.getUidRxBytes(u.mUid);
+ u.mStartedTcpBytesSent = TrafficStats.getUidTxBytes(u.mUid);
u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent;
}
@@ -1031,10 +1031,10 @@
mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
}
// Track total mobile data
- doDataUnplug(mMobileDataRx, NetStat.getMobileRxBytes());
- doDataUnplug(mMobileDataTx, NetStat.getMobileTxBytes());
- doDataUnplug(mTotalDataRx, NetStat.getTotalRxBytes());
- doDataUnplug(mTotalDataTx, NetStat.getTotalTxBytes());
+ doDataUnplug(mMobileDataRx, TrafficStats.getMobileRxBytes());
+ doDataUnplug(mMobileDataTx, TrafficStats.getMobileTxBytes());
+ doDataUnplug(mTotalDataRx, TrafficStats.getTotalRxBytes());
+ doDataUnplug(mTotalDataTx, TrafficStats.getTotalTxBytes());
// Track radio awake time
mRadioDataStart = getCurrentRadioDataUptime();
mRadioDataUptime = 0;
@@ -1058,10 +1058,10 @@
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
}
- doDataPlug(mMobileDataRx, NetStat.getMobileRxBytes());
- doDataPlug(mMobileDataTx, NetStat.getMobileTxBytes());
- doDataPlug(mTotalDataRx, NetStat.getTotalRxBytes());
- doDataPlug(mTotalDataTx, NetStat.getTotalTxBytes());
+ doDataPlug(mMobileDataRx, TrafficStats.getMobileRxBytes());
+ doDataPlug(mMobileDataTx, TrafficStats.getMobileTxBytes());
+ doDataPlug(mTotalDataRx, TrafficStats.getTotalRxBytes());
+ doDataPlug(mTotalDataTx, TrafficStats.getTotalTxBytes());
// Track radio awake time
mRadioDataUptime = getRadioDataUptime();
mRadioDataStart = -1;
@@ -1519,7 +1519,7 @@
public long computeCurrentTcpBytesReceived() {
return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
- ? (NetStat.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
+ ? (TrafficStats.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
}
@Override
@@ -1696,7 +1696,7 @@
public long computeCurrentTcpBytesSent() {
return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
- ? (NetStat.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
+ ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
}
void writeToParcelLocked(Parcel out, long batteryRealtime) {
@@ -2919,22 +2919,22 @@
/** Only STATS_UNPLUGGED works properly */
public long getMobileTcpBytesSent(int which) {
- return getTcpBytes(NetStat.getMobileTxBytes(), mMobileDataTx, which);
+ return getTcpBytes(TrafficStats.getMobileTxBytes(), mMobileDataTx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getMobileTcpBytesReceived(int which) {
- return getTcpBytes(NetStat.getMobileRxBytes(), mMobileDataRx, which);
+ return getTcpBytes(TrafficStats.getMobileRxBytes(), mMobileDataRx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getTotalTcpBytesSent(int which) {
- return getTcpBytes(NetStat.getTotalTxBytes(), mTotalDataTx, which);
+ return getTcpBytes(TrafficStats.getTotalTxBytes(), mTotalDataTx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getTotalTcpBytesReceived(int which) {
- return getTcpBytes(NetStat.getTotalRxBytes(), mTotalDataRx, which);
+ return getTcpBytes(TrafficStats.getTotalRxBytes(), mTotalDataRx, which);
}
@Override
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index eacf0ce..ba0bf0d 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -76,6 +76,13 @@
*/
public static final native IBinder getContextObject();
+ /**
+ * Special for system process to not allow incoming calls to run at
+ * background scheduling priority.
+ * @hide
+ */
+ public static final native void disableBackgroundScheduling(boolean disable);
+
static native final void handleGc();
public static void forceGc(String reason) {
diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java
index 8a1298f..7bdc7f8 100644
--- a/core/java/com/google/android/net/GoogleHttpClient.java
+++ b/core/java/com/google/android/net/GoogleHttpClient.java
@@ -19,13 +19,14 @@
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.net.TrafficStats;
import android.net.http.AndroidHttpClient;
import android.os.Build;
-import android.os.NetStat;
import android.os.SystemClock;
import android.provider.Checkin;
import android.util.Config;
import android.util.Log;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
@@ -45,7 +46,6 @@
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
import java.io.IOException;
import java.net.InetAddress;
@@ -205,8 +205,8 @@
// to follow redirects, count each redirect as an additional round trip.
int uid = android.os.Process.myUid();
- long startTx = NetStat.getUidTxBytes(uid);
- long startRx = NetStat.getUidRxBytes(uid);
+ long startTx = TrafficStats.getUidTxBytes(uid);
+ long startRx = TrafficStats.getUidRxBytes(uid);
response = mClient.execute(request, context);
HttpEntity origEntity = response == null ? null : response.getEntity();
diff --git a/core/java/com/google/android/net/NetworkStatsEntity.java b/core/java/com/google/android/net/NetworkStatsEntity.java
index f5d2349..a22fa1e 100644
--- a/core/java/com/google/android/net/NetworkStatsEntity.java
+++ b/core/java/com/google/android/net/NetworkStatsEntity.java
@@ -16,11 +16,10 @@
package com.google.android.net;
-import android.os.NetStat;
+import android.net.TrafficStats;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.EventLog;
-
import org.apache.http.HttpEntity;
import org.apache.http.entity.HttpEntityWrapper;
@@ -45,8 +44,8 @@
super.close();
} finally {
long processingTime = SystemClock.elapsedRealtime() - mProcessingStartTime;
- long tx = NetStat.getUidTxBytes(mUid);
- long rx = NetStat.getUidRxBytes(mUid);
+ long tx = TrafficStats.getUidTxBytes(mUid);
+ long rx = TrafficStats.getUidRxBytes(mUid);
EventLog.writeEvent(HTTP_STATS_EVENT, mUa, mResponseLatency, processingTime,
tx - mStartTx, rx - mStartRx);
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f0885fd..627fcbf 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -670,6 +670,12 @@
android::IPCThreadState::self()->joinThreadPool();
}
+static void android_os_BinderInternal_disableBackgroundScheduling(JNIEnv* env,
+ jobject clazz, jboolean disable)
+{
+ IPCThreadState::disableBackgroundScheduling(disable ? true : false);
+}
+
static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz)
{
LOGV("Gc has executed, clearing binder ops");
@@ -682,6 +688,7 @@
/* name, signature, funcPtr */
{ "getContextObject", "()Landroid/os/IBinder;", (void*)android_os_BinderInternal_getContextObject },
{ "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool },
+ { "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling },
{ "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }
};
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 094b02d..e84f2e5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -120,11 +120,7 @@
jint android_os_Process_myTid(JNIEnv* env, jobject clazz)
{
-#ifdef HAVE_GETTID
- return gettid();
-#else
- return getpid();
-#endif
+ return androidGetTid();
}
jint android_os_Process_getUidForName(JNIEnv* env, jobject clazz, jstring name)
@@ -191,15 +187,11 @@
void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
- if (grp > ANDROID_TGROUP_MAX || grp < 0) {
- signalExceptionForGroupError(env, clazz, EINVAL);
+ int res = androidSetThreadSchedulingGroup(pid, grp);
+ if (res != NO_ERROR) {
+ signalExceptionForGroupError(env, clazz, res == BAD_VALUE ? EINVAL : errno);
return;
}
-
- if (set_sched_policy(pid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
- SP_BACKGROUND : SP_FOREGROUND)) {
- signalExceptionForGroupError(env, clazz, errno);
- }
}
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
@@ -275,22 +267,15 @@
void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,
jint pid, jint pri)
{
- int rc = 0;
-
- if (pri >= ANDROID_PRIORITY_BACKGROUND) {
- rc = set_sched_policy(pid, SP_BACKGROUND);
- } else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) {
- rc = set_sched_policy(pid, SP_FOREGROUND);
+ int rc = androidSetThreadPriority(pid, pri);
+ if (rc != 0) {
+ if (rc == INVALID_OPERATION) {
+ signalExceptionForPriorityError(env, clazz, errno);
+ } else {
+ signalExceptionForGroupError(env, clazz, errno);
+ }
}
-
- if (rc) {
- signalExceptionForGroupError(env, clazz, errno);
- return;
- }
-
- if (setpriority(PRIO_PROCESS, pid, pri) < 0) {
- signalExceptionForPriorityError(env, clazz, errno);
- }
+
//LOGI("Setting priority of %d: %d, getpriority returns %d\n",
// pid, pri, getpriority(PRIO_PROCESS, pid));
}
diff --git a/include/binder/IBinder.h b/include/binder/IBinder.h
index 884b5c1..749a977 100644
--- a/include/binder/IBinder.h
+++ b/include/binder/IBinder.h
@@ -52,7 +52,7 @@
DUMP_TRANSACTION = B_PACK_CHARS('_','D','M','P'),
INTERFACE_TRANSACTION = B_PACK_CHARS('_', 'N', 'T', 'F'),
- // Corresponds to tfOneWay -- an asynchronous call.
+ // Corresponds to TF_ONE_WAY -- an asynchronous call.
FLAG_ONEWAY = 0x00000001
};
diff --git a/include/binder/IPCThreadState.h b/include/binder/IPCThreadState.h
index 78306b2b..3ab985d 100644
--- a/include/binder/IPCThreadState.h
+++ b/include/binder/IPCThreadState.h
@@ -68,6 +68,13 @@
static void shutdown();
+ // Call this to disable switching threads to background scheduling when
+ // receiving incoming IPC calls. This is specifically here for the
+ // Android system process, since it expects to have background apps calling
+ // in to it but doesn't want to acquire locks in its services while in
+ // the background.
+ static void disableBackgroundScheduling(bool disable);
+
private:
IPCThreadState();
~IPCThreadState();
@@ -93,9 +100,10 @@
void* cookie);
const sp<ProcessState> mProcess;
+ const pid_t mMyThreadId;
Vector<BBinder*> mPendingStrongDerefs;
Vector<RefBase::weakref_type*> mPendingWeakDerefs;
-
+
Parcel mIn;
Parcel mOut;
status_t mLastError;
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 49145e8..a845908 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -393,7 +393,10 @@
enum {
// If set, the string index is sorted by the string values (based
// on strcmp16()).
- SORTED_FLAG = 1<<0
+ SORTED_FLAG = 1<<0,
+
+ // String pool is encoded in UTF-8
+ UTF8_FLAG = 1<<8
};
uint32_t flags;
@@ -456,9 +459,11 @@
void* mOwnedData;
const ResStringPool_header* mHeader;
size_t mSize;
+ mutable Mutex mDecodeLock;
const uint32_t* mEntries;
const uint32_t* mEntryStyles;
- const char16_t* mStrings;
+ const void* mStrings;
+ char16_t** mCache;
uint32_t mStringPoolSize; // number of uint16_t
const uint32_t* mStyles;
uint32_t mStylePoolSize; // number of uint32_t
diff --git a/include/utils/String16.h b/include/utils/String16.h
index a2d22ee..07a0c11 100644
--- a/include/utils/String16.h
+++ b/include/utils/String16.h
@@ -49,12 +49,17 @@
// Version of strzcmp16 for comparing strings in different endianness.
int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2);
+// Convert UTF-8 to UTF-16 including surrogate pairs
+void utf8_to_utf16(const uint8_t *src, size_t srcLen, char16_t* dst, const size_t dstLen);
+
}
// ---------------------------------------------------------------------------
namespace android {
+// ---------------------------------------------------------------------------
+
class String8;
class TextOutput;
diff --git a/include/utils/String8.h b/include/utils/String8.h
index ecc5774..c4b18a4 100644
--- a/include/utils/String8.h
+++ b/include/utils/String8.h
@@ -60,6 +60,11 @@
/*
* Returns the UTF-8 length of "src".
*/
+size_t utf8_length_from_utf16(const char16_t *src, size_t src_len);
+
+/*
+ * Returns the UTF-8 length of "src".
+ */
size_t utf8_length_from_utf32(const char32_t *src, size_t src_len);
/*
@@ -120,6 +125,9 @@
size_t utf32_to_utf8(const char32_t* src, size_t src_len,
char* dst, size_t dst_len);
+size_t utf16_to_utf8(const char16_t* src, size_t src_len,
+ char* dst, size_t dst_len);
+
}
// ---------------------------------------------------------------------------
diff --git a/include/utils/threads.h b/include/utils/threads.h
index 0fc533f..130d83c 100644
--- a/include/utils/threads.h
+++ b/include/utils/threads.h
@@ -124,6 +124,24 @@
extern void androidSetCreateThreadFunc(android_create_thread_fn func);
+// ------------------------------------------------------------------
+// Extra functions working with raw pids.
+
+// Get pid for the current thread.
+extern pid_t androidGetTid();
+
+// Change the scheduling group of a particular thread. The group
+// should be one of the ANDROID_TGROUP constants. Returns BAD_VALUE if
+// grp is out of range, else another non-zero value with errno set if
+// the operation failed.
+extern int androidSetThreadSchedulingGroup(pid_t tid, int grp);
+
+// Change the priority AND scheduling group of a particular thread. The priority
+// should be one of the ANDROID_PRIORITY constants. Returns INVALID_OPERATION
+// if the priority set failed, else another value if just the group set failed;
+// in either case errno is set.
+extern int androidSetThreadPriority(pid_t tid, int prio);
+
#ifdef __cplusplus
}
#endif
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index b2a7db8..473f580 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -292,6 +292,7 @@
static bool gHaveTLS = false;
static pthread_key_t gTLS = 0;
static bool gShutdown = false;
+static bool gDisableBackgroundScheduling = false;
IPCThreadState* IPCThreadState::self()
{
@@ -332,6 +333,11 @@
}
}
+void IPCThreadState::disableBackgroundScheduling(bool disable)
+{
+ gDisableBackgroundScheduling = disable;
+}
+
sp<ProcessState> IPCThreadState::process()
{
return mProcess;
@@ -386,6 +392,11 @@
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
+ // This thread may have been spawned by a thread that was in the background
+ // scheduling group, so first we will make sure it is in the default/foreground
+ // one to avoid performing an initial transaction in the background.
+ androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);
+
status_t result;
do {
int32_t cmd;
@@ -427,19 +438,13 @@
}
// After executing the command, ensure that the thread is returned to the
- // default cgroup and priority before rejoining the pool. This is a failsafe
- // in case the command implementation failed to properly restore the thread's
- // scheduling parameters upon completion.
- int my_id;
-#ifdef HAVE_GETTID
- my_id = gettid();
-#else
- my_id = getpid();
-#endif
- if (!set_sched_policy(my_id, SP_FOREGROUND)) {
- // success; reset the priority as well
- setpriority(PRIO_PROCESS, my_id, ANDROID_PRIORITY_NORMAL);
- }
+ // default cgroup before rejoining the pool. The driver takes care of
+ // restoring the priority, but doesn't do anything with cgroups so we
+ // need to take care of that here in userspace. Note that we do make
+ // sure to go in the foreground after executing a transaction, but
+ // there are other callbacks into user code that could have changed
+ // our group so we want to make absolutely sure it is put back.
+ androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);
// Let this thread exit the thread pool if it is no longer
// needed and it is not the main process thread.
@@ -583,10 +588,10 @@
}
IPCThreadState::IPCThreadState()
- : mProcess(ProcessState::self())
+ : mProcess(ProcessState::self()), mMyThreadId(androidGetTid())
{
pthread_setspecific(gTLS, this);
- clearCaller();
+ clearCaller();
mIn.setDataCapacity(256);
mOut.setDataCapacity(256);
}
@@ -930,6 +935,17 @@
mCallingPid = tr.sender_pid;
mCallingUid = tr.sender_euid;
+ bool doBackground = !gDisableBackgroundScheduling &&
+ getpriority(PRIO_PROCESS, mMyThreadId)
+ >= ANDROID_PRIORITY_BACKGROUND;
+ if (doBackground) {
+ // We have inherited a background priority from the caller.
+ // Ensure this thread is in the background scheduling class,
+ // since the driver won't modify scheduling classes for us.
+ androidSetThreadSchedulingGroup(mMyThreadId,
+ ANDROID_TGROUP_BG_NONINTERACT);
+ }
+
//LOGI(">>>> TRANSACT from pid %d uid %d\n", mCallingPid, mCallingUid);
Parcel reply;
@@ -967,6 +983,13 @@
mCallingPid = origPid;
mCallingUid = origUid;
+ if (doBackground) {
+ // We moved to the background scheduling group to execute
+ // this transaction, so now that we are done go back in the
+ // foreground.
+ androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);
+ }
+
IF_LOG_TRANSACTIONS() {
TextOutput::Bundle _b(alog);
alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj "
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index 450af8d..afca814 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -229,12 +229,12 @@
// --------------------------------------------------------------------
ResStringPool::ResStringPool()
- : mError(NO_INIT), mOwnedData(NULL)
+ : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
{
}
ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
- : mError(NO_INIT), mOwnedData(NULL)
+ : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
{
setTo(data, size, copyData);
}
@@ -296,7 +296,17 @@
(int)size);
return (mError=BAD_TYPE);
}
- mStrings = (const char16_t*)
+
+ size_t charSize;
+ if (mHeader->flags&ResStringPool_header::UTF8_FLAG) {
+ charSize = sizeof(uint8_t);
+ mCache = (char16_t**)malloc(sizeof(char16_t**)*mHeader->stringCount);
+ memset(mCache, 0, sizeof(char16_t**)*mHeader->stringCount);
+ } else {
+ charSize = sizeof(char16_t);
+ }
+
+ mStrings = (const void*)
(((const uint8_t*)data)+mHeader->stringsStart);
if (mHeader->stringsStart >= (mHeader->header.size-sizeof(uint16_t))) {
LOGW("Bad string block: string pool starts at %d, after total size %d\n",
@@ -305,7 +315,7 @@
}
if (mHeader->styleCount == 0) {
mStringPoolSize =
- (mHeader->header.size-mHeader->stringsStart)/sizeof(uint16_t);
+ (mHeader->header.size-mHeader->stringsStart)/charSize;
} else {
// check invariant: styles follow the strings
if (mHeader->stylesStart <= mHeader->stringsStart) {
@@ -314,7 +324,7 @@
return (mError=BAD_TYPE);
}
mStringPoolSize =
- (mHeader->stylesStart-mHeader->stringsStart)/sizeof(uint16_t);
+ (mHeader->stylesStart-mHeader->stringsStart)/charSize;
}
// check invariant: stringCount > 0 requires a string pool to exist
@@ -329,13 +339,19 @@
for (i=0; i<mHeader->stringCount; i++) {
e[i] = dtohl(mEntries[i]);
}
- char16_t* s = const_cast<char16_t*>(mStrings);
- for (i=0; i<mStringPoolSize; i++) {
- s[i] = dtohs(mStrings[i]);
+ if (!(mHeader->flags&ResStringPool_header::UTF8_FLAG)) {
+ const char16_t* strings = (const char16_t*)mStrings;
+ char16_t* s = const_cast<char16_t*>(strings);
+ for (i=0; i<mStringPoolSize; i++) {
+ s[i] = dtohs(strings[i]);
+ }
}
}
- if (mStrings[mStringPoolSize-1] != 0) {
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+ ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) ||
+ (!mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+ ((char16_t*)mStrings)[mStringPoolSize-1] != 0)) {
LOGW("Bad string block: last string is not 0-terminated\n");
return (mError=BAD_TYPE);
}
@@ -410,24 +426,67 @@
free(mOwnedData);
mOwnedData = NULL;
}
+ if (mHeader != NULL && mCache != NULL) {
+ for (size_t x = 0; x < mHeader->stringCount; x++) {
+ if (mCache[x] != NULL) {
+ free(mCache[x]);
+ mCache[x] = NULL;
+ }
+ }
+ free(mCache);
+ mCache = NULL;
+ }
}
+#define DECODE_LENGTH(str, chrsz, len) \
+ len = *(str); \
+ if (*(str)&(1<<(chrsz*8-1))) { \
+ (str)++; \
+ len = (((len)&((1<<(chrsz*8-1))-1))<<(chrsz*8)) + *(str); \
+ } \
+ (str)++;
+
const uint16_t* ResStringPool::stringAt(size_t idx, size_t* outLen) const
{
if (mError == NO_ERROR && idx < mHeader->stringCount) {
- const uint32_t off = (mEntries[idx]/sizeof(uint16_t));
+ const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0;
+ const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t));
if (off < (mStringPoolSize-1)) {
- const char16_t* str = mStrings+off;
- *outLen = *str;
- if ((*str)&0x8000) {
- str++;
- *outLen = (((*outLen)&0x7fff)<<16) + *str;
- }
- if ((uint32_t)(str+1+*outLen-mStrings) < mStringPoolSize) {
- return str+1;
+ if (!isUTF8) {
+ const char16_t* strings = (char16_t*)mStrings;
+ const char16_t* str = strings+off;
+ DECODE_LENGTH(str, sizeof(char16_t), *outLen)
+ if ((uint32_t)(str+*outLen-strings) < mStringPoolSize) {
+ return str;
+ } else {
+ LOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+ (int)idx, (int)(str+*outLen-strings), (int)mStringPoolSize);
+ }
} else {
- LOGW("Bad string block: string #%d extends to %d, past end at %d\n",
- (int)idx, (int)(str+1+*outLen-mStrings), (int)mStringPoolSize);
+ const uint8_t* strings = (uint8_t*)mStrings;
+ const uint8_t* str = strings+off;
+ DECODE_LENGTH(str, sizeof(uint8_t), *outLen)
+ size_t encLen;
+ DECODE_LENGTH(str, sizeof(uint8_t), encLen)
+ if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
+ AutoMutex lock(mDecodeLock);
+ if (mCache[idx] != NULL) {
+ return mCache[idx];
+ }
+ char16_t *u16str = (char16_t *)calloc(*outLen+1, sizeof(char16_t));
+ if (!u16str) {
+ LOGW("No memory when trying to allocate decode cache for string #%d\n",
+ (int)idx);
+ return NULL;
+ }
+ const unsigned char *u8src = reinterpret_cast<const unsigned char *>(str);
+ utf8_to_utf16(u8src, encLen, u16str, *outLen);
+ mCache[idx] = u16str;
+ return u16str;
+ } else {
+ LOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+ (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
+ }
}
} else {
LOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
@@ -466,6 +525,10 @@
size_t len;
+ // TODO optimize searching for UTF-8 strings taking into account
+ // the cache fill to determine when to convert the searched-for
+ // string key to UTF-8.
+
if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
// Do a binary search for the string...
ssize_t l = 0;
@@ -1043,6 +1106,7 @@
void ResXMLTree::uninit()
{
mError = NO_INIT;
+ mStrings.uninit();
if (mOwnedData) {
free(mOwnedData);
mOwnedData = NULL;
diff --git a/libs/utils/String16.cpp b/libs/utils/String16.cpp
index aef67f2..eab7b2b 100644
--- a/libs/utils/String16.cpp
+++ b/libs/utils/String16.cpp
@@ -172,10 +172,6 @@
: 0);
}
-// ---------------------------------------------------------------------------
-
-namespace android {
-
static inline size_t
utf8_char_len(uint8_t ch)
{
@@ -215,8 +211,38 @@
//printf("Char at %p: len=%d, utf-16=%p\n", src, length, (void*)result);
}
+void
+utf8_to_utf16(const uint8_t *src, size_t srcLen,
+ char16_t* dst, const size_t dstLen)
+{
+ const uint8_t* const end = src + srcLen;
+ const char16_t* const dstEnd = dst + dstLen;
+ while (src < end && dst < dstEnd) {
+ size_t len = utf8_char_len(*src);
+ uint32_t codepoint = utf8_to_utf32((const uint8_t*)src, len);
+
+ // Convert the UTF32 codepoint to one or more UTF16 codepoints
+ if (codepoint <= 0xFFFF) {
+ // Single UTF16 character
+ *dst++ = (char16_t) codepoint;
+ } else {
+ // Multiple UTF16 characters with surrogates
+ codepoint = codepoint - 0x10000;
+ *dst++ = (char16_t) ((codepoint >> 10) + 0xD800);
+ *dst++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00);
+ }
+
+ src += len;
+ }
+ if (dst < dstEnd) {
+ *dst = 0;
+ }
+}
+
// ---------------------------------------------------------------------------
+namespace android {
+
static SharedBuffer* gEmptyStringBuf = NULL;
static char16_t* gEmptyString = NULL;
@@ -260,30 +286,14 @@
p += utf8len;
}
- SharedBuffer* buf = SharedBuffer::alloc((chars+1)*sizeof(char16_t));
+ size_t bufSize = (chars+1)*sizeof(char16_t);
+ SharedBuffer* buf = SharedBuffer::alloc(bufSize);
if (buf) {
p = in;
char16_t* str = (char16_t*)buf->data();
- char16_t* d = str;
- while (p < end) {
- size_t len = utf8_char_len(*p);
- uint32_t codepoint = utf8_to_utf32((const uint8_t*)p, len);
-
- // Convert the UTF32 codepoint to one or more UTF16 codepoints
- if (codepoint <= 0xFFFF) {
- // Single UTF16 character
- *d++ = (char16_t) codepoint;
- } else {
- // Multiple UTF16 characters with surrogates
- codepoint = codepoint - 0x10000;
- *d++ = (char16_t) ((codepoint >> 10) + 0xD800);
- *d++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00);
- }
-
- p += len;
- }
- *d = 0;
+ utf8_to_utf16((const uint8_t*)p, len, str, bufSize);
+
//printf("Created UTF-16 string from UTF-8 \"%s\":", in);
//printHexData(1, str, buf->size(), 16, 1);
//printf("\n");
diff --git a/libs/utils/String8.cpp b/libs/utils/String8.cpp
index e908ec1..3a34838 100644
--- a/libs/utils/String8.cpp
+++ b/libs/utils/String8.cpp
@@ -208,10 +208,23 @@
return getEmptyString();
}
-// Note: not dealing with expanding surrogate pairs.
static char* allocFromUTF16(const char16_t* in, size_t len)
{
- return allocFromUTF16OrUTF32<char16_t, size_t>(in, len);
+ if (len == 0) return getEmptyString();
+
+ const size_t bytes = utf8_length_from_utf16(in, len);
+
+ SharedBuffer* buf = SharedBuffer::alloc(bytes+1);
+ LOG_ASSERT(buf, "Unable to allocate shared buffer");
+ if (buf) {
+ char* str = (char*)buf->data();
+
+ utf16_to_utf8(in, len, str, bytes+1);
+
+ return str;
+ }
+
+ return getEmptyString();
}
static char* allocFromUTF32(const char32_t* in, size_t len)
@@ -762,6 +775,26 @@
return ret;
}
+size_t utf8_length_from_utf16(const char16_t *src, size_t src_len)
+{
+ if (src == NULL || src_len == 0) {
+ return 0;
+ }
+ size_t ret = 0;
+ const char16_t* const end = src + src_len;
+ while (src < end) {
+ if ((*src & 0xFC00) == 0xD800 && (src + 1) < end
+ && (*++src & 0xFC00) == 0xDC00) {
+ // surrogate pairs are always 4 bytes.
+ ret += 4;
+ src++;
+ } else {
+ ret += android::utf32_to_utf8_bytes((char32_t) *src++);
+ }
+ }
+ return ret;
+}
+
static int32_t utf32_at_internal(const char* cur, size_t *num_read)
{
const char first_char = *cur;
@@ -848,3 +881,33 @@
}
return cur - dst;
}
+
+size_t utf16_to_utf8(const char16_t* src, size_t src_len,
+ char* dst, size_t dst_len)
+{
+ if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) {
+ return 0;
+ }
+ const char16_t* cur_utf16 = src;
+ const char16_t* const end_utf16 = src + src_len;
+ char *cur = dst;
+ const char* const end = dst + dst_len;
+ while (cur_utf16 < end_utf16 && cur < end) {
+ char32_t utf32;
+ // surrogate pairs
+ if ((*cur_utf16 & 0xFC00) == 0xD800 && (cur_utf16 + 1) < end_utf16) {
+ utf32 = (*cur_utf16++ - 0xD800) << 10;
+ utf32 |= *cur_utf16++ - 0xDC00;
+ utf32 += 0x10000;
+ } else {
+ utf32 = (char32_t) *cur_utf16++;
+ }
+ size_t len = android::utf32_to_utf8_bytes(utf32);
+ android::utf32_to_utf8((uint8_t*)cur, utf32, len);
+ cur += len;
+ }
+ if (cur < end) {
+ *cur = '\0';
+ }
+ return cur - dst;
+}
diff --git a/libs/utils/Threads.cpp b/libs/utils/Threads.cpp
index ec3db09..6ca2603 100644
--- a/libs/utils/Threads.cpp
+++ b/libs/utils/Threads.cpp
@@ -20,6 +20,8 @@
#include <utils/threads.h>
#include <utils/Log.h>
+#include <cutils/sched_policy.h>
+
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
@@ -269,6 +271,53 @@
gCreateThreadFn = func;
}
+pid_t androidGetTid()
+{
+#ifdef HAVE_GETTID
+ return gettid();
+#else
+ return getpid();
+#endif
+}
+
+int androidSetThreadSchedulingGroup(pid_t tid, int grp)
+{
+ if (grp > ANDROID_TGROUP_MAX || grp < 0) {
+ return BAD_VALUE;
+ }
+
+ if (set_sched_policy(tid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
+ SP_BACKGROUND : SP_FOREGROUND)) {
+ return PERMISSION_DENIED;
+ }
+
+ return NO_ERROR;
+}
+
+int androidSetThreadPriority(pid_t tid, int pri)
+{
+ int rc = 0;
+ int lasterr = 0;
+
+ if (pri >= ANDROID_PRIORITY_BACKGROUND) {
+ rc = set_sched_policy(tid, SP_BACKGROUND);
+ } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
+ rc = set_sched_policy(tid, SP_FOREGROUND);
+ }
+
+ if (rc) {
+ lasterr = errno;
+ }
+
+ if (setpriority(PRIO_PROCESS, tid, pri) < 0) {
+ rc = INVALID_OPERATION;
+ } else {
+ errno = lasterr;
+ }
+
+ return rc;
+}
+
namespace android {
/*
diff --git a/preloaded-classes b/preloaded-classes
index af74b33..f86555c 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -435,6 +435,7 @@
android.net.NetworkInfo$DetailedState
android.net.NetworkInfo$State
android.net.NetworkUtils
+android.net.TrafficStats
android.net.Uri
android.net.Uri$HierarchicalUri
android.net.Uri$OpaqueUri
@@ -515,7 +516,6 @@
android.os.Looper
android.os.MemoryFile
android.os.Message
-android.os.NetStat
android.os.Parcel
android.os.Parcel$1
android.os.ParcelFileDescriptor
@@ -600,16 +600,6 @@
android.provider.ContactsContract$RawContacts
android.provider.Downloads
android.provider.DrmStore$Audio
-android.provider.Gmail
-android.provider.Gmail$AttachmentOrigin
-android.provider.Gmail$AttachmentRendition
-android.provider.Gmail$BecomeActiveNetworkCursor
-android.provider.Gmail$ConversationCursor
-android.provider.Gmail$CursorStatus
-android.provider.Gmail$LabelMap
-android.provider.Gmail$MailCursor
-android.provider.Gmail$MessageCursor
-android.provider.Gmail$PersonalLevel
android.provider.Im$Account
android.provider.Im$Avatars
android.provider.Im$Chats
diff --git a/services/java/com/android/server/NetStatService.java b/services/java/com/android/server/NetStatService.java
index 1ea0bac..0834405 100644
--- a/services/java/com/android/server/NetStatService.java
+++ b/services/java/com/android/server/NetStatService.java
@@ -17,8 +17,8 @@
package com.android.server;
import android.content.Context;
+import android.net.TrafficStats;
import android.os.INetStatService;
-import android.os.NetStat;
public class NetStatService extends INetStatService.Stub {
@@ -27,34 +27,34 @@
}
public long getMobileTxPackets() {
- return NetStat.getMobileTxPkts();
+ return TrafficStats.getMobileTxPkts();
}
public long getMobileRxPackets() {
- return NetStat.getMobileRxPkts();
+ return TrafficStats.getMobileRxPkts();
}
public long getMobileTxBytes() {
- return NetStat.getMobileTxBytes();
+ return TrafficStats.getMobileTxBytes();
}
public long getMobileRxBytes() {
- return NetStat.getMobileRxBytes();
+ return TrafficStats.getMobileRxBytes();
}
public long getTotalTxPackets() {
- return NetStat.getTotalTxPkts();
+ return TrafficStats.getTotalTxPkts();
}
public long getTotalRxPackets() {
- return NetStat.getTotalRxPkts();
+ return TrafficStats.getTotalRxPkts();
}
public long getTotalTxBytes() {
- return NetStat.getTotalTxBytes();
+ return TrafficStats.getTotalTxBytes();
}
public long getTotalRxBytes() {
- return NetStat.getTotalRxBytes();
+ return TrafficStats.getTotalRxBytes();
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73f930d..214ecc1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -18,6 +18,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.status.StatusBarService;
+import com.android.internal.os.BinderInternal;
import com.android.internal.os.SamplingProfilerIntegration;
import dalvik.system.VMRuntime;
@@ -80,6 +81,8 @@
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_FOREGROUND);
+ BinderInternal.disableBackgroundScheduling(true);
+
String factoryTestStr = SystemProperties.get("ro.factorytest");
int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
: Integer.parseInt(factoryTestStr);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 65070dd..6c5c52f 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -5258,7 +5258,8 @@
app.thread = thread;
app.curAdj = app.setAdj = -100;
- app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
+ app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
+ app.setSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.forcingToForeground = null;
app.foregroundServices = false;
app.debugging = false;
@@ -9133,7 +9134,7 @@
if (needSep) pw.println(" ");
needSep = true;
pw.println(" Running processes (most recent first):");
- dumpProcessList(pw, mLRUProcesses, " ",
+ dumpProcessList(pw, this, mLRUProcesses, " ",
"App ", "PERS", true);
needSep = true;
}
@@ -9164,7 +9165,7 @@
if (needSep) pw.println(" ");
needSep = true;
pw.println(" Persisent processes that are starting:");
- dumpProcessList(pw, mPersistentStartingProcesses, " ",
+ dumpProcessList(pw, this, mPersistentStartingProcesses, " ",
"Starting Norm", "Restarting PERS", false);
}
@@ -9172,7 +9173,7 @@
if (needSep) pw.println(" ");
needSep = true;
pw.println(" Processes that are starting:");
- dumpProcessList(pw, mStartingProcesses, " ",
+ dumpProcessList(pw, this, mStartingProcesses, " ",
"Starting Norm", "Starting PERS", false);
}
@@ -9180,7 +9181,7 @@
if (needSep) pw.println(" ");
needSep = true;
pw.println(" Processes that are being removed:");
- dumpProcessList(pw, mRemovedProcesses, " ",
+ dumpProcessList(pw, this, mRemovedProcesses, " ",
"Removed Norm", "Removed PERS", false);
}
@@ -9188,7 +9189,7 @@
if (needSep) pw.println(" ");
needSep = true;
pw.println(" Processes that are on old until the system is ready:");
- dumpProcessList(pw, mProcessesOnHold, " ",
+ dumpProcessList(pw, this, mProcessesOnHold, " ",
"OnHold Norm", "OnHold PERS", false);
}
@@ -9617,7 +9618,16 @@
}
}
- private static final int dumpProcessList(PrintWriter pw, List list,
+ private static String buildOomTag(String prefix, String space, int val, int base) {
+ if (val == base) {
+ if (space == null) return prefix;
+ return prefix + " ";
+ }
+ return prefix + "+" + Integer.toString(val-base);
+ }
+
+ private static final int dumpProcessList(PrintWriter pw,
+ ActivityManagerService service, List list,
String prefix, String normalLabel, String persistentLabel,
boolean inclOomAdj) {
int numPers = 0;
@@ -9628,9 +9638,55 @@
+ " #" + i + ":");
r.dump(pw, prefix + " ");
} else if (inclOomAdj) {
- pw.println(String.format("%s%s #%2d: adj=%4d/%d %s (%s)",
+ String oomAdj;
+ if (r.setAdj >= EMPTY_APP_ADJ) {
+ oomAdj = buildOomTag("empty", null, r.setAdj,
+ EMPTY_APP_ADJ);
+ } else if (r.setAdj >= CONTENT_PROVIDER_ADJ) {
+ oomAdj = buildOomTag("cprov", null, r.setAdj,
+ CONTENT_PROVIDER_ADJ);
+ } else if (r.setAdj >= HIDDEN_APP_MIN_ADJ) {
+ oomAdj = buildOomTag("hid", " ", r.setAdj,
+ HIDDEN_APP_MIN_ADJ);
+ } else if (r.setAdj >= service.HOME_APP_ADJ) {
+ oomAdj = buildOomTag("home ", null, r.setAdj,
+ service.HOME_APP_ADJ);
+ } else if (r.setAdj >= service.SECONDARY_SERVER_ADJ) {
+ oomAdj = buildOomTag("svc", " ", r.setAdj,
+ service.SECONDARY_SERVER_ADJ);
+ } else if (r.setAdj >= service.BACKUP_APP_ADJ) {
+ oomAdj = buildOomTag("bckup", null, r.setAdj,
+ service.BACKUP_APP_ADJ);
+ } else if (r.setAdj >= service.VISIBLE_APP_ADJ) {
+ oomAdj = buildOomTag("vis ", null, r.setAdj,
+ service.VISIBLE_APP_ADJ);
+ } else if (r.setAdj >= service.FOREGROUND_APP_ADJ) {
+ oomAdj = buildOomTag("fore ", null, r.setAdj,
+ service.FOREGROUND_APP_ADJ);
+ } else if (r.setAdj >= CORE_SERVER_ADJ) {
+ oomAdj = buildOomTag("core ", null, r.setAdj,
+ CORE_SERVER_ADJ);
+ } else if (r.setAdj >= SYSTEM_ADJ) {
+ oomAdj = buildOomTag("sys ", null, r.setAdj,
+ SYSTEM_ADJ);
+ } else {
+ oomAdj = Integer.toString(r.setAdj);
+ }
+ String schedGroup;
+ switch (r.setSchedGroup) {
+ case Process.THREAD_GROUP_BG_NONINTERACTIVE:
+ schedGroup = "B";
+ break;
+ case Process.THREAD_GROUP_DEFAULT:
+ schedGroup = "F";
+ break;
+ default:
+ schedGroup = Integer.toString(r.setSchedGroup);
+ break;
+ }
+ pw.println(String.format("%s%s #%2d: adj=%s/%s %s (%s)",
prefix, (r.persistent ? persistentLabel : normalLabel),
- i, r.setAdj, r.setSchedGroup, r.toString(), r.adjType));
+ i, oomAdj, schedGroup, r.toString(), r.adjType));
if (r.adjSource != null || r.adjTarget != null) {
pw.println(prefix + " " + r.adjTarget
+ " used by " + r.adjSource);
@@ -13038,6 +13094,7 @@
if (app.thread == null) {
app.adjSeq = mAdjSeq;
+ app.curSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
return (app.curAdj=EMPTY_APP_ADJ);
}
@@ -13058,52 +13115,63 @@
// Determine the importance of the process, starting with most
// important to least, and assign an appropriate OOM adjustment.
int adj;
+ int schedGroup;
int N;
if (app == TOP_APP) {
// The last app on the list is the foreground app.
adj = FOREGROUND_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "top-activity";
} else if (app.instrumentationClass != null) {
// Don't want to kill running instrumentation.
adj = FOREGROUND_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "instrumentation";
} else if (app.persistentActivities > 0) {
// Special persistent activities... shouldn't be used these days.
adj = FOREGROUND_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "persistent";
} else if (app.curReceiver != null ||
(mPendingBroadcast != null && mPendingBroadcast.curApp == app)) {
// An app that is currently receiving a broadcast also
// counts as being in the foreground.
adj = FOREGROUND_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "broadcast";
} else if (app.executingServices.size() > 0) {
// An app that is currently executing a service callback also
// counts as being in the foreground.
adj = FOREGROUND_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "exec-service";
} else if (app.foregroundServices) {
// The user is aware of this app, so make it visible.
adj = VISIBLE_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "foreground-service";
} else if (app.forcingToForeground != null) {
// The user is aware of this app, so make it visible.
adj = VISIBLE_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "force-foreground";
app.adjSource = app.forcingToForeground;
} else if (app == mHomeProcess) {
// This process is hosting what we currently consider to be the
// home app, so we don't want to let it go into the background.
adj = HOME_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.adjType = "home";
} else if ((N=app.activities.size()) != 0) {
// This app is in the background with paused activities.
adj = hiddenAdj;
+ schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.adjType = "bg-activities";
for (int j=0; j<N; j++) {
if (((HistoryRecord)app.activities.get(j)).visible) {
// This app has a visible activity!
adj = VISIBLE_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "visible";
break;
}
@@ -13111,6 +13179,7 @@
} else {
// A very not-needed process.
adj = EMPTY_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.adjType = "empty";
}
@@ -13120,7 +13189,6 @@
// infinite recursion.
app.adjSeq = mAdjSeq;
app.curRawAdj = adj;
- app.curAdj = adj <= app.maxAdj ? adj : app.maxAdj;
if (mBackupTarget != null && app == mBackupTarget.app) {
// If possible we want to avoid killing apps while they're being backed up
@@ -13131,7 +13199,8 @@
}
}
- if (app.services.size() != 0 && adj > FOREGROUND_APP_ADJ) {
+ if (app.services.size() != 0 && (adj > FOREGROUND_APP_ADJ
+ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
final long now = SystemClock.uptimeMillis();
// This process is more important if the top activity is
// bound to the service.
@@ -13149,7 +13218,8 @@
}
}
}
- if (s.connections.size() > 0 && adj > FOREGROUND_APP_ADJ) {
+ if (s.connections.size() > 0 && (adj > FOREGROUND_APP_ADJ
+ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
Iterator<ConnectionRecord> kt
= s.connections.values().iterator();
while (kt.hasNext() && adj > FOREGROUND_APP_ADJ) {
@@ -13181,6 +13251,11 @@
app.adjSource = cr.binding.client;
app.adjTarget = s.serviceInfo.name;
}
+ if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
+ if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
+ }
+ }
}
HistoryRecord a = cr.activity;
//if (a != null) {
@@ -13190,6 +13265,7 @@
(a.state == ActivityState.RESUMED
|| a.state == ActivityState.PAUSING)) {
adj = FOREGROUND_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "service";
app.adjTypeCode = ActivityManager.RunningAppProcessInfo
.REASON_SERVICE_IN_USE;
@@ -13211,9 +13287,11 @@
}
}
- if (app.pubProviders.size() != 0 && adj > FOREGROUND_APP_ADJ) {
+ if (app.pubProviders.size() != 0 && (adj > FOREGROUND_APP_ADJ
+ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
Iterator jt = app.pubProviders.values().iterator();
- while (jt.hasNext() && adj > FOREGROUND_APP_ADJ) {
+ while (jt.hasNext() && (adj > FOREGROUND_APP_ADJ
+ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
ContentProviderRecord cpr = (ContentProviderRecord)jt.next();
if (cpr.clients.size() != 0) {
Iterator<ProcessRecord> kt = cpr.clients.iterator();
@@ -13242,6 +13320,9 @@
app.adjSource = client;
app.adjTarget = cpr.info.name;
}
+ if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
+ }
}
}
// If the provider has external (non-framework) process
@@ -13250,6 +13331,7 @@
if (cpr.externals != 0) {
if (adj > FOREGROUND_APP_ADJ) {
adj = FOREGROUND_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "provider";
app.adjTarget = cpr.info.name;
}
@@ -13272,12 +13354,13 @@
// " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
if (adj > app.maxAdj) {
adj = app.maxAdj;
+ if (app.maxAdj <= VISIBLE_APP_ADJ) {
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
+ }
}
app.curAdj = adj;
- app.curSchedGroup = adj > VISIBLE_APP_ADJ
- ? Process.THREAD_GROUP_BG_NONINTERACTIVE
- : Process.THREAD_GROUP_DEFAULT;
+ app.curSchedGroup = schedGroup;
return adj;
}
@@ -13422,7 +13505,7 @@
int adj = computeOomAdjLocked(app, hiddenAdj, TOP_APP);
- if (app.pid != 0 && app.pid != MY_PID) {
+ if ((app.pid != 0 && app.pid != MY_PID) || Process.supportsProcesses()) {
if (app.curRawAdj != app.setRawAdj) {
if (app.curRawAdj > FOREGROUND_APP_ADJ
&& app.setRawAdj <= FOREGROUND_APP_ADJ) {
diff --git a/tests/LotsOfApps/Android.mk b/tests/LotsOfApps/Android.mk
index 3019f5c..8d0cfa3 100644
--- a/tests/LotsOfApps/Android.mk
+++ b/tests/LotsOfApps/Android.mk
@@ -1,7 +1,7 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 1ac13f2..cf70121 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -37,7 +37,7 @@
mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
mUpdate(false), mExtending(false),
mRequireLocalization(false), mPseudolocalize(false),
- mValues(false),
+ mUTF8(false), mValues(false),
mCompressionMethod(0), mOutputAPKFile(NULL),
mAssetSourceDir(NULL), mProguardFile(NULL),
mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
@@ -76,6 +76,8 @@
void setRequireLocalization(bool val) { mRequireLocalization = val; }
bool getPseudolocalize(void) const { return mPseudolocalize; }
void setPseudolocalize(bool val) { mPseudolocalize = val; }
+ bool getUTF8(void) const { return mUTF8; }
+ void setUTF8(bool val) { mUTF8 = val; }
bool getValues(void) const { return mValues; }
void setValues(bool val) { mValues = val; }
int getCompressionMethod(void) const { return mCompressionMethod; }
@@ -161,6 +163,7 @@
bool mExtending;
bool mRequireLocalization;
bool mPseudolocalize;
+ bool mUTF8;
bool mValues;
int mCompressionMethod;
bool mJunkPath;
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 1a536d6..ff9cc11 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -412,6 +412,7 @@
}
tree.restart();
printXMLBlock(&tree);
+ tree.uninit();
delete asset;
asset = NULL;
}
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 98286c0..bd03b74 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -118,6 +118,7 @@
" -P specify where to output public resource definitions\n"
" -S directory in which to find resources. Multiple directories will be scanned"
" and the first match found (left to right) will take precedence."
+ " -8 Encode string resources in UTF-8.\n"
" -0 specifies an additional extension for which such files will not\n"
" be stored compressed in the .apk. An empty string means to not\n"
" compress any files at all.\n"
@@ -370,6 +371,9 @@
bundle.setCompressionMethod(ZipEntry::kCompressStored);
}
break;
+ case '8':
+ bundle.setUTF8(true);
+ break;
case '-':
if (strcmp(cp, "-min-sdk-version") == 0) {
argc--;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index fdcada4..d04a873 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -613,6 +613,12 @@
NOISY(printf("Found %d included resource packages\n", (int)table.size()));
+ // Standard flags for compiled XML and optional UTF-8 encoding
+ int xmlFlags = XML_COMPILE_STANDARD_RESOURCE;
+ if (bundle->getUTF8()) {
+ xmlFlags |= XML_COMPILE_UTF8;
+ }
+
// --------------------------------------------------------------
// First, gather all resource information.
// --------------------------------------------------------------
@@ -763,7 +769,7 @@
ResourceDirIterator it(layouts, String8("layout"));
while ((err=it.next()) == NO_ERROR) {
String8 src = it.getFile()->getPrintableSource();
- err = compileXmlFile(assets, it.getFile(), &table);
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
if (err == NO_ERROR) {
ResXMLTree block;
block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
@@ -782,7 +788,7 @@
if (anims != NULL) {
ResourceDirIterator it(anims, String8("anim"));
while ((err=it.next()) == NO_ERROR) {
- err = compileXmlFile(assets, it.getFile(), &table);
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
if (err != NO_ERROR) {
hasErrors = true;
}
@@ -797,7 +803,7 @@
if (xmls != NULL) {
ResourceDirIterator it(xmls, String8("xml"));
while ((err=it.next()) == NO_ERROR) {
- err = compileXmlFile(assets, it.getFile(), &table);
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
if (err != NO_ERROR) {
hasErrors = true;
}
@@ -819,7 +825,7 @@
if (colors != NULL) {
ResourceDirIterator it(colors, String8("color"));
while ((err=it.next()) == NO_ERROR) {
- err = compileXmlFile(assets, it.getFile(), &table);
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
if (err != NO_ERROR) {
hasErrors = true;
}
@@ -835,7 +841,7 @@
ResourceDirIterator it(menus, String8("menu"));
while ((err=it.next()) == NO_ERROR) {
String8 src = it.getFile()->getPrintableSource();
- err = compileXmlFile(assets, it.getFile(), &table);
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
if (err != NO_ERROR) {
hasErrors = true;
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 19b9b01..a9cbd11 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -39,6 +39,10 @@
root->removeWhitespace(false, NULL);
}
+ if ((options&XML_COMPILE_UTF8) != 0) {
+ root->setUTF8(true);
+ }
+
bool hasErrors = false;
if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
@@ -2505,7 +2509,7 @@
// Iterate through all data, collecting all values (strings,
// references, etc).
- StringPool valueStrings;
+ StringPool valueStrings = StringPool(false, bundle->getUTF8());
for (pi=0; pi<N; pi++) {
sp<Package> p = mOrderedPackages.itemAt(pi);
if (p->getTypes().size() == 0) {
@@ -2513,8 +2517,8 @@
continue;
}
- StringPool typeStrings;
- StringPool keyStrings;
+ StringPool typeStrings = StringPool(false, bundle->getUTF8());
+ StringPool keyStrings = StringPool(false, bundle->getUTF8());
const size_t N = p->getOrderedTypes().size();
for (size_t ti=0; ti<N; ti++) {
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index caa01b3..cfa75a71 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -24,6 +24,7 @@
XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
XML_COMPILE_STRIP_WHITESPACE = 1<<3,
XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
+ XML_COMPILE_UTF8 = 1<<5,
XML_COMPILE_STANDARD_RESOURCE =
XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 715170a..ec58591 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -30,8 +30,8 @@
}
}
-StringPool::StringPool(bool sorted)
- : mSorted(sorted), mValues(-1), mIdents(-1)
+StringPool::StringPool(bool sorted, bool utf8)
+ : mSorted(sorted), mUTF8(utf8), mValues(-1), mIdents(-1)
{
}
@@ -165,6 +165,16 @@
return err == NO_ERROR ? pool : NULL;
}
+#define ENCODE_LENGTH(str, chrsz, strSize) \
+{ \
+ size_t maxMask = 1 << ((chrsz*8)-1); \
+ size_t maxSize = maxMask-1; \
+ if (strSize > maxSize) { \
+ *str++ = maxMask | ((strSize>>(chrsz*8))&maxSize); \
+ } \
+ *str++ = strSize; \
+}
+
status_t StringPool::writeStringBlock(const sp<AaptFile>& pool)
{
// Allow appending. Sorry this is a little wacky.
@@ -213,28 +223,53 @@
return NO_MEMORY;
}
+ const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(char16_t);
+
size_t strPos = 0;
for (i=0; i<STRINGS; i++) {
entry& ent = mEntries.editItemAt(i);
const size_t strSize = (ent.value.size());
- const size_t lenSize = strSize > 0x7fff ? sizeof(uint32_t) : sizeof(uint16_t);
- const size_t totalSize = lenSize + ((strSize+1)*sizeof(uint16_t));
+ const size_t lenSize = strSize > (size_t)(1<<((charSize*8)-1))-1 ?
+ charSize*2 : charSize;
+
+ String8 encStr;
+ if (mUTF8) {
+ encStr = String8(ent.value);
+ }
+
+ const size_t encSize = mUTF8 ? encStr.size() : 0;
+ const size_t encLenSize = mUTF8 ?
+ (encSize > (size_t)(1<<((charSize*8)-1))-1 ?
+ charSize*2 : charSize) : 0;
ent.offset = strPos;
- uint16_t* dat = (uint16_t*)pool->editData(preSize + strPos + totalSize);
+
+ const size_t totalSize = lenSize + encLenSize +
+ ((mUTF8 ? encSize : strSize)+1)*charSize;
+
+ void* dat = (void*)pool->editData(preSize + strPos + totalSize);
if (dat == NULL) {
fprintf(stderr, "ERROR: Out of memory for string pool\n");
return NO_MEMORY;
}
- dat += (preSize+strPos)/sizeof(uint16_t);
- if (lenSize > sizeof(uint16_t)) {
- *dat = htods(0x8000 | ((strSize>>16)&0x7fff));
- dat++;
- }
- *dat++ = htods(strSize);
- strcpy16_htod(dat, ent.value);
+ dat = (uint8_t*)dat + preSize + strPos;
+ if (mUTF8) {
+ uint8_t* strings = (uint8_t*)dat;
- strPos += lenSize + (strSize+1)*sizeof(uint16_t);
+ ENCODE_LENGTH(strings, sizeof(uint8_t), strSize)
+
+ ENCODE_LENGTH(strings, sizeof(uint8_t), encSize)
+
+ strncpy((char*)strings, encStr, encSize+1);
+ } else {
+ uint16_t* strings = (uint16_t*)dat;
+
+ ENCODE_LENGTH(strings, sizeof(uint16_t), strSize)
+
+ strcpy16_htod(strings, ent.value);
+ }
+
+ strPos += totalSize;
}
// Pad ending string position up to a uint32_t boundary.
@@ -312,6 +347,9 @@
if (mSorted) {
header->flags |= htodl(ResStringPool_header::SORTED_FLAG);
}
+ if (mUTF8) {
+ header->flags |= htodl(ResStringPool_header::UTF8_FLAG);
+ }
header->stringsStart = htodl(preSize);
header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0);
diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h
index 9082b37..7275259 100644
--- a/tools/aapt/StringPool.h
+++ b/tools/aapt/StringPool.h
@@ -68,8 +68,11 @@
* lookup with ResStringPool::indexOfString() (O(log n)), at the expense
* of support for styled string entries (which requires the same string
* be included multiple times in the pool).
+ *
+ * If 'utf8' is true, strings will be encoded with UTF-8 instead of
+ * left in Java's native UTF-16.
*/
- explicit StringPool(bool sorted = false);
+ explicit StringPool(bool sorted = false, bool utf8 = false);
/**
* Add a new string to the pool. If mergeDuplicates is true, thenif
@@ -123,6 +126,7 @@
private:
const bool mSorted;
+ const bool mUTF8;
// Raw array of unique strings, in some arbitrary order.
Vector<entry> mEntries;
// Array of indices into mEntries, in the order they were
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index d4d2a45c..036dde4 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -478,6 +478,7 @@
, mFilename(filename)
, mStartLineNumber(0)
, mEndLineNumber(0)
+ , mUTF8(false)
{
if (isNamespace) {
mNamespacePrefix = s1;
@@ -837,7 +838,7 @@
status_t XMLNode::flatten(const sp<AaptFile>& dest,
bool stripComments, bool stripRawValues) const
{
- StringPool strings;
+ StringPool strings = StringPool(false, mUTF8);
Vector<uint32_t> resids;
// First collect just the strings for attribute names that have a
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index a9bea43..dc92fa7 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -124,6 +124,8 @@
void removeWhitespace(bool stripAll=true, const char** cDataTags=NULL);
+ void setUTF8(bool val) { mUTF8 = val; }
+
status_t parseValues(const sp<AaptAssets>& assets, ResourceTable* table);
status_t assignResourceIds(const sp<AaptAssets>& assets,
@@ -189,6 +191,9 @@
String8 mFilename;
int32_t mStartLineNumber;
int32_t mEndLineNumber;
+
+ // Encode compiled XML with UTF-8 StringPools?
+ bool mUTF8;
};
#endif
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 66552e4..0b9c69c 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -1162,7 +1162,7 @@
}
}
break;
-
+
case EVENT_DRIVER_STATE_CHANGED:
// Wi-Fi driver state changed:
// 0 STARTED