Merge "Fix SimUtilsTest after changing in bcdToString."
diff --git a/Android.mk b/Android.mk
index 50e05d9..b4090a7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -436,10 +436,10 @@
framework_docs_SDK_PREVIEW:=0
## Latest ADT version identifiers, for reference from published docs
-framework_docs_ADT_VERSION:=0.9.7
-framework_docs_ADT_DOWNLOAD:=ADT-0.9.7.zip
-framework_docs_ADT_BYTES:=8033750
-framework_docs_ADT_CHECKSUM:=de2431c8d4786d127ae5bfc95b4605df
+framework_docs_ADT_VERSION:=0.9.8
+framework_docs_ADT_DOWNLOAD:=ADT-0.9.8.zip
+framework_docs_ADT_BYTES:=8703591
+framework_docs_ADT_CHECKSUM:=22070f8e52924605a3b3abf87c1ba39f
framework_docs_LOCAL_DROIDDOC_OPTIONS += \
-hdf sdk.version $(framework_docs_SDK_VERSION) \
diff --git a/api/current.xml b/api/current.xml
index 3319a98..589ec10 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2088,6 +2088,39 @@
visibility="public"
>
</field>
+<field name="actionBarTabBarStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843572"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionBarTabStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843571"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionBarTabTextStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843573"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="actionButtonPadding"
type="int"
transient="false"
@@ -2143,6 +2176,17 @@
visibility="public"
>
</field>
+<field name="actionOverflowButtonStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843574"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="activityCloseEnterAnimation"
type="int"
transient="false"
@@ -4200,6 +4244,17 @@
visibility="public"
>
</field>
+<field name="filterTouchesWhenObscured"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843460"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="finishOnCloseSystemDialogs"
type="int"
transient="false"
@@ -6158,50 +6213,6 @@
visibility="public"
>
</field>
-<field name="kraken_resource_pad61"
- type="int"
- transient="false"
- volatile="false"
- value="16843460"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="kraken_resource_pad62"
- type="int"
- transient="false"
- volatile="false"
- value="16843459"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="kraken_resource_pad63"
- type="int"
- transient="false"
- volatile="false"
- value="16843458"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="kraken_resource_pad64"
- type="int"
- transient="false"
- volatile="false"
- value="16843457"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="kraken_resource_pad7"
type="int"
transient="false"
@@ -150355,6 +150366,17 @@
visibility="public"
>
</field>
+<field name="INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.media.action.MEDIA_PLAY_FROM_SEARCH""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="INTENT_ACTION_MEDIA_SEARCH"
type="java.lang.String"
transient="false"
@@ -191959,6 +191981,17 @@
visibility="public"
>
</method>
+<method name="getFlags"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getHistoricalEventTime"
return="long"
abstract="false"
@@ -192596,6 +192629,8 @@
</parameter>
<parameter name="source" type="int">
</parameter>
+<parameter name="flags" type="int">
+</parameter>
</method>
<method name="obtain"
return="android.view.MotionEvent"
@@ -193052,6 +193087,17 @@
visibility="public"
>
</field>
+<field name="FLAG_WINDOW_IS_OBSCURED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
</class>
<class name="MotionEvent.PointerCoords"
extends="java.lang.Object"
@@ -195842,6 +195888,17 @@
visibility="public"
>
</method>
+<method name="getFilterTouchesWhenObscured"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getFocusables"
return="java.util.ArrayList<android.view.View>"
abstract="false"
@@ -197273,6 +197330,19 @@
<parameter name="canvas" type="android.graphics.Canvas">
</parameter>
</method>
+<method name="onFilterTouchEventForSecurity"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
<method name="onFinishInflate"
return="void"
abstract="false"
@@ -198170,6 +198240,19 @@
<parameter name="length" type="int">
</parameter>
</method>
+<method name="setFilterTouchesWhenObscured"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enabled" type="boolean">
+</parameter>
+</method>
<method name="setFocusable"
return="void"
abstract="false"
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index d1bc9bdc..69ad67e 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -520,6 +520,10 @@
staggerDelay = 0;
final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
+ if (!observer.isAlive()) {
+ // If the observer's not in a good state, skip the transition
+ return;
+ }
int numChildren = parent.getChildCount();
for (int i = 0; i < numChildren; ++i) {
@@ -607,7 +611,7 @@
// layout listeners.
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
- observer.removeOnPreDrawListener(this);
+ parent.getViewTreeObserver().removeOnPreDrawListener(this);
int numChildren = parent.getChildCount();
for (int i = 0; i < numChildren; ++i) {
final View child = parent.getChildAt(i);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 359eaaa..4d0b8b0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1577,6 +1577,30 @@
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK";
/**
+ * Broadcast Action: A sticky broadcast that indicates a memory full
+ * condition on the device. This is intended for activities that want
+ * to be able to fill the data partition completely, leaving only
+ * enough free space to prevent system-wide SQLite failures.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_STORAGE_FULL = "android.intent.action.DEVICE_STORAGE_FULL";
+ /**
+ * Broadcast Action: Indicates memory full condition on the device
+ * no longer exists.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_STORAGE_NOT_FULL = "android.intent.action.DEVICE_STORAGE_NOT_FULL";
+ /**
* Broadcast Action: Indicates low memory condition notification acknowledged by user
* and package management should be started.
* This is triggered by the user from the ACTION_DEVICE_STORAGE_LOW
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 05ebd07..653e353 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -105,6 +105,14 @@
* it with {@link android.content.Intent#getStringExtra(String)}.
*/
public static final String EXTRA_EXTRA_INFO = "extraInfo";
+ /**
+ * The lookup key for an int that provides information about
+ * our connection to the internet at large. 0 indicates no connection,
+ * 100 indicates a great connection. Retrieve it with
+ * {@link android.content.Intent@getIntExtra(String)}.
+ * {@hide}
+ */
+ public static final String EXTRA_INET_CONDITION = "inetCondition";
/**
* Broadcast Action: The setting for background data usage has changed
@@ -575,4 +583,16 @@
return false;
}
}
+
+ /*
+ * @param networkType The type of network you want to report on
+ * @param percentage The quality of the connection 0 is bad, 100 is good
+ * {@hide}
+ */
+ public void reportInetCondition(int networkType, int percentage) {
+ try {
+ mService.reportInetCondition(networkType, percentage);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index f31fb75..9f2fc17 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -78,4 +78,6 @@
String[] getTetherableBluetoothRegexs();
void requestNetworkTransitionWakelock(in String forWhom);
+
+ void reportInetCondition(int networkType, int percentage);
}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 420992b..5420d8f 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -71,6 +71,18 @@
public static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 7;
/**
+ * msg.arg1 = network type
+ * msg.arg2 = condition (0 bad, 100 good)
+ */
+ public static final int EVENT_INET_CONDITION_CHANGE = 8;
+
+ /**
+ * msg.arg1 = network type
+ * msg.arg2 = default connection sequence number
+ */
+ public static final int EVENT_INET_CONDITION_HOLD_END = 9;
+
+ /**
* -------------------------------------------------------------
* Control Interface
* -------------------------------------------------------------
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index e2e4d2c..9c63cd9 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -46,6 +46,8 @@
// Set to true to enable extra debugging.
private static final boolean DEBUG = false;
+ // Used to notify an app that's caching the default connection proxy
+ // that either the default connection or its proxy has changed
public static final String PROXY_CHANGE_ACTION =
"android.intent.action.PROXY_CHANGE";
@@ -74,9 +76,10 @@
EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
}
+ // useful because it holds the processed exclusion list - don't want to reparse it each time
private static class ProxySpec {
String[] exclusionList = null;
- InetSocketAddress proxyAddress = null;
+ InetSocketAddress address = null;
public ProxySpec() { };
}
@@ -151,7 +154,7 @@
retval = java.net.Proxy.NO_PROXY;
} else {
retval =
- new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.proxyAddress);
+ new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.address);
}
} else {
// If network is WiFi, return no proxy.
@@ -187,7 +190,7 @@
parseGlobalProxyInfoReadLocked(ctx);
}
if (sGlobalProxySpec != null) {
- InetSocketAddress sa = sGlobalProxySpec.proxyAddress;
+ InetSocketAddress sa = sGlobalProxySpec.address;
return sa.getHostName();
}
return getDefaultHost();
@@ -210,7 +213,7 @@
parseGlobalProxyInfoReadLocked(ctx);
}
if (sGlobalProxySpec != null) {
- InetSocketAddress sa = sGlobalProxySpec.proxyAddress;
+ InetSocketAddress sa = sGlobalProxySpec.address;
return sa.getPort();
}
return getDefaultPort();
@@ -389,7 +392,7 @@
int port = parsePort(proxyHost);
if (proxyHost != null) {
sGlobalProxySpec = new ProxySpec();
- sGlobalProxySpec.proxyAddress = new InetSocketAddress(host, port);
+ sGlobalProxySpec.address= new InetSocketAddress(host, port);
if ((exclusionListSpec != null) && (exclusionListSpec.length() != 0)) {
String[] exclusionListEntries = exclusionListSpec.toLowerCase().split(",");
String[] processedEntries = new String[exclusionListEntries.length];
diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java
index bce7ec0..fb59fed 100644
--- a/core/java/android/net/ProxyProperties.java
+++ b/core/java/android/net/ProxyProperties.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
import java.net.UnknownHostException;
/**
@@ -29,8 +30,7 @@
*/
public class ProxyProperties implements Parcelable {
- private InetAddress mProxy;
- private int mPort;
+ private InetSocketAddress mProxy;
private String mExclusionList;
public ProxyProperties() {
@@ -39,8 +39,7 @@
// copy constructor instead of clone
public ProxyProperties(ProxyProperties source) {
if (source != null) {
- mProxy = source.getAddress();
- mPort = source.getPort();
+ mProxy = source.getSocketAddress();
String exclusionList = source.getExclusionList();
if (exclusionList != null) {
mExclusionList = new String(exclusionList);
@@ -48,22 +47,14 @@
}
}
- public InetAddress getAddress() {
+ public InetSocketAddress getSocketAddress() {
return mProxy;
}
- public void setAddress(InetAddress proxy) {
+ public void setSocketAddress(InetSocketAddress proxy) {
mProxy = proxy;
}
- public int getPort() {
- return mPort;
- }
-
- public void setPort(int port) {
- mPort = port;
- }
-
public String getExclusionList() {
return mExclusionList;
}
@@ -76,7 +67,7 @@
public String toString() {
StringBuilder sb = new StringBuilder();
if (mProxy != null) {
- sb.append(mProxy.getHostAddress()).append(":").append(mPort);
+ sb.append(mProxy.toString());
if (mExclusionList != null) {
sb.append(" xl=").append(mExclusionList);
}
@@ -98,13 +89,15 @@
*/
public void writeToParcel(Parcel dest, int flags) {
if (mProxy != null) {
- dest.writeByte((byte)1);
- dest.writeString(mProxy.getHostName());
- dest.writeByteArray(mProxy.getAddress());
+ InetAddress addr = mProxy.getAddress();
+ if (addr != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(addr.getAddress());
+ dest.writeInt(mProxy.getPort());
+ }
} else {
dest.writeByte((byte)0);
}
- dest.writeInt(mPort);
dest.writeString(mExclusionList);
}
@@ -118,11 +111,10 @@
ProxyProperties proxyProperties = new ProxyProperties();
if (in.readByte() == 1) {
try {
- proxyProperties.setAddress(InetAddress.getByAddress(in.readString(),
- in.createByteArray()));
- } catch (UnknownHostException e) {}
+ InetAddress addr = InetAddress.getByAddress(in.createByteArray());
+ proxyProperties.setSocketAddress(new InetSocketAddress(addr, in.readInt()));
+ } catch (UnknownHostException e) { }
}
- proxyProperties.setPort(in.readInt());
proxyProperties.setExclusionList(in.readString());
return proxyProperties;
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a857e58..32fb108 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1586,8 +1586,10 @@
sb.append(prefix); sb.append(" CPU: ");
formatTime(sb, userTime); sb.append("usr + ");
formatTime(sb, systemTime); sb.append("krn\n");
- sb.append(prefix); sb.append(" "); sb.append(starts);
- sb.append(" proc starts");
+ if (starts != 0) {
+ sb.append(prefix); sb.append(" "); sb.append(starts);
+ sb.append(" proc starts");
+ }
pw.println(sb.toString());
for (int e=0; e<numExcessive; e++) {
Uid.Proc.ExcessiveWake ew = ps.getExcessiveWake(e);
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 9b2b090..2b8a8e6 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -75,6 +75,22 @@
public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
/**
+ * An intent to perform a search for music media and automatically play content from the
+ * result when possible. This can be fired, for example, by the result of a voice recognition
+ * command to listen to music.
+ * <p>
+ * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string
+ * that can contain any type of unstructured music search, like the name of an artist,
+ * an album, a song, a genre, or any combination of these.
+ * <p>
+ * Because this intent includes an open-ended unstructured search string, it makes the most
+ * sense for apps that can support large-scale search of music, such as services connected
+ * to an online database of music which can be streamed and played on the device.
+ */
+ public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
+ "android.media.action.MEDIA_PLAY_FROM_SEARCH";
+
+ /**
* The name of the Intent-extra used to define the artist
*/
public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dcd1d94..6af6a81 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3005,31 +3005,31 @@
public static final String WTF_IS_FATAL = "wtf_is_fatal";
/**
- * Maximum age of entries kept by {@link android.os.IDropBox}.
+ * Maximum age of entries kept by {@link com.android.internal.os.IDropBoxManagerService}.
* @hide
*/
public static final String DROPBOX_AGE_SECONDS =
"dropbox_age_seconds";
/**
- * Maximum number of entry files which {@link android.os.IDropBox} will keep around.
+ * Maximum number of entry files which {@link com.android.internal.os.IDropBoxManagerService} will keep around.
* @hide
*/
public static final String DROPBOX_MAX_FILES =
"dropbox_max_files";
/**
- * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what.
+ * Maximum amount of disk space used by {@link com.android.internal.os.IDropBoxManagerService} no matter what.
* @hide
*/
public static final String DROPBOX_QUOTA_KB =
"dropbox_quota_kb";
/**
- * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use.
+ * Percent of free disk (excluding reserve) which {@link com.android.internal.os.IDropBoxManagerService} will use.
* @hide
*/
public static final String DROPBOX_QUOTA_PERCENT =
"dropbox_quota_percent";
/**
- * Percent of total disk which {@link android.os.IDropBox} will never dip into.
+ * Percent of total disk which {@link com.android.internal.os.IDropBoxManagerService} will never dip into.
* @hide
*/
public static final String DROPBOX_RESERVE_PERCENT =
@@ -3089,6 +3089,15 @@
"sys_storage_threshold_percentage";
/**
+ * Minimum bytes of free storage on the device before the data
+ * partition is considered full. By default, 1 MB is reserved
+ * to avoid system-wide SQLite disk full exceptions.
+ * @hide
+ */
+ public static final String SYS_STORAGE_FULL_THRESHOLD_BYTES =
+ "sys_storage_full_threshold_bytes";
+
+ /**
* The interval in milliseconds after which Wi-Fi is considered idle.
* When idle, it is possible for the device to be switched from Wi-Fi to
* the mobile data network.
@@ -3462,6 +3471,21 @@
public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE =
"download_manager_max_bytes_over_mobile";
+ /**
+ * ms during which to consume extra events related to Inet connection condition
+ * after a transtion to fully-connected
+ * @hide
+ */
+ public static final String INET_CONDITION_DEBOUNCE_UP_DELAY =
+ "inet_condition_debounce_up_delay";
+
+ /**
+ * ms during which to consume extra events related to Inet connection condtion
+ * after a transtion to partly-connected
+ * @hide
+ */
+ public static final String INET_CONDITION_DEBOUNCE_DOWN_DELAY =
+ "inet_condition_debounce_down_delay";
/**
* @hide
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 57a72bf..2b083dc 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -178,6 +179,9 @@
};
final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
+ {
+ mRequestedFormat = PixelFormat.RGB_565;
+ }
@Override
public boolean onAllowLockCanvas() {
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index 5be2a48..09cbbb8 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -246,8 +246,10 @@
private void initPrefs(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
mResolver = new WeakReference<ContentResolver>(contentResolver);
- mObserver = new SettingsObserver();
- contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
+ if (mObserver == null) {
+ mObserver = new SettingsObserver();
+ contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
+ }
updatePrefs(contentResolver);
mPrefsInited = true;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 74318ba..78b9b5d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -30,6 +30,7 @@
*/
public final class MotionEvent extends InputEvent implements Parcelable {
private static final long MS_PER_NS = 1000000;
+ private static final boolean TRACK_RECYCLED_LOCATION = false;
/**
* Bit mask of the parts of the action code that are the action itself.
@@ -155,7 +156,17 @@
@Deprecated
public static final int ACTION_POINTER_ID_SHIFT = 8;
- private static final boolean TRACK_RECYCLED_LOCATION = false;
+ /**
+ * This flag indicates that the window that received this motion event is partly
+ * or wholly obscured by another visible window above it. This flag is set to true
+ * even if the event did not directly pass through the obscured area.
+ * A security sensitive application can check this flag to identify situations in which
+ * a malicious application may have covered up part of its content for the purpose
+ * of misleading the user or hijacking touches. An appropriate response might be
+ * to drop the suspect touches or to take additional precautions to confirm the user's
+ * actual intent.
+ */
+ public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;
/**
* Flag indicating the motion event intersected the top edge of the screen.
@@ -251,6 +262,7 @@
private float mYPrecision;
private int mEdgeFlags;
private int mMetaState;
+ private int mFlags;
private int mNumPointers;
private int mNumSamples;
@@ -338,20 +350,22 @@
* @param deviceId The id for the device that this event came from. An id of
* zero indicates that the event didn't come from a physical device; other
* numbers are arbitrary and you shouldn't depend on the values.
- * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
* MotionEvent.
* @param source The source of this event.
+ * @param flags The motion event flags.
*/
static public MotionEvent obtain(long downTime, long eventTime,
int action, int pointers, int[] pointerIds, PointerCoords[] pointerCoords,
int metaState, float xPrecision, float yPrecision, int deviceId,
- int edgeFlags, int source) {
+ int edgeFlags, int source, int flags) {
MotionEvent ev = obtain(pointers, 1);
ev.mDeviceId = deviceId;
ev.mSource = source;
ev.mEdgeFlags = edgeFlags;
ev.mDownTimeNano = downTime * MS_PER_NS;
ev.mAction = action;
+ ev.mFlags = flags;
ev.mMetaState = metaState;
ev.mXOffset = 0;
ev.mYOffset = 0;
@@ -401,7 +415,7 @@
* @param deviceId The id for the device that this event came from. An id of
* zero indicates that the event didn't come from a physical device; other
* numbers are arbitrary and you shouldn't depend on the values.
- * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
* MotionEvent.
*/
static public MotionEvent obtain(long downTime, long eventTime, int action,
@@ -413,6 +427,7 @@
ev.mEdgeFlags = edgeFlags;
ev.mDownTimeNano = downTime * MS_PER_NS;
ev.mAction = action;
+ ev.mFlags = 0;
ev.mMetaState = metaState;
ev.mXOffset = 0;
ev.mYOffset = 0;
@@ -462,7 +477,7 @@
* @param deviceId The id for the device that this event came from. An id of
* zero indicates that the event didn't come from a physical device; other
* numbers are arbitrary and you shouldn't depend on the values.
- * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
* MotionEvent.
*
* @deprecated Use {@link #obtain(long, long, int, float, float, float, float, int, float, float, int, int)}
@@ -509,6 +524,7 @@
ev.mEdgeFlags = o.mEdgeFlags;
ev.mDownTimeNano = o.mDownTimeNano;
ev.mAction = o.mAction;
+ ev.mFlags = o.mFlags;
ev.mMetaState = o.mMetaState;
ev.mXOffset = o.mXOffset;
ev.mYOffset = o.mYOffset;
@@ -540,6 +556,7 @@
ev.mEdgeFlags = o.mEdgeFlags;
ev.mDownTimeNano = o.mDownTimeNano;
ev.mAction = o.mAction;
+ o.mFlags = o.mFlags;
ev.mMetaState = o.mMetaState;
ev.mXOffset = o.mXOffset;
ev.mYOffset = o.mYOffset;
@@ -651,6 +668,15 @@
}
/**
+ * Gets the motion event flags.
+ *
+ * @see #FLAG_WINDOW_IS_OBSCURED
+ */
+ public final int getFlags() {
+ return mFlags;
+ }
+
+ /**
* Returns the time (in ms) when the user originally pressed down to start
* a stream of position events.
*/
@@ -1285,7 +1311,7 @@
/**
- * Sets the bitfield indicating which edges, if any, where touched by this
+ * Sets the bitfield indicating which edges, if any, were touched by this
* MotionEvent.
*
* @see #getEdgeFlags()
@@ -1480,6 +1506,7 @@
ev.mYPrecision = in.readFloat();
ev.mEdgeFlags = in.readInt();
ev.mMetaState = in.readInt();
+ ev.mFlags = in.readInt();
final int[] pointerIdentifiers = ev.mPointerIdentifiers;
for (int i = 0; i < NP; i++) {
@@ -1521,6 +1548,7 @@
out.writeFloat(mYPrecision);
out.writeInt(mEdgeFlags);
out.writeInt(mMetaState);
+ out.writeInt(mFlags);
final int[] pointerIdentifiers = mPointerIdentifiers;
for (int i = 0; i < NP; i++) {
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index fb88c71..b1fdec8 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -41,6 +41,7 @@
private static final int MAX_AGE_MILLISECONDS = 200;
private static final int POINTER_POOL_CAPACITY = 20;
+ private static final int INVALID_POINTER = -1;
private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
Pools.finitePool(new PoolableManager<VelocityTracker>() {
@@ -76,6 +77,7 @@
private Pointer mPointerListHead; // sorted by id in increasing order
private int mLastTouchIndex;
private int mGeneration;
+ private int mActivePointerId;
private VelocityTracker mNext;
@@ -125,6 +127,7 @@
mPointerListHead = null;
mLastTouchIndex = 0;
+ mActivePointerId = INVALID_POINTER;
}
/**
@@ -180,6 +183,10 @@
// Pointer went down. Add it to the list.
// Write a sentinel at the end of the pastTime trace so we will be able to
// tell when the trace started.
+ if (mActivePointerId == INVALID_POINTER) {
+ // Congratulations! You're the new active pointer!
+ mActivePointerId = pointerId;
+ }
pointer = obtainPointer();
pointer.id = pointerId;
pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
@@ -214,6 +221,7 @@
previousPointer = null;
for (Pointer pointer = mPointerListHead; pointer != null; ) {
final Pointer nextPointer = pointer.next;
+ final int pointerId = pointer.id;
if (pointer.generation != generation) {
// Pointer went up. Remove it from the list.
if (previousPointer == null) {
@@ -222,6 +230,12 @@
previousPointer.next = nextPointer;
}
releasePointer(pointer);
+
+ if (pointerId == mActivePointerId) {
+ // Pick a new active pointer. How is arbitrary.
+ mActivePointerId = mPointerListHead != null ?
+ mPointerListHead.id : INVALID_POINTER;
+ }
} else {
previousPointer = pointer;
}
@@ -334,7 +348,7 @@
* @return The previously computed X velocity.
*/
public float getXVelocity() {
- Pointer pointer = getPointer(0);
+ Pointer pointer = getPointer(mActivePointerId);
return pointer != null ? pointer.xVelocity : 0;
}
@@ -345,7 +359,7 @@
* @return The previously computed Y velocity.
*/
public float getYVelocity() {
- Pointer pointer = getPointer(0);
+ Pointer pointer = getPointer(mActivePointerId);
return pointer != null ? pointer.yVelocity : 0;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f8d79e5..5d258967 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -545,6 +545,28 @@
* take care of redrawing the appropriate views until the animation completes.
* </p>
*
+ * <a name="Security"></a>
+ * <h3>Security</h3>
+ * <p>
+ * Sometimes it is essential that an application be able to verify that an action
+ * is being performed with the full knowledge and consent of the user, such as
+ * granting a permission request, making a purchase or clicking on an advertisement.
+ * Unfortunately, a malicious application could try to spoof the user into
+ * performing these actions, unaware, by concealing the intended purpose of the view.
+ * As a remedy, the framework offers a touch filtering mechanism that can be used to
+ * improve the security of views that provide access to sensitive functionality.
+ * </p><p>
+ * To enable touch filtering, call {@link #setFilterTouchesWhenObscured} or set the
+ * andoird:filterTouchesWhenObscured attribute to true. When enabled, the framework
+ * will discard touches that are received whenever the view's window is obscured by
+ * another visible window. As a result, the view will not receive touches whenever a
+ * toast, dialog or other window appears above the view's window.
+ * </p><p>
+ * For more fine-grained control over security, consider overriding the
+ * {@link #onFilterTouchEventForSecurity} method to implement your own security policy.
+ * See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}.
+ * </p>
+ *
* @attr ref android.R.styleable#View_background
* @attr ref android.R.styleable#View_clickable
* @attr ref android.R.styleable#View_contentDescription
@@ -553,6 +575,7 @@
* @attr ref android.R.styleable#View_id
* @attr ref android.R.styleable#View_fadingEdge
* @attr ref android.R.styleable#View_fadingEdgeLength
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
* @attr ref android.R.styleable#View_fitsSystemWindows
* @attr ref android.R.styleable#View_isScrollContainer
* @attr ref android.R.styleable#View_focusable
@@ -714,7 +737,14 @@
*/
static final int SCROLLBARS_MASK = 0x00000300;
- // note 0x00000400 and 0x00000800 are now available for next flags...
+ /**
+ * Indicates that the view should filter touches when its window is obscured.
+ * Refer to the class comments for more information about this security feature.
+ * {@hide}
+ */
+ static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
+
+ // note flag value 0x00000800 is now available for next flags...
/**
* <p>This view doesn't show fading edges.</p>
@@ -2171,6 +2201,12 @@
viewFlagMasks |= KEEP_SCREEN_ON;
}
break;
+ case R.styleable.View_filterTouchesWhenObscured:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= FILTER_TOUCHES_WHEN_OBSCURED;
+ viewFlagMasks |= FILTER_TOUCHES_WHEN_OBSCURED;
+ }
+ break;
case R.styleable.View_nextFocusLeft:
mNextFocusLeftId = a.getResourceId(attr, View.NO_ID);
break;
@@ -3539,6 +3575,35 @@
setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK);
}
+ /**
+ * Gets whether the framework should discard touches when the view's
+ * window is obscured by another visible window.
+ * Refer to the {@link View} security documentation for more details.
+ *
+ * @return True if touch filtering is enabled.
+ *
+ * @see #setFilterTouchesWhenObscured(boolean)
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+ */
+ @ViewDebug.ExportedProperty
+ public boolean getFilterTouchesWhenObscured() {
+ return (mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0;
+ }
+
+ /**
+ * Sets whether the framework should discard touches when the view's
+ * window is obscured by another visible window.
+ * Refer to the {@link View} security documentation for more details.
+ *
+ * @param enabled True if touch filtering should be enabled.
+ *
+ * @see #getFilterTouchesWhenObscured
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+ */
+ public void setFilterTouchesWhenObscured(boolean enabled) {
+ setFlags(enabled ? 0 : FILTER_TOUCHES_WHEN_OBSCURED,
+ FILTER_TOUCHES_WHEN_OBSCURED);
+ }
/**
* Indicates whether the entire hierarchy under this view will save its
@@ -3990,6 +4055,10 @@
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
+ if (!onFilterTouchEventForSecurity(event)) {
+ return false;
+ }
+
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
@@ -3998,6 +4067,23 @@
}
/**
+ * Filter the touch event to apply security policies.
+ *
+ * @param event The motion event to be filtered.
+ * @return True if the event should be dispatched, false if the event should be dropped.
+ *
+ * @see #getFilterTouchesWhenObscured
+ */
+ public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+ if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
+ && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
+ // Window is obscured, drop this touch.
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Pass a trackball motion event down to the focused view.
*
* @param event The motion event to be dispatched.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 363ccd6..c8b26ef 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -868,12 +868,16 @@
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (!onFilterTouchEventForSecurity(ev)) {
+ return false;
+ }
+
if ((mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS) {
if (mSplitMotionTargets == null) {
mSplitMotionTargets = new SplitMotionTargets();
}
return dispatchSplitTouchEvent(ev);
- }
+ }
final int action = ev.getAction();
final float xf = ev.getX();
@@ -4471,7 +4475,7 @@
return MotionEvent.obtain(downTime, ev.getEventTime(),
action, pointerCount, mPointerIds, mPointerCoords, ev.getMetaState(),
ev.getXPrecision(), ev.getYPrecision(), ev.getDeviceId(), ev.getEdgeFlags(),
- ev.getSource());
+ ev.getSource(), ev.getFlags());
}
static class TargetInfo {
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 659f9cd..76701a9 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -74,6 +74,8 @@
public final static int FLAG_MENU = 0x00000040;
public final static int FLAG_LAUNCHER = 0x00000080;
+ public final static int FLAG_INJECTED = 0x01000000;
+
public final static int FLAG_WOKE_HERE = 0x10000000;
public final static int FLAG_BRIGHT_HERE = 0x20000000;
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index e8d96c5..052a38a 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -339,6 +339,25 @@
return new ViewGroup.LayoutParams(0, 0);
}
+ private void refreshChildren() {
+ for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
+ int index = modulo(i, mNumActiveViews);
+
+ // get the fresh child from the adapter
+ View updatedChild = mAdapter.getView(i, null, this);
+
+ if (mActiveViews[index] != null) {
+ FrameLayout fl = (FrameLayout) mActiveViews[index];
+ // flush out the old child
+ fl.removeAllViewsInLayout();
+ // add the new child to the frame, if it exists
+ if (updatedChild != null) {
+ fl.addView(updatedChild);
+ }
+ }
+ }
+ }
+
void showOnly(int childIndex, boolean animate, boolean onLayout) {
if (mAdapter == null) return;
@@ -414,16 +433,19 @@
// We've cleared a spot for the new view. Get it from the adapter, add it
// and apply any transform / animation
View newView = mAdapter.getView(i, null, this);
+
+ // We wrap the new view in a FrameLayout so as to respect the contract
+ // with the adapter, that is, that we don't modify this view directly
+ FrameLayout fl = new FrameLayout(mContext);
+
+ // If the view from the adapter is null, we still keep an empty frame in place
if (newView != null) {
- // We wrap the new view in a FrameLayout so as to respect the contract
- // with the adapter, that is, that we don't modify this view directly
- FrameLayout fl = new FrameLayout(mContext);
- fl.addView(newView);
- mActiveViews[index] = fl;
- addChild(fl);
- applyTransformForChildAtIndex(fl, newRelativeIndex);
- animateViewForTransition(-1, newRelativeIndex, fl);
+ fl.addView(newView);
}
+ mActiveViews[index] = fl;
+ addChild(fl);
+ applyTransformForChildAtIndex(fl, newRelativeIndex);
+ animateViewForTransition(-1, newRelativeIndex, fl);
}
mActiveViews[index].bringToFront();
}
@@ -523,10 +545,12 @@
// if the data changes, mWhichChild might be out of the bounds of the adapter
// in this case, we reset mWhichChild to the beginning
- if (mWhichChild >= mAdapter.getCount())
+ if (mWhichChild >= mAdapter.getCount()) {
mWhichChild = 0;
- showOnly(mWhichChild, true, true);
+ showOnly(mWhichChild, true, true);
+ }
+ refreshChildren();
}
final int childCount = getChildCount();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dabe7ba..c87264a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4448,7 +4448,7 @@
}
hideControllers();
-
+
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
/*
@@ -5875,7 +5875,10 @@
* Return true iff there is a selection inside this text view.
*/
public boolean hasSelection() {
- return getSelectionStart() != getSelectionEnd();
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ return selectionStart >= 0 && selectionStart != selectionEnd;
}
/**
@@ -6539,7 +6542,7 @@
mShowCursor = SystemClock.uptimeMillis();
ensureEndedBatchEdit();
-
+
if (focused) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
@@ -7068,7 +7071,7 @@
return false;
}
- if (mText.length() > 0 && getSelectionStart() >= 0) {
+ if (mText.length() > 0 && hasSelection()) {
if (mText instanceof Editable && mInput != null) {
return true;
}
@@ -7082,7 +7085,7 @@
return false;
}
- if (mText.length() > 0 && getSelectionStart() >= 0) {
+ if (mText.length() > 0 && hasSelection()) {
return true;
}
@@ -7203,6 +7206,49 @@
int minOffset = selectionModifierCursorController.getMinTouchOffset();
int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
+ if (minOffset == maxOffset) {
+ int offset = Math.max(0, Math.min(minOffset, mTransformed.length()));
+
+ // Tolerance, number of charaters around tapped position
+ final int range = 1;
+ final int max = mTransformed.length() - 1;
+
+ // 'Smart' word selection: detect position between words
+ for (int i = -range; i <= range; i++) {
+ int index = offset + i;
+ if (index >= 0 && index <= max) {
+ if (Character.isSpaceChar(mTransformed.charAt(index))) {
+ // Select current space
+ selectionStart = index;
+ selectionEnd = selectionStart + 1;
+
+ // Extend selection to maximum space range
+ while (selectionStart > 0 &&
+ Character.isSpaceChar(mTransformed.charAt(selectionStart - 1))) {
+ selectionStart--;
+ }
+ while (selectionEnd < max &&
+ Character.isSpaceChar(mTransformed.charAt(selectionEnd))) {
+ selectionEnd++;
+ }
+
+ Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+ return;
+ }
+ }
+ }
+
+ // 'Smart' word selection: detect position at beginning or end of text.
+ if (offset <= range) {
+ Selection.setSelection((Spannable) mText, 0, 0);
+ return;
+ }
+ if (offset >= (max - range)) {
+ Selection.setSelection((Spannable) mText, max + 1, max + 1);
+ return;
+ }
+ }
+
long wordLimits = getWordLimitsAt(minOffset);
if (wordLimits >= 0) {
selectionStart = (int) (wordLimits >>> 32);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index a6a031a..9ed3658 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -22,6 +22,8 @@
import android.net.TrafficStats;
import android.os.BatteryManager;
import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
@@ -79,6 +81,38 @@
private final JournaledFile mFile;
+ static final int MSG_UPDATE_WAKELOCKS = 1;
+ static final int MSG_REPORT_POWER_CHANGE = 2;
+ static final long DELAY_UPDATE_WAKELOCKS = 15*1000;
+
+ public interface BatteryCallback {
+ public void batteryNeedsCpuUpdate();
+ public void batteryPowerChanged(boolean onBattery);
+ }
+
+ final class MyHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ BatteryCallback cb = mCallback;
+ switch (msg.what) {
+ case MSG_UPDATE_WAKELOCKS:
+ if (cb != null) {
+ cb.batteryNeedsCpuUpdate();
+ }
+ break;
+ case MSG_REPORT_POWER_CHANGE:
+ if (cb != null) {
+ cb.batteryPowerChanged(msg.arg1 != 0);
+ }
+ break;
+ }
+ }
+ }
+
+ private final MyHandler mHandler;
+
+ private BatteryCallback mCallback;
+
/**
* The statistics we have collected organized by uids.
*/
@@ -95,6 +129,9 @@
final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers
= new SparseArray<ArrayList<StopwatchTimer>>();
+ // Last partial timers we use for distributing CPU usage.
+ final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>();
+
// These are the objects that will want to do something when the device
// is unplugged from power.
final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>();
@@ -240,6 +277,7 @@
// For debugging
public BatteryStatsImpl() {
mFile = null;
+ mHandler = null;
}
public static interface Unpluggable {
@@ -739,7 +777,9 @@
* State for keeping track of timing information.
*/
public static final class StopwatchTimer extends Timer {
+ final Uid mUid;
final ArrayList<StopwatchTimer> mTimerPool;
+
int mNesting;
/**
@@ -757,16 +797,24 @@
long mTimeout;
- StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool,
+ /**
+ * For partial wake locks, keep track of whether we are in the list
+ * to consume CPU cycles.
+ */
+ boolean mInList;
+
+ StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
ArrayList<Unpluggable> unpluggables, Parcel in) {
super(type, unpluggables, in);
+ mUid = uid;
mTimerPool = timerPool;
mUpdateTime = in.readLong();
}
- StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool,
+ StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
ArrayList<Unpluggable> unpluggables) {
super(type, unpluggables);
+ mUid = uid;
mTimerPool = timerPool;
}
@@ -1254,6 +1302,10 @@
mWakeLockNesting++;
}
if (uid >= 0) {
+ if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+ Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+ mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+ }
getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type);
}
}
@@ -1269,10 +1321,112 @@
}
}
if (uid >= 0) {
+ if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+ Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+ mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+ }
getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type);
}
}
+ public int startAddingCpuLocked() {
+ mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
+
+ if (mScreenOn) {
+ return 0;
+ }
+
+ final int N = mPartialTimers.size();
+ if (N == 0) {
+ mLastPartialTimers.clear();
+ return 0;
+ }
+
+ // How many timers should consume CPU? Only want to include ones
+ // that have already been in the list.
+ for (int i=0; i<N; i++) {
+ StopwatchTimer st = mPartialTimers.get(i);
+ if (st.mInList) {
+ Uid uid = st.mUid;
+ // We don't include the system UID, because it so often
+ // holds wake locks at one request or another of an app.
+ if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+ return 50;
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ public void finishAddingCpuLocked(int perc, int utime, int stime, long[] cpuSpeedTimes) {
+ final int N = mPartialTimers.size();
+ if (perc != 0) {
+ int num = 0;
+ for (int i=0; i<N; i++) {
+ StopwatchTimer st = mPartialTimers.get(i);
+ if (st.mInList) {
+ Uid uid = st.mUid;
+ // We don't include the system UID, because it so often
+ // holds wake locks at one request or another of an app.
+ if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+ num++;
+ }
+ }
+ }
+ if (num != 0) {
+ for (int i=0; i<N; i++) {
+ StopwatchTimer st = mPartialTimers.get(i);
+ if (st.mInList) {
+ int myUTime = utime/num;
+ int mySTime = stime/num;
+ utime -= myUTime;
+ stime -= mySTime;
+ num--;
+ Uid uid = st.mUid;
+ if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+ Uid.Proc proc = uid.getProcessStatsLocked("*wakelock*");
+ proc.addCpuTimeLocked(myUTime, mySTime);
+ proc.addSpeedStepTimes(cpuSpeedTimes);
+ }
+ }
+ }
+ }
+
+ // Just in case, collect any lost CPU time.
+ if (utime != 0 || stime != 0) {
+ Uid uid = getUidStatsLocked(Process.SYSTEM_UID);
+ if (uid != null) {
+ Uid.Proc proc = uid.getProcessStatsLocked("*lost*");
+ proc.addCpuTimeLocked(utime, stime);
+ proc.addSpeedStepTimes(cpuSpeedTimes);
+ }
+ }
+ }
+
+ final int NL = mLastPartialTimers.size();
+ boolean diff = N != NL;
+ for (int i=0; i<NL && !diff; i++) {
+ diff |= mPartialTimers.get(i) != mLastPartialTimers.get(i);
+ }
+ if (!diff) {
+ for (int i=0; i<NL; i++) {
+ mPartialTimers.get(i).mInList = true;
+ }
+ return;
+ }
+
+ for (int i=0; i<NL; i++) {
+ mLastPartialTimers.get(i).mInList = false;
+ }
+ mLastPartialTimers.clear();
+ for (int i=0; i<N; i++) {
+ StopwatchTimer st = mPartialTimers.get(i);
+ st.mInList = true;
+ mLastPartialTimers.add(st);
+ }
+ }
+
public void noteProcessDiedLocked(int uid, int pid) {
Uid u = mUidStats.get(uid);
if (u != null) {
@@ -1924,13 +2078,18 @@
public Uid(int uid) {
mUid = uid;
- mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables);
- mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK, null, mUnpluggables);
- mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK, null, mUnpluggables);
- mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
+ mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
null, mUnpluggables);
- mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON, null, mUnpluggables);
- mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON, null, mUnpluggables);
+ mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
+ null, mUnpluggables);
+ mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
+ null, mUnpluggables);
+ mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
+ null, mUnpluggables);
+ mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
+ null, mUnpluggables);
+ mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
+ null, mUnpluggables);
}
@Override
@@ -1998,7 +2157,7 @@
if (!mWifiTurnedOn) {
mWifiTurnedOn = true;
if (mWifiTurnedOnTimer == null) {
- mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON,
+ mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
null, mUnpluggables);
}
mWifiTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2018,7 +2177,7 @@
if (!mFullWifiLockOut) {
mFullWifiLockOut = true;
if (mFullWifiLockTimer == null) {
- mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK,
+ mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
null, mUnpluggables);
}
mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2038,7 +2197,7 @@
if (!mScanWifiLockOut) {
mScanWifiLockOut = true;
if (mScanWifiLockTimer == null) {
- mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK,
+ mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
null, mUnpluggables);
}
mScanWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2058,7 +2217,7 @@
if (!mWifiMulticastEnabled) {
mWifiMulticastEnabled = true;
if (mWifiMulticastTimer == null) {
- mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
+ mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
null, mUnpluggables);
}
mWifiMulticastTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2078,7 +2237,7 @@
if (!mAudioTurnedOn) {
mAudioTurnedOn = true;
if (mAudioTurnedOnTimer == null) {
- mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON,
+ mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
null, mUnpluggables);
}
mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2098,7 +2257,7 @@
if (!mVideoTurnedOn) {
mVideoTurnedOn = true;
if (mVideoTurnedOnTimer == null) {
- mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON,
+ mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
null, mUnpluggables);
}
mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
@@ -2458,42 +2617,42 @@
mTcpBytesSentAtLastUnplug = in.readLong();
mWifiTurnedOn = false;
if (in.readInt() != 0) {
- mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON,
+ mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
null, mUnpluggables, in);
} else {
mWifiTurnedOnTimer = null;
}
mFullWifiLockOut = false;
if (in.readInt() != 0) {
- mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK,
+ mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
null, mUnpluggables, in);
} else {
mFullWifiLockTimer = null;
}
mScanWifiLockOut = false;
if (in.readInt() != 0) {
- mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK,
+ mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
null, mUnpluggables, in);
} else {
mScanWifiLockTimer = null;
}
mWifiMulticastEnabled = false;
if (in.readInt() != 0) {
- mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
+ mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
null, mUnpluggables, in);
} else {
mWifiMulticastTimer = null;
}
mAudioTurnedOn = false;
if (in.readInt() != 0) {
- mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON,
+ mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
null, mUnpluggables, in);
} else {
mAudioTurnedOnTimer = null;
}
mVideoTurnedOn = false;
if (in.readInt() != 0) {
- mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON,
+ mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
null, mUnpluggables, in);
} else {
mVideoTurnedOnTimer = null;
@@ -2540,7 +2699,7 @@
return null;
}
- return new StopwatchTimer(type, pool, unpluggables, in);
+ return new StopwatchTimer(Uid.this, type, pool, unpluggables, in);
}
boolean reset() {
@@ -2616,7 +2775,7 @@
pool = new ArrayList<StopwatchTimer>();
mSensorTimers.put(mHandle, pool);
}
- return new StopwatchTimer(0, pool, unpluggables, in);
+ return new StopwatchTimer(Uid.this, 0, pool, unpluggables, in);
}
boolean reset() {
@@ -3418,21 +3577,24 @@
case WAKE_TYPE_PARTIAL:
t = wl.mTimerPartial;
if (t == null) {
- t = new StopwatchTimer(WAKE_TYPE_PARTIAL, mPartialTimers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, WAKE_TYPE_PARTIAL,
+ mPartialTimers, mUnpluggables);
wl.mTimerPartial = t;
}
return t;
case WAKE_TYPE_FULL:
t = wl.mTimerFull;
if (t == null) {
- t = new StopwatchTimer(WAKE_TYPE_FULL, mFullTimers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, WAKE_TYPE_FULL,
+ mFullTimers, mUnpluggables);
wl.mTimerFull = t;
}
return t;
case WAKE_TYPE_WINDOW:
t = wl.mTimerWindow;
if (t == null) {
- t = new StopwatchTimer(WAKE_TYPE_WINDOW, mWindowTimers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, WAKE_TYPE_WINDOW,
+ mWindowTimers, mUnpluggables);
wl.mTimerWindow = t;
}
return t;
@@ -3459,7 +3621,7 @@
timers = new ArrayList<StopwatchTimer>();
mSensorTimers.put(sensor, timers);
}
- t = new StopwatchTimer(BatteryStats.SENSOR, timers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mUnpluggables);
se.mTimer = t;
return t;
}
@@ -3532,25 +3694,26 @@
public BatteryStatsImpl(String filename) {
mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
+ mHandler = new MyHandler();
mStartCount++;
- mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables);
+ mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i] = new StopwatchTimer(-100-i, null, mUnpluggables);
+ mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mUnpluggables);
}
mInputEventCounter = new Counter(mUnpluggables);
- mPhoneOnTimer = new StopwatchTimer(-2, null, mUnpluggables);
+ mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables);
for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(-200-i, null, mUnpluggables);
+ mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, mUnpluggables);
}
- mPhoneSignalScanningTimer = new StopwatchTimer(-200+1, null, mUnpluggables);
+ mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i] = new StopwatchTimer(-300-i, null, mUnpluggables);
+ mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, mUnpluggables);
}
- mWifiOnTimer = new StopwatchTimer(-3, null, mUnpluggables);
- mWifiRunningTimer = new StopwatchTimer(-4, null, mUnpluggables);
- mBluetoothOnTimer = new StopwatchTimer(-5, null, mUnpluggables);
- mAudioOnTimer = new StopwatchTimer(-6, null, mUnpluggables);
- mVideoOnTimer = new StopwatchTimer(-7, null, mUnpluggables);
+ mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables);
+ mWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables);
+ mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables);
+ mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables);
+ mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables);
mOnBattery = mOnBatteryInternal = false;
initTimes();
mTrackBatteryPastUptime = 0;
@@ -3568,9 +3731,14 @@
public BatteryStatsImpl(Parcel p) {
mFile = null;
+ mHandler = null;
readFromParcel(p);
}
+ public void setCallback(BatteryCallback cb) {
+ mCallback = cb;
+ }
+
public void setNumSpeedSteps(int steps) {
if (sNumSpeedSteps == 0) sNumSpeedSteps = steps;
}
@@ -3655,6 +3823,9 @@
void setOnBattery(boolean onBattery, int oldStatus, int level) {
synchronized(this) {
boolean doWrite = false;
+ Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
+ m.arg1 = onBattery ? 1 : 0;
+ mHandler.sendMessage(m);
mOnBattery = mOnBatteryInternal = onBattery;
long uptime = SystemClock.uptimeMillis() * 1000;
@@ -4555,26 +4726,29 @@
mBatteryRealtime = in.readLong();
mBatteryLastRealtime = 0;
mScreenOn = false;
- mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables, in);
+ mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables, in);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i] = new StopwatchTimer(-100-i, null, mUnpluggables, in);
+ mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i,
+ null, mUnpluggables, in);
}
mInputEventCounter = new Counter(mUnpluggables, in);
mPhoneOn = false;
- mPhoneOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+ mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(-200-i, null, mUnpluggables, in);
+ mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i,
+ null, mUnpluggables, in);
}
- mPhoneSignalScanningTimer = new StopwatchTimer(-200+1, null, mUnpluggables, in);
+ mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables, in);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i] = new StopwatchTimer(-300-i, null, mUnpluggables, in);
+ mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i,
+ null, mUnpluggables, in);
}
mWifiOn = false;
- mWifiOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+ mWifiOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
mWifiRunning = false;
- mWifiRunningTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+ mWifiRunningTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
mBluetoothOn = false;
- mBluetoothOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+ mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
mUptime = in.readLong();
mUptimeStart = in.readLong();
mLastUptime = 0;
diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java
index 3a04993..1e97cd6 100644
--- a/core/java/com/android/internal/view/BaseSurfaceHolder.java
+++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java
@@ -41,7 +41,8 @@
int mRequestedWidth = -1;
int mRequestedHeight = -1;
- int mRequestedFormat = PixelFormat.OPAQUE;
+ /** @hide */
+ protected int mRequestedFormat = PixelFormat.OPAQUE;
int mRequestedType = -1;
long mLastLockTime = 0;
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index e064e2c..c4b6214 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -213,13 +213,11 @@
private class OverflowMenuButton extends ImageButton {
public OverflowMenuButton(Context context) {
- super(context, null, com.android.internal.R.attr.actionButtonStyle);
+ super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
final Resources res = context.getResources();
setClickable(true);
setFocusable(true);
- setContentDescription(res.getString(com.android.internal.R.string.more_item_label));
- setImageDrawable(res.getDrawable(com.android.internal.R.drawable.ic_menu_more));
setVisibility(VISIBLE);
setEnabled(true);
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 6b3d353..308a709 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -371,7 +371,8 @@
break;
case ActionBar.NAVIGATION_MODE_TABS:
mTabScrollView = new HorizontalScrollView(getContext());
- mTabLayout = new LinearLayout(getContext());
+ mTabLayout = new LinearLayout(getContext(), null,
+ com.android.internal.R.attr.actionBarTabBarStyle);
mTabScrollView.addView(mTabLayout);
addView(mTabScrollView);
break;
@@ -609,7 +610,7 @@
if (mTabScrollView != null) {
mTabScrollView.measure(
MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
break;
}
@@ -711,7 +712,7 @@
private ActionBar.Tab mTab;
public TabView(Context context, ActionBar.Tab tab) {
- super(context);
+ super(context, null, com.android.internal.R.attr.actionBarTabStyle);
mTab = tab;
final View custom = tab.getCustomView();
@@ -734,7 +735,8 @@
}
if (text != null) {
- TextView textView = new TextView(context);
+ TextView textView = new TextView(context, null,
+ com.android.internal.R.attr.actionBarTabTextStyle);
textView.setText(text);
textView.setSingleLine();
textView.setEllipsize(TruncateAt.END);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index b062264..880fb6e 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -12,6 +12,8 @@
#include <jni.h>
+#include <Caches.h>
+
#if 0
#define TRACE_BITMAP(code) code
#else
@@ -251,6 +253,11 @@
}
static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {
+#ifdef USE_OPENGL_RENDERER
+ if (android::uirenderer::Caches::hasInstance()) {
+ android::uirenderer::Caches::getInstance().textureCache.remove(bitmap);
+ }
+#endif
delete bitmap;
}
diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp
index b8b2a10..abe33f4 100644
--- a/core/jni/android/graphics/Path.cpp
+++ b/core/jni/android/graphics/Path.cpp
@@ -26,12 +26,19 @@
#include "SkPath.h"
+#include <Caches.h>
+
namespace android {
class SkPathGlue {
public:
static void finalizer(JNIEnv* env, jobject clazz, SkPath* obj) {
+#ifdef USE_OPENGL_RENDERER
+ if (android::uirenderer::Caches::hasInstance()) {
+ android::uirenderer::Caches::getInstance().pathCache.remove(obj);
+ }
+#endif
delete obj;
}
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index cb1c333..9202429 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -9,6 +9,7 @@
#include "SkXfermode.h"
#include <SkiaShader.h>
+#include <Caches.h>
using namespace android::uirenderer;
@@ -52,6 +53,11 @@
static void Shader_destructor(JNIEnv* env, jobject o, SkShader* shader, SkiaShader* skiaShader)
{
+#ifdef USE_OPENGL_RENDERER
+ if (android::uirenderer::Caches::hasInstance()) {
+ android::uirenderer::Caches::getInstance().gradientCache.remove(shader);
+ }
+#endif
delete skiaShader;
shader->safeUnref();
}
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index fe247e8..93fd54f 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -46,6 +46,7 @@
jfieldID mYPrecision;
jfieldID mEdgeFlags;
jfieldID mMetaState;
+ jfieldID mFlags;
jfieldID mNumPointers;
jfieldID mNumSamples;
jfieldID mPointerIdentifiers;
@@ -91,6 +92,8 @@
event->getEdgeFlags());
env->SetIntField(eventObj, gMotionEventClassInfo.mMetaState,
event->getMetaState());
+ env->SetIntField(eventObj, gMotionEventClassInfo.mFlags,
+ event->getFlags());
env->SetIntField(eventObj, gMotionEventClassInfo.mNumPointers,
numPointers);
env->SetIntField(eventObj, gMotionEventClassInfo.mNumSamples,
@@ -162,6 +165,7 @@
jfloat yPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mYPrecision);
jint edgeFlags = env->GetIntField(eventObj, gMotionEventClassInfo.mEdgeFlags);
jint metaState = env->GetIntField(eventObj, gMotionEventClassInfo.mMetaState);
+ jint flags = env->GetIntField(eventObj, gMotionEventClassInfo.mFlags);
jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers);
jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples);
jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj,
@@ -196,7 +200,7 @@
samplePointerCoords[j].orientation = *(srcDataSamples++);
}
- event->initialize(deviceId, source, action, edgeFlags, metaState,
+ event->initialize(deviceId, source, action, flags, edgeFlags, metaState,
xOffset, yOffset, xPrecision, yPrecision, downTimeNano, sampleEventTime,
numPointers, pointerIdentifiers, samplePointerCoords);
@@ -281,6 +285,8 @@
"mEdgeFlags", "I");
GET_FIELD_ID(gMotionEventClassInfo.mMetaState, gMotionEventClassInfo.clazz,
"mMetaState", "I");
+ GET_FIELD_ID(gMotionEventClassInfo.mFlags, gMotionEventClassInfo.clazz,
+ "mFlags", "I");
GET_FIELD_ID(gMotionEventClassInfo.mNumPointers, gMotionEventClassInfo.clazz,
"mNumPointers", "I");
GET_FIELD_ID(gMotionEventClassInfo.mNumSamples, gMotionEventClassInfo.clazz,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c7c8ec1..89298ea 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -50,6 +50,8 @@
<protected-broadcast android:name="android.intent.action.ACTION_SHUTDOWN" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_LOW" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_OK" />
+ <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_FULL" />
+ <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_NOT_FULL" />
<protected-broadcast android:name="android.intent.action.NEW_OUTGOING_CALL" />
<protected-broadcast android:name="android.intent.action.REBOOT" />
<protected-broadcast android:name="android.intent.action.DOCK_EVENT" />
diff --git a/core/res/res/drawable-mdpi/minitab_lt_focus.9.png b/core/res/res/drawable-mdpi/minitab_lt_focus.9.png
new file mode 100644
index 0000000..415c571
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_focus.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_press.9.png b/core/res/res/drawable-mdpi/minitab_lt_press.9.png
new file mode 100644
index 0000000..4166543
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_press.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_selected.9.png b/core/res/res/drawable-mdpi/minitab_lt_selected.9.png
new file mode 100644
index 0000000..fefa27e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png b/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png
new file mode 100644
index 0000000..0051cd5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_unselected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png b/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png
new file mode 100644
index 0000000..69444dd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/minitab_lt_unselected_press.9.png
Binary files differ
diff --git a/core/res/res/drawable/minitab_lt.xml b/core/res/res/drawable/minitab_lt.xml
new file mode 100644
index 0000000..aeea97c
--- /dev/null
+++ b/core/res/res/drawable/minitab_lt.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:state_selected="true"
+ android:drawable="@drawable/minitab_lt_press" />
+ <item android:state_selected="true"
+ android:drawable="@drawable/minitab_lt_selected" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/minitab_lt_unselected_press" />
+ <item android:drawable="@drawable/minitab_lt_unselected" />
+</selector>
diff --git a/core/res/res/values-mcc450-ko/config.xml b/core/res/res/values-mcc450-ko/config.xml
new file mode 100644
index 0000000..335b967
--- /dev/null
+++ b/core/res/res/values-mcc450-ko/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <string name="gsm_alphabet_default_charset">euc-kr</string>
+</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7b8d427..f466e82 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -473,6 +473,11 @@
<eat-comment />
<!-- Default amount of padding to use between action buttons. -->
<attr name="actionButtonPadding" format="dimension" />
+ <!-- Default style for tabs within an action bar -->
+ <attr name="actionBarTabStyle" format="reference" />
+ <attr name="actionBarTabBarStyle" format="reference" />
+ <attr name="actionBarTabTextStyle" format="reference" />
+ <attr name="actionOverflowButtonStyle" format="reference" />
<!-- =================== -->
<!-- Action mode styles -->
@@ -1345,6 +1350,12 @@
be saved. -->
<attr name="saveEnabled" format="boolean" />
+ <!-- Specifies whether to filter touches when the view's window is obscured by
+ another visible window. When set to true, the view will not receive touches
+ whenever a toast, dialog or other window appears above the view's window.
+ Refer to the {@link android.view.View} security documentation for more details. -->
+ <attr name="filterTouchesWhenObscured" format="boolean" />
+
<!-- Defines the quality of translucent drawing caches. This property is used
only when the drawing cache is enabled and translucent. The default value is auto. -->
<attr name="drawingCacheQuality">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 988176d..ac5828f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -80,6 +80,10 @@
for backward compatibility with apps that require external storage. -->
<bool name="config_emulateExternalStorage">false</bool>
+ <!-- Set to true if external storage is case sensitive.
+ Typically external storage is FAT, which is case insensitive. -->
+ <bool name="config_caseSensitiveExternalStorage">false</bool>
+
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. -->
@@ -401,4 +405,8 @@
<!-- IP address of the dns server to use if nobody else suggests one -->
<string name="config_default_dns_server">8.8.8.8</string>
+
+ <!-- The default character set for GsmAlphabet -->
+ <!-- Empty string means MBCS is not considered -->
+ <string name="gsm_alphabet_default_charset"></string>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 8594027..7f168a1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1251,6 +1251,8 @@
<public type="attr" name="logo" id="0x010102be" />
<public type="attr" name="xlargeScreens" id="0x010102bf" />
<public type="attr" name="immersive" id="0x010102c0" />
+ <public type="attr" name="filterTouchesWhenObscured" id="0x010102c4" />
+
<public-padding type="attr" name="kraken_resource_pad" end="0x01010300" />
<public-padding type="id" name="kraken_resource_pad" end="0x01020040" />
@@ -1333,6 +1335,10 @@
<public type="attr" name="listChoiceBackgroundIndicator" />
<public type="attr" name="spinnerMode" />
<public type="attr" name="animateLayoutChanges" />
+ <public type="attr" name="actionBarTabStyle" />
+ <public type="attr" name="actionBarTabBarStyle" />
+ <public type="attr" name="actionBarTabTextStyle" />
+ <public type="attr" name="actionOverflowButtonStyle" />
<public type="anim" name="animator_fade_in" />
<public type="anim" name="animator_fade_out" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e9d799b..1981ac0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -283,9 +283,15 @@
<!-- Shutdown Confirmation Dialog. When the user chooses to power off the phone, there will be a confirmation dialog. This is the message. -->
<string name="shutdown_confirm">Your phone will shut down.</string>
- <!-- Recent Tasks dialog: title -->
+ <!-- Recent Tasks dialog: title
+ TODO: this should move to SystemUI.apk, but the code for the old
+ recent dialog is still in the framework
+ -->
<string name="recent_tasks_title">Recent</string>
- <!-- Recent Tasks dialog: message when there are no recent applications -->
+ <!-- Recent Tasks dialog: message when there are no recent applications
+ TODO: this should move to SystemUI.apk, but the code for the old
+ recent dialog is still in the framework
+ -->
<string name="no_recent_tasks">No recent applications.</string>
<!-- Title of the Global Actions Dialog -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 6964808..5c3870d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -885,12 +885,32 @@
<item name="android:divider">@android:drawable/action_bar_divider</item>
<item name="android:height">?android:attr/windowActionBarSize</item>
<item name="android:paddingLeft">3dip</item>
- <item name="android:paddingTop">3dip</item>
+ <item name="android:paddingTop">0dip</item>
<item name="android:paddingRight">3dip</item>
- <item name="android:paddingBottom">3dip</item>
+ <item name="android:paddingBottom">0dip</item>
</style>
<style name="Widget.ActionButton">
<item name="android:background">@null</item>
</style>
+
+ <style name="Widget.ActionButton.Overflow">
+ <item name="android:src">@drawable/ic_menu_more</item>
+ <item name="android:contentDescription">@string/more_item_label</item>
+ </style>
+
+ <style name="Widget.ActionBarView_TabView">
+ <item name="android:background">@drawable/minitab_lt</item>
+ <item name="android:paddingLeft">4dip</item>
+ <item name="android:paddingRight">4dip</item>
+ </style>
+
+ <style name="Widget.ActionBarView_TabBar">
+ </style>
+
+ <style name="Widget.ActionBarView_TabText">
+ <item name="android:textAppearance">@style/TextAppearance.Widget.TextView.PopupMenu</item>
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="android:textSize">18sp</item>
+ </style>
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 6ed7b71..7b3a47c 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -183,6 +183,7 @@
<item name="dropDownSpinnerStyle">@android:style/Widget.Spinner.DropDown</item>
<item name="actionDropDownStyle">@android:style/Widget.Spinner.DropDown</item>
<item name="actionButtonStyle">@android:style/Widget.ActionButton</item>
+ <item name="actionOverflowButtonStyle">@android:style/Widget.ActionButton.Overflow</item>
<item name="starStyle">@android:style/Widget.CompoundButton.Star</item>
<item name="tabWidgetStyle">@android:style/Widget.TabWidget</item>
<item name="textViewStyle">@android:style/Widget.TextView</item>
@@ -199,6 +200,9 @@
<item name="quickContactBadgeStyleSmallWindowSmall">@android:style/Widget.QuickContactBadgeSmall.WindowSmall</item>
<item name="quickContactBadgeStyleSmallWindowMedium">@android:style/Widget.QuickContactBadgeSmall.WindowMedium</item>
<item name="quickContactBadgeStyleSmallWindowLarge">@android:style/Widget.QuickContactBadgeSmall.WindowLarge</item>
+ <item name="actionBarTabStyle">@style/Widget.ActionBarView_TabView</item>
+ <item name="actionBarTabBarStyle">@style/Widget.ActionBarView_TabBar</item>
+ <item name="actionBarTabTextStyle">@style/Widget.ActionBarView_TabText</item>
<!-- Preference styles -->
<item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 693ef18..b496805 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -12,7 +12,7 @@
$(call all-java-files-under, EnabledTestApp/src)
LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib android-common
+LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib android-common frameworks-core-util-lib
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCoreTests
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index ce73ae1..f09421b 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -36,12 +36,16 @@
android:description="@string/permdesc_testDenied" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
<uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
+ <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
diff --git a/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java
new file mode 100644
index 0000000..ee0f5f1
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java
@@ -0,0 +1,888 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.DownloadManager;
+import android.net.NetworkInfo;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.wifi.WifiManager;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.provider.Settings;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.concurrent.TimeoutException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Vector;
+
+import junit.framework.AssertionFailedError;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+
+/**
+ * Base class for Instrumented tests for the Download Manager.
+ */
+public class DownloadManagerBaseTest extends InstrumentationTestCase {
+
+ protected DownloadManager mDownloadManager = null;
+ protected MockWebServer mServer = null;
+ protected String mFileType = "text/plain";
+ protected Context mContext = null;
+ protected static final int DEFAULT_FILE_SIZE = 130 * 1024; // 130kb
+ protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024;
+
+ protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest";
+ protected static final int HTTP_OK = 200;
+ protected static final int HTTP_PARTIAL_CONTENT = 206;
+ protected static final int HTTP_NOT_FOUND = 404;
+ protected static final int HTTP_SERVICE_UNAVAILABLE = 503;
+ protected String DEFAULT_FILENAME = "somefile.txt";
+
+ protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000; // 2 minutes
+ protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000; // 5 seconds
+
+ protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000; // 1 second
+ protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes
+
+ // Just a few popular file types used to return from a download
+ protected enum DownloadFileType {
+ PLAINTEXT,
+ APK,
+ GIF,
+ GARBAGE,
+ UNRECOGNIZED,
+ ZIP
+ }
+
+ protected enum DataType {
+ TEXT,
+ BINARY
+ }
+
+ public static class LoggingRng extends Random {
+
+ /**
+ * Constructor
+ *
+ * Creates RNG with self-generated seed value.
+ */
+ public LoggingRng() {
+ this(SystemClock.uptimeMillis());
+ }
+
+ /**
+ * Constructor
+ *
+ * Creats RNG with given initial seed value
+
+ * @param seed The initial seed value
+ */
+ public LoggingRng(long seed) {
+ super(seed);
+ Log.i(LOG_TAG, "Seeding RNG with value: " + seed);
+ }
+ }
+
+ public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver {
+ private volatile int mNumDownloadsCompleted = 0;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
+ ++mNumDownloadsCompleted;
+ Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " +
+ intent.getAction() + " --> total count: " + mNumDownloadsCompleted);
+ }
+ }
+
+ /**
+ * Gets the number of times the {@link #onReceive} callback has been called for the
+ * {@link DownloadManager.ACTION_DOWNLOAD_COMPLETED} action, indicating the number of
+ * downloads completed thus far.
+ *
+ * @return the number of downloads completed so far.
+ */
+ public int numDownloadsCompleted() {
+ return mNumDownloadsCompleted;
+ }
+ }
+
+ public static class WiFiChangedReceiver extends BroadcastReceiver {
+ private Context mContext = null;
+
+ /**
+ * Constructor
+ *
+ * Sets the current state of WiFi.
+ *
+ * @param context The current app {@link Context}.
+ */
+ public WiFiChangedReceiver(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction());
+ synchronized (this) {
+ this.notify();
+ }
+ }
+ }
+
+ /**
+ * Gets the current state of WiFi.
+ *
+ * @return Returns true if WiFi is on, false otherwise.
+ */
+ public boolean getWiFiIsOn() {
+ ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ return info.isConnected();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp() throws Exception {
+ mContext = getInstrumentation().getContext();
+ mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+ mServer = new MockWebServer();
+ // Note: callers overriding this should call mServer.play() with the desired port #
+ }
+
+ /**
+ * Helper to enqueue a response from the MockWebServer.
+ *
+ * @param status The HTTP status code to return for this response
+ * @param body The body to return in this response
+ * @return Returns the mock web server response that was queued (which can be modified)
+ */
+ protected MockResponse enqueueResponse(int status, byte[] body) {
+ return doEnqueueResponse(status).setBody(body);
+
+ }
+
+ /**
+ * Helper to enqueue a response from the MockWebServer.
+ *
+ * @param status The HTTP status code to return for this response
+ * @param bodyFile The body to return in this response
+ * @return Returns the mock web server response that was queued (which can be modified)
+ */
+ protected MockResponse enqueueResponse(int status, File bodyFile) {
+ return doEnqueueResponse(status).setBody(bodyFile);
+ }
+
+ /**
+ * Helper for enqueue'ing a response from the MockWebServer.
+ *
+ * @param status The HTTP status code to return for this response
+ * @return Returns the mock web server response that was queued (which can be modified)
+ */
+ protected MockResponse doEnqueueResponse(int status) {
+ MockResponse response = new MockResponse().setResponseCode(status);
+ response.addHeader("Content-type", mFileType);
+ mServer.enqueue(response);
+ return response;
+ }
+
+ /**
+ * Helper to generate a random blob of bytes.
+ *
+ * @param size The size of the data to generate
+ * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or
+ * {@link DataType.BINARY}.
+ * @return The random data that is generated.
+ */
+ protected byte[] generateData(int size, DataType type) {
+ return generateData(size, type, null);
+ }
+
+ /**
+ * Helper to generate a random blob of bytes using a given RNG.
+ *
+ * @param size The size of the data to generate
+ * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or
+ * {@link DataType.BINARY}.
+ * @param rng (optional) The RNG to use; pass null to use
+ * @return The random data that is generated.
+ */
+ protected byte[] generateData(int size, DataType type, Random rng) {
+ int min = Byte.MIN_VALUE;
+ int max = Byte.MAX_VALUE;
+
+ // Only use chars in the HTTP ASCII printable character range for Text
+ if (type == DataType.TEXT) {
+ min = 32;
+ max = 126;
+ }
+ byte[] result = new byte[size];
+ Log.i(LOG_TAG, "Generating data of size: " + size);
+
+ if (rng == null) {
+ rng = new LoggingRng();
+ }
+
+ for (int i = 0; i < size; ++i) {
+ result[i] = (byte) (min + rng.nextInt(max - min + 1));
+ }
+ return result;
+ }
+
+ /**
+ * Helper to verify the size of a file.
+ *
+ * @param pfd The input file to compare the size of
+ * @param size The expected size of the file
+ */
+ protected void verifyFileSize(ParcelFileDescriptor pfd, long size) {
+ assertEquals(pfd.getStatSize(), size);
+ }
+
+ /**
+ * Helper to verify the contents of a downloaded file versus a byte[].
+ *
+ * @param actual The file of whose contents to verify
+ * @param expected The data we expect to find in the aforementioned file
+ * @throws IOException if there was a problem reading from the file
+ */
+ protected void verifyFileContents(ParcelFileDescriptor actual, byte[] expected)
+ throws IOException {
+ AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(actual);
+ long fileSize = actual.getStatSize();
+
+ assertTrue(fileSize <= Integer.MAX_VALUE);
+ assertEquals(expected.length, fileSize);
+
+ byte[] actualData = new byte[expected.length];
+ assertEquals(input.read(actualData), fileSize);
+ compareByteArrays(actualData, expected);
+ }
+
+ /**
+ * Helper to compare 2 byte arrays.
+ *
+ * @param actual The array whose data we want to verify
+ * @param expected The array of data we expect to see
+ */
+ protected void compareByteArrays(byte[] actual, byte[] expected) {
+ assertEquals(actual.length, expected.length);
+ int length = actual.length;
+ for (int i = 0; i < length; ++i) {
+ // assert has a bit of overhead, so only do the assert when the values are not the same
+ if (actual[i] != expected[i]) {
+ fail("Byte arrays are not equal.");
+ }
+ }
+ }
+
+ /**
+ * Verifies the contents of a downloaded file versus the contents of a File.
+ *
+ * @param pfd The file whose data we want to verify
+ * @param file The file containing the data we expect to see in the aforementioned file
+ * @throws IOException If there was a problem reading either of the two files
+ */
+ protected void verifyFileContents(ParcelFileDescriptor pfd, File file) throws IOException {
+ byte[] actual = new byte[FILE_BLOCK_READ_SIZE];
+ byte[] expected = new byte[FILE_BLOCK_READ_SIZE];
+
+ AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+
+ assertEquals(file.length(), pfd.getStatSize());
+
+ DataInputStream inFile = new DataInputStream(new FileInputStream(file));
+ int actualRead = 0;
+ int expectedRead = 0;
+
+ while (((actualRead = input.read(actual)) != -1) &&
+ ((expectedRead = inFile.read(expected)) != -1)) {
+ assertEquals(actualRead, expectedRead);
+ compareByteArrays(actual, expected);
+ }
+ }
+
+ /**
+ * Sets the MIME type of file that will be served from the mock server
+ *
+ * @param type The MIME type to return from the server
+ */
+ protected void setServerMimeType(DownloadFileType type) {
+ mFileType = getMimeMapping(type);
+ }
+
+ /**
+ * Gets the MIME content string for a given type
+ *
+ * @param type The MIME type to return
+ * @return the String representation of that MIME content type
+ */
+ protected String getMimeMapping(DownloadFileType type) {
+ switch (type) {
+ case APK:
+ return "application/vnd.android.package-archive";
+ case GIF:
+ return "image/gif";
+ case ZIP:
+ return "application/x-zip-compressed";
+ case GARBAGE:
+ return "zip\\pidy/doo/da";
+ case UNRECOGNIZED:
+ return "application/new.undefined.type.of.app";
+ }
+ return "text/plain";
+ }
+
+ /**
+ * Gets the Uri that should be used to access the mock server
+ *
+ * @param filename The name of the file to try to retrieve from the mock server
+ * @return the Uri to use for access the file on the mock server
+ */
+ protected Uri getServerUri(String filename) throws Exception {
+ URL url = mServer.getUrl("/" + filename);
+ return Uri.parse(url.toString());
+ }
+
+ /**
+ * Gets the Uri that should be used to access the mock server
+ *
+ * @param filename The name of the file to try to retrieve from the mock server
+ * @return the Uri to use for access the file on the mock server
+ */
+ protected void logDBColumnData(Cursor cursor, String column) {
+ int index = cursor.getColumnIndex(column);
+ Log.i(LOG_TAG, "columnName: " + column);
+ Log.i(LOG_TAG, "columnValue: " + cursor.getString(index));
+ }
+
+ /**
+ * Helper to create and register a new MultipleDownloadCompletedReciever
+ *
+ * This is used to track many simultaneous downloads by keeping count of all the downloads
+ * that have completed.
+ *
+ * @return A new receiver that records and can be queried on how many downloads have completed.
+ */
+ protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() {
+ MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver();
+ mContext.registerReceiver(receiver, new IntentFilter(
+ DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+ return receiver;
+ }
+
+ /**
+ * Helper to verify a standard single-file download from the mock server, and clean up after
+ * verification
+ *
+ * Note that this also calls the Download manager's remove, which cleans up the file from cache.
+ *
+ * @param requestId The id of the download to remove
+ * @param fileData The data to verify the file contains
+ */
+ protected void verifyAndCleanupSingleFileDownload(long requestId, byte[] fileData)
+ throws Exception {
+ int fileSize = fileData.length;
+ ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(requestId);
+ Cursor cursor = mDownloadManager.query(new Query().setFilterById(requestId));
+
+ try {
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToFirst());
+
+ mServer.checkForExceptions();
+
+ verifyFileSize(pfd, fileSize);
+ verifyFileContents(pfd, fileData);
+ } finally {
+ pfd.close();
+ cursor.close();
+ mDownloadManager.remove(requestId);
+ }
+ }
+
+ /**
+ * Enables or disables WiFi.
+ *
+ * Note: Needs the following permissions:
+ * android.permission.ACCESS_WIFI_STATE
+ * android.permission.CHANGE_WIFI_STATE
+ * @param enable true if it should be enabled, false if it should be disabled
+ */
+ protected void setWiFiStateOn(boolean enable) throws Exception {
+ WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
+
+ manager.setWifiEnabled(enable);
+
+ String timeoutMessage = "Timed out waiting for Wifi to be "
+ + (enable ? "enabled!" : "disabled!");
+
+ WiFiChangedReceiver receiver = new WiFiChangedReceiver(mContext);
+ mContext.registerReceiver(receiver, new IntentFilter(
+ ConnectivityManager.CONNECTIVITY_ACTION));
+
+ synchronized (receiver) {
+ long timeoutTime = SystemClock.elapsedRealtime() + DEFAULT_MAX_WAIT_TIME;
+ boolean timedOut = false;
+
+ while (receiver.getWiFiIsOn() != enable && !timedOut) {
+ try {
+ receiver.wait(DEFAULT_MAX_WAIT_TIME);
+
+ if (SystemClock.elapsedRealtime() > timeoutTime) {
+ timedOut = true;
+ }
+ }
+ catch (InterruptedException e) {
+ // ignore InterruptedExceptions
+ }
+ }
+ if (timedOut) {
+ fail(timeoutMessage);
+ }
+ }
+ assertEquals(enable, receiver.getWiFiIsOn());
+ }
+
+ /**
+ * Helper to enables or disables airplane mode. If successful, it also broadcasts an intent
+ * indicating that the mode has changed.
+ *
+ * Note: Needs the following permission:
+ * android.permission.WRITE_SETTINGS
+ * @param enable true if airplane mode should be ON, false if it should be OFF
+ */
+ protected void setAirplaneModeOn(boolean enable) throws Exception {
+ int state = enable ? 1 : 0;
+
+ // Change the system setting
+ Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON,
+ state);
+
+ String timeoutMessage = "Timed out waiting for airplane mode to be " +
+ (enable ? "enabled!" : "disabled!");
+
+ // wait for airplane mode to change state
+ int currentWaitTime = 0;
+ while (Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.AIRPLANE_MODE_ON, -1) != state) {
+ timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME,
+ timeoutMessage);
+ }
+
+ // Post the intent
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", true);
+ mContext.sendBroadcast(intent);
+ }
+
+ /**
+ * Helper to create a large file of random data on the SD card.
+ *
+ * @param filename (optional) The name of the file to create on the SD card; pass in null to
+ * use a default temp filename.
+ * @param type The type of file to create
+ * @param subdirectory If not null, the subdirectory under the SD card where the file should go
+ * @return The File that was created
+ * @throws IOException if there was an error while creating the file.
+ */
+ protected File createFileOnSD(String filename, long fileSize, DataType type,
+ String subdirectory) throws IOException {
+
+ // Build up the file path and name
+ String sdPath = Environment.getExternalStorageDirectory().getPath();
+ StringBuilder fullPath = new StringBuilder(sdPath);
+ if (subdirectory != null) {
+ fullPath.append(File.separatorChar).append(subdirectory);
+ }
+
+ File file = null;
+ if (filename == null) {
+ file = File.createTempFile("DMTEST_", null, new File(fullPath.toString()));
+ }
+ else {
+ fullPath.append(File.separatorChar).append(filename);
+ file = new File(fullPath.toString());
+ file.createNewFile();
+ }
+
+ // Fill the file with random data
+ DataOutputStream output = new DataOutputStream(new FileOutputStream(file));
+ final int CHUNK_SIZE = 1000000; // copy random data in 1000000-char chunks
+ long remaining = fileSize;
+ int nextChunkSize = CHUNK_SIZE;
+ byte[] randomData = null;
+ Random rng = new LoggingRng();
+
+ try {
+ while (remaining > 0) {
+ if (remaining < CHUNK_SIZE) {
+ nextChunkSize = (int)remaining;
+ remaining = 0;
+ }
+ else {
+ remaining -= CHUNK_SIZE;
+ }
+
+ randomData = generateData(nextChunkSize, type, rng);
+ output.write(randomData);
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Error writing to file " + file.getAbsolutePath());
+ file.delete();
+ throw e;
+ } finally {
+ output.close();
+ }
+ return file;
+ }
+
+ /**
+ * Helper to wait for a particular download to finish, or else a timeout to occur
+ *
+ * @param id The download id to query on (wait for)
+ */
+ protected void waitForDownloadOrTimeout(long id) throws TimeoutException,
+ InterruptedException {
+ waitForDownloadOrTimeout(id, WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+ }
+
+ /**
+ * Helper to wait for a particular download to finish, or else a timeout to occur
+ *
+ * @param id The download id to query on (wait for)
+ * @param poll The amount of time to wait
+ * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+ */
+ protected void waitForDownloadOrTimeout(long id, long poll, long timeoutMillis)
+ throws TimeoutException, InterruptedException {
+ doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis);
+ }
+
+ /**
+ * Helper to wait for all downloads to finish, or else a specified timeout to occur
+ *
+ * @param poll The amount of time to wait
+ * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+ */
+ protected void waitForDownloadsOrTimeout(long poll, long timeoutMillis) throws TimeoutException,
+ InterruptedException {
+ doWaitForDownloadsOrTimeout(new Query(), poll, timeoutMillis);
+ }
+
+ /**
+ * Helper to wait for all downloads to finish, or else a timeout to occur, but does not throw
+ *
+ * @param id The id of the download to query against
+ * @param poll The amount of time to wait
+ * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+ * @return true if download completed successfully (didn't timeout), false otherwise
+ */
+ protected boolean waitForDownloadOrTimeoutNoThrow(long id, long poll, long timeoutMillis) {
+ try {
+ doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis);
+ } catch (TimeoutException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Helper function to synchronously wait, or timeout if the maximum threshold has been exceeded.
+ *
+ * @param currentTotalWaitTime The total time waited so far
+ * @param poll The amount of time to wait
+ * @param maxTimeoutMillis The total wait time threshold; if we've waited more than this long,
+ * we timeout and fail
+ * @param timedOutMessage The message to display in the failure message if we timeout
+ * @return The new total amount of time we've waited so far
+ * @throws TimeoutException if timed out waiting for SD card to mount
+ */
+ protected int timeoutWait(int currentTotalWaitTime, long poll, long maxTimeoutMillis,
+ String timedOutMessage) throws TimeoutException {
+ long now = SystemClock.elapsedRealtime();
+ long end = now + poll;
+
+ // if we get InterruptedException's, ignore them and just keep sleeping
+ while (now < end) {
+ try {
+ Thread.sleep(end - now);
+ } catch (InterruptedException e) {
+ // ignore interrupted exceptions
+ }
+ now = SystemClock.elapsedRealtime();
+ }
+
+ currentTotalWaitTime += poll;
+ if (currentTotalWaitTime > maxTimeoutMillis) {
+ throw new TimeoutException(timedOutMessage);
+ }
+ return currentTotalWaitTime;
+ }
+
+ /**
+ * Helper to wait for all downloads to finish, or else a timeout to occur
+ *
+ * @param query The query to pass to the download manager
+ * @param poll The poll time to wait between checks
+ * @param timeoutMillis The max amount of time (in ms) to wait for the download(s) to complete
+ */
+ protected void doWaitForDownloadsOrTimeout(Query query, long poll, long timeoutMillis)
+ throws TimeoutException {
+ int currentWaitTime = 0;
+ while (true) {
+ query.setFilterByStatus(DownloadManager.STATUS_PENDING | DownloadManager.STATUS_PAUSED
+ | DownloadManager.STATUS_RUNNING);
+ Cursor cursor = mDownloadManager.query(query);
+
+ try {
+ // If we've finished the downloads then we're done
+ if (cursor.getCount() == 0) {
+ break;
+ }
+ currentWaitTime = timeoutWait(currentWaitTime, poll, timeoutMillis,
+ "Timed out waiting for all downloads to finish");
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ /**
+ * Synchronously waits for external store to be mounted (eg: SD Card).
+ *
+ * @throws InterruptedException if interrupted
+ * @throws Exception if timed out waiting for SD card to mount
+ */
+ protected void waitForExternalStoreMount() throws Exception {
+ String extStorageState = Environment.getExternalStorageState();
+ int currentWaitTime = 0;
+ while (!extStorageState.equals(Environment.MEDIA_MOUNTED)) {
+ Log.i(LOG_TAG, "Waiting for SD card...");
+ currentWaitTime = timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME,
+ DEFAULT_MAX_WAIT_TIME, "Timed out waiting for SD Card to be ready!");
+ extStorageState = Environment.getExternalStorageState();
+ }
+ }
+
+ /**
+ * Synchronously waits for a download to start.
+ *
+ * @param dlRequest the download request id used by Download Manager to track the download.
+ * @throws Exception if timed out while waiting for SD card to mount
+ */
+ protected void waitForDownloadToStart(long dlRequest) throws Exception {
+ Cursor cursor = getCursor(dlRequest);
+ try {
+ int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+ int value = cursor.getInt(columnIndex);
+ int currentWaitTime = 0;
+
+ while (value != DownloadManager.STATUS_RUNNING &&
+ (value != DownloadManager.STATUS_FAILED) &&
+ (value != DownloadManager.STATUS_SUCCESSFUL)) {
+ Log.i(LOG_TAG, "Waiting for download to start...");
+ currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+ MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download to start!");
+ cursor.requery();
+ assertTrue(cursor.moveToFirst());
+ columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+ value = cursor.getInt(columnIndex);
+ }
+ assertFalse("Download failed immediately after start",
+ value == DownloadManager.STATUS_FAILED);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Synchronously waits for a file to increase in size (such as to monitor that a download is
+ * progressing).
+ *
+ * @param file The file whose size to track.
+ * @throws Exception if timed out while waiting for the file to grow in size.
+ */
+ protected void waitForFileToGrow(File file) throws Exception {
+ int currentWaitTime = 0;
+
+ // File may not even exist yet, so wait until it does (or we timeout)
+ while (!file.exists()) {
+ Log.i(LOG_TAG, "Waiting for file to exist...");
+ currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+ MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be created.");
+ }
+
+ // Get original file size...
+ long originalSize = file.length();
+
+ while (file.length() <= originalSize) {
+ Log.i(LOG_TAG, "Waiting for file to be written to...");
+ currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+ MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be written to.");
+ }
+ }
+
+ /**
+ * Helper to remove all downloads that are registered with the DL Manager.
+ *
+ * Note: This gives us a clean slate b/c it includes downloads that are pending, running,
+ * paused, or have completed.
+ */
+ protected void removeAllCurrentDownloads() {
+ Log.i(LOG_TAG, "Removing all current registered downloads...");
+ Cursor cursor = mDownloadManager.query(new Query());
+ try {
+ if (cursor.moveToFirst()) {
+ do {
+ int index = cursor.getColumnIndex(DownloadManager.COLUMN_ID);
+ long downloadId = cursor.getLong(index);
+
+ mDownloadManager.remove(downloadId);
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Helper to perform a standard enqueue of data to the mock server.
+ *
+ * @param body The body to return in the response from the server
+ */
+ protected long doStandardEnqueue(byte[] body) throws Exception {
+ // Prepare the mock server with a standard response
+ enqueueResponse(HTTP_OK, body);
+ return doCommonStandardEnqueue();
+ }
+
+ /**
+ * Helper to perform a standard enqueue of data to the mock server.
+ *
+ * @param body The body to return in the response from the server, contained in the file
+ */
+ protected long doStandardEnqueue(File body) throws Exception {
+ // Prepare the mock server with a standard response
+ enqueueResponse(HTTP_OK, body);
+ return doCommonStandardEnqueue();
+ }
+
+ /**
+ * Helper to do the additional steps (setting title and Uri of default filename) when
+ * doing a standard enqueue request to the server.
+ */
+ protected long doCommonStandardEnqueue() throws Exception {
+ Uri uri = getServerUri(DEFAULT_FILENAME);
+ Request request = new Request(uri);
+ request.setTitle(DEFAULT_FILENAME);
+
+ long dlRequest = mDownloadManager.enqueue(request);
+ Log.i(LOG_TAG, "request ID: " + dlRequest);
+ return dlRequest;
+ }
+
+ /**
+ * Helper to verify an int value in a Cursor
+ *
+ * @param cursor The cursor containing the query results
+ * @param columnName The name of the column to query
+ * @param expected The expected int value
+ */
+ protected void verifyInt(Cursor cursor, String columnName, int expected) {
+ int index = cursor.getColumnIndex(columnName);
+ int actual = cursor.getInt(index);
+ assertEquals(expected, actual);
+ }
+
+ /**
+ * Helper to verify a String value in a Cursor
+ *
+ * @param cursor The cursor containing the query results
+ * @param columnName The name of the column to query
+ * @param expected The expected String value
+ */
+ protected void verifyString(Cursor cursor, String columnName, String expected) {
+ int index = cursor.getColumnIndex(columnName);
+ String actual = cursor.getString(index);
+ Log.i(LOG_TAG, ": " + actual);
+ assertEquals(expected, actual);
+ }
+
+ /**
+ * Performs a query based on ID and returns a Cursor for the query.
+ *
+ * @param id The id of the download in DL Manager; pass -1 to query all downloads
+ * @return A cursor for the query results
+ */
+ protected Cursor getCursor(long id) throws Exception {
+ Query query = new Query();
+ if (id != -1) {
+ query.setFilterById(id);
+ }
+
+ Cursor cursor = mDownloadManager.query(query);
+ int currentWaitTime = 0;
+
+ try {
+ while (!cursor.moveToFirst()) {
+ Thread.sleep(DEFAULT_WAIT_POLL_TIME);
+ currentWaitTime += DEFAULT_WAIT_POLL_TIME;
+ if (currentWaitTime > DEFAULT_MAX_WAIT_TIME) {
+ fail("timed out waiting for a non-null query result");
+ }
+ cursor.requery();
+ }
+ } catch (Exception e) {
+ cursor.close();
+ throw e;
+ }
+ return cursor;
+ }
+
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java
new file mode 100644
index 0000000..be3cbf7
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.DownloadManagerBaseTest.DataType;
+import android.net.DownloadManagerBaseTest.MultipleDownloadsCompletedReceiver;
+import android.net.wifi.WifiManager;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Random;
+
+import junit.framework.AssertionFailedError;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+
+/**
+ * Integration tests of the DownloadManager API.
+ */
+public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
+
+ private static String LOG_TAG = "android.net.DownloadManagerIntegrationTest";
+ private static String PROHIBITED_DIRECTORY = "/system";
+ protected MultipleDownloadsCompletedReceiver mReceiver = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ setWiFiStateOn(true);
+ mServer.play();
+ removeAllCurrentDownloads();
+ mReceiver = registerNewMultipleDownloadsReceiver();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ setWiFiStateOn(true);
+
+ if (mReceiver != null) {
+ mContext.unregisterReceiver(mReceiver);
+ mReceiver = null;
+ removeAllCurrentDownloads();
+ }
+ }
+
+ /**
+ * Helper that does the actual basic download verification.
+ */
+ protected void doBasicDownload(byte[] blobData) throws Exception {
+ long dlRequest = doStandardEnqueue(blobData);
+
+ // wait for the download to complete
+ waitForDownloadOrTimeout(dlRequest);
+
+ verifyAndCleanupSingleFileDownload(dlRequest, blobData);
+ assertEquals(1, mReceiver.numDownloadsCompleted());
+ }
+
+ /**
+ * Test a basic download of a binary file 500k in size.
+ */
+ @LargeTest
+ public void testBasicBinaryDownload() throws Exception {
+ int fileSize = 500 * 1024; // 500k
+ byte[] blobData = generateData(fileSize, DataType.BINARY);
+
+ doBasicDownload(blobData);
+ }
+
+ /**
+ * Tests the basic downloading of a text file 300000 bytes in size.
+ */
+ @LargeTest
+ public void testBasicTextDownload() throws Exception {
+ int fileSize = 300000;
+ byte[] blobData = generateData(fileSize, DataType.TEXT);
+
+ doBasicDownload(blobData);
+ }
+
+ /**
+ * Tests when the server drops the connection after all headers (but before any data send).
+ */
+ @LargeTest
+ public void testDropConnection_headers() throws Exception {
+ byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+ MockResponse response = enqueueResponse(HTTP_OK, blobData);
+ response.setCloseConnectionAfterHeader("content-length");
+ long dlRequest = doCommonStandardEnqueue();
+
+ // Download will never complete when header is dropped
+ boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest, DEFAULT_WAIT_POLL_TIME,
+ DEFAULT_MAX_WAIT_TIME);
+
+ assertFalse(success);
+ }
+
+ /**
+ * Tests that we get an error code when the server drops the connection during a download.
+ */
+ @LargeTest
+ public void testServerDropConnection_body() throws Exception {
+ byte[] blobData = generateData(25000, DataType.TEXT); // file size = 25000 bytes
+
+ MockResponse response = enqueueResponse(HTTP_OK, blobData);
+ response.setCloseConnectionAfterXBytes(15382);
+ long dlRequest = doCommonStandardEnqueue();
+ waitForDownloadOrTimeout(dlRequest);
+
+ Cursor cursor = getCursor(dlRequest);
+ try {
+ verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+ verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ DownloadManager.ERROR_CANNOT_RESUME);
+ } finally {
+ cursor.close();
+ }
+ // Even tho the server drops the connection, we should still get a completed notification
+ assertEquals(1, mReceiver.numDownloadsCompleted());
+ }
+
+ /**
+ * Attempts to download several files simultaneously
+ */
+ @LargeTest
+ public void testMultipleDownloads() throws Exception {
+ // need to be sure all current downloads have stopped first
+ removeAllCurrentDownloads();
+ int NUM_FILES = 50;
+ int MAX_FILE_SIZE = 500 * 1024; // 500 kb
+
+ Random r = new LoggingRng();
+ for (int i=0; i<NUM_FILES; ++i) {
+ int size = r.nextInt(MAX_FILE_SIZE);
+ byte[] blobData = generateData(size, DataType.TEXT);
+
+ Uri uri = getServerUri(DEFAULT_FILENAME);
+ Request request = new Request(uri);
+ request.setTitle(String.format("%s--%d", DEFAULT_FILENAME, i));
+
+ // Prepare the mock server with a standard response
+ enqueueResponse(HTTP_OK, blobData);
+
+ Log.i(LOG_TAG, "request: " + i);
+ mDownloadManager.enqueue(request);
+ }
+
+ waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+ Cursor cursor = mDownloadManager.query(new Query());
+ try {
+ assertEquals(NUM_FILES, cursor.getCount());
+
+ if (cursor.moveToFirst()) {
+ do {
+ int status = cursor.getInt(cursor.getColumnIndex(
+ DownloadManager.COLUMN_STATUS));
+ String filename = cursor.getString(cursor.getColumnIndex(
+ DownloadManager.COLUMN_URI));
+ String errorString = String.format(
+ "File %s failed to download successfully. Status code: %d",
+ filename, status);
+ assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status);
+ } while (cursor.moveToNext());
+ }
+
+ assertEquals(NUM_FILES, mReceiver.numDownloadsCompleted());
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Tests trying to download to SD card when the file with same name already exists.
+ */
+ @LargeTest
+ public void testDownloadToExternal_fileExists() throws Exception {
+ File existentFile = createFileOnSD(null, 1, DataType.TEXT, null);
+ byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+ // Prepare the mock server with a standard response
+ enqueueResponse(HTTP_OK, blobData);
+
+ try {
+ Uri uri = getServerUri(DEFAULT_FILENAME);
+ Request request = new Request(uri);
+
+ Uri localUri = Uri.fromFile(existentFile);
+ Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+ request.setDestinationUri(localUri);
+
+ long dlRequest = mDownloadManager.enqueue(request);
+
+ // wait for the download to complete
+ waitForDownloadOrTimeout(dlRequest);
+ Cursor cursor = getCursor(dlRequest);
+
+ try {
+ verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+ verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ DownloadManager.ERROR_FILE_ERROR);
+ } finally {
+ cursor.close();
+ }
+ } finally {
+ existentFile.delete();
+ }
+ }
+
+ /**
+ * Tests trying to download a file to SD card.
+ */
+ @LargeTest
+ public void testDownloadToExternal() throws Exception {
+ String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+ File downloadedFile = new File(localDownloadDirectory, DEFAULT_FILENAME);
+ // make sure the file doesn't already exist in the directory
+ downloadedFile.delete();
+
+ try {
+ byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+ // Prepare the mock server with a standard response
+ enqueueResponse(HTTP_OK, blobData);
+
+ Uri uri = getServerUri(DEFAULT_FILENAME);
+ Request request = new Request(uri);
+
+ Uri localUri = Uri.fromFile(downloadedFile);
+ Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+ request.setDestinationUri(localUri);
+
+ long dlRequest = mDownloadManager.enqueue(request);
+
+ // wait for the download to complete
+ waitForDownloadOrTimeout(dlRequest);
+
+ verifyAndCleanupSingleFileDownload(dlRequest, blobData);
+
+ assertEquals(1, mReceiver.numDownloadsCompleted());
+ } finally {
+ downloadedFile.delete();
+ }
+ }
+
+ /**
+ * Tests trying to download a file to the system partition.
+ */
+ @LargeTest
+ public void testDownloadToProhibitedDirectory() throws Exception {
+ File downloadedFile = new File(PROHIBITED_DIRECTORY, DEFAULT_FILENAME);
+ try {
+ byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+ // Prepare the mock server with a standard response
+ enqueueResponse(HTTP_OK, blobData);
+
+ Uri uri = getServerUri(DEFAULT_FILENAME);
+ Request request = new Request(uri);
+
+ Uri localUri = Uri.fromFile(downloadedFile);
+ Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+ request.setDestinationUri(localUri);
+
+ try {
+ mDownloadManager.enqueue(request);
+ fail("Failed to throw SecurityException when trying to write to /system.");
+ } catch (SecurityException s) {
+ assertFalse(downloadedFile.exists());
+ }
+ } finally {
+ // Just in case file somehow got created, make sure to delete it
+ downloadedFile.delete();
+ }
+ }
+
+ /**
+ * Tests that a download set for Wifi does not progress while Wifi is disabled, but resumes
+ * once Wifi is re-enabled.
+ */
+ @LargeTest
+ public void testDownloadNoWifi() throws Exception {
+ long timeout = 60 * 1000; // wait only 60 seconds before giving up
+ int fileSize = 140 * 1024; // 140k
+ byte[] blobData = generateData(fileSize, DataType.TEXT);
+
+ setWiFiStateOn(false);
+ enqueueResponse(HTTP_OK, blobData);
+
+ try {
+ Uri uri = getServerUri(DEFAULT_FILENAME);
+ Request request = new Request(uri);
+ request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+ long dlRequest = mDownloadManager.enqueue(request);
+
+ // wait for the download to complete
+ boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest,
+ WAIT_FOR_DOWNLOAD_POLL_TIME, timeout);
+ assertFalse("Download proceeded without Wifi connection!", success);
+
+ setWiFiStateOn(true);
+ waitForDownloadOrTimeout(dlRequest);
+
+ assertEquals(1, mReceiver.numDownloadsCompleted());
+ } finally {
+ setWiFiStateOn(true);
+ }
+ }
+
+ /**
+ * Tests trying to download two large files (50M bytes, followed by 60M bytes)
+ */
+ @LargeTest
+ public void testInsufficientSpaceSingleFiles() throws Exception {
+ long fileSize1 = 50000000L;
+ long fileSize2 = 60000000L;
+ File largeFile1 = createFileOnSD(null, fileSize1, DataType.TEXT, null);
+ File largeFile2 = createFileOnSD(null, fileSize2, DataType.TEXT, null);
+
+ try {
+ long dlRequest = doStandardEnqueue(largeFile1);
+ waitForDownloadOrTimeout(dlRequest);
+ ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+ verifyFileContents(pfd, largeFile1);
+ verifyFileSize(pfd, largeFile1.length());
+
+ dlRequest = doStandardEnqueue(largeFile2);
+ waitForDownloadOrTimeout(dlRequest);
+ Cursor cursor = getCursor(dlRequest);
+ try {
+ verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ DownloadManager.ERROR_INSUFFICIENT_SPACE);
+ } finally {
+ cursor.close();
+ }
+ } finally {
+ largeFile1.delete();
+ largeFile2.delete();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/net/DownloadManagerStressTest.java b/core/tests/coretests/src/android/net/DownloadManagerStressTest.java
new file mode 100644
index 0000000..9fa8620
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerStressTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import java.io.File;
+import java.util.Random;
+
+import android.database.Cursor;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.os.ParcelFileDescriptor;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+
+public class DownloadManagerStressTest extends DownloadManagerBaseTest {
+ private static String LOG_TAG = "android.net.DownloadManagerStressTest";
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mServer.play(0);
+ removeAllCurrentDownloads();
+ }
+
+ /**
+ * Attempts to downloading thousands of files simultaneously
+ */
+ public void testDownloadThousands() throws Exception {
+ int NUM_FILES = 1500;
+ int MAX_FILE_SIZE = 3000;
+ long[] reqs = new long[NUM_FILES];
+
+ // need to be sure all current downloads have stopped first
+ MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver();
+ Cursor cursor = null;
+ try {
+ Random r = new LoggingRng();
+ for (int i = 0; i < NUM_FILES; ++i) {
+ int size = r.nextInt(MAX_FILE_SIZE);
+ byte[] blobData = generateData(size, DataType.TEXT);
+
+ Uri uri = getServerUri(DEFAULT_FILENAME);
+ Request request = new Request(uri);
+ request.setTitle(String.format("%s--%d", DEFAULT_FILENAME, i));
+
+ // Prepare the mock server with a standard response
+ enqueueResponse(HTTP_OK, blobData);
+
+ Log.i(LOG_TAG, "issuing request: " + i);
+ long reqId = mDownloadManager.enqueue(request);
+ reqs[i] = reqId;
+ }
+
+ // wait for the download to complete or timeout
+ waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+ cursor = mDownloadManager.query(new Query());
+ assertEquals(NUM_FILES, cursor.getCount());
+ Log.i(LOG_TAG, "Verified number of downloads in download manager is what we expect.");
+ while (cursor.moveToNext()) {
+ int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
+ String filename = cursor.getString(cursor.getColumnIndex(
+ DownloadManager.COLUMN_URI));
+ String errorString = String.format("File %s failed to download successfully. " +
+ "Status code: %d", filename, status);
+ assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status);
+ }
+ Log.i(LOG_TAG, "Verified each download was successful.");
+ assertEquals(NUM_FILES, receiver.numDownloadsCompleted());
+ Log.i(LOG_TAG, "Verified number of completed downloads in our receiver.");
+
+ // Verify that for each request, we can open the downloaded file
+ for (int i = 0; i < NUM_FILES; ++i) {
+ ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(reqs[i]);
+ pfd.close();
+ }
+ Log.i(LOG_TAG, "Verified we can open each file.");
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ mContext.unregisterReceiver(receiver);
+ removeAllCurrentDownloads();
+ }
+ }
+
+ /**
+ * Tests trying to download a large file (50M bytes).
+ */
+ public void testDownloadLargeFile() throws Exception {
+ long fileSize = 50000000L; // note: kept relatively small to not exceed /cache dir size
+ File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null);
+ MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver();
+
+ try {
+ long dlRequest = doStandardEnqueue(largeFile);
+
+ // wait for the download to complete
+ waitForDownloadOrTimeout(dlRequest);
+
+ ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+ verifyFileContents(pfd, largeFile);
+ verifyFileSize(pfd, largeFile.length());
+
+ assertEquals(1, receiver.numDownloadsCompleted());
+ mContext.unregisterReceiver(receiver);
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ largeFile.delete();
+ }
+ }
+
+ /**
+ * Tests trying to download a large file (~300M bytes) when there's not enough space in cache
+ */
+ public void testInsufficientSpace() throws Exception {
+ long fileSize = 300000000L;
+ File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null);
+
+ Cursor cursor = null;
+ try {
+ long dlRequest = doStandardEnqueue(largeFile);
+
+ // wait for the download to complete
+ waitForDownloadOrTimeout(dlRequest);
+
+ cursor = getCursor(dlRequest);
+ verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+ verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ DownloadManager.ERROR_INSUFFICIENT_SPACE);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ largeFile.delete();
+ }
+ }
+}
diff --git a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
index 38191b0..c5ea9c1 100644
--- a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
@@ -37,7 +37,9 @@
import java.io.StringReader;
import java.lang.Runtime;
import java.lang.Process;
+import java.util.Hashtable;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -117,18 +119,38 @@
/**
* Helper method to run tests and return the listener that collected the results.
+ *
+ * For the optional params, pass null to use the default values.
+
* @param pkgName Android application package for tests
- * @return the {@link CollectingTestRunListener}
+ * @param className (optional) The class containing the method to test
+ * @param methodName (optional) The method in the class of which to test
+ * @param runnerName (optional) The name of the TestRunner of the test on the device to be run
+ * @param params (optional) Any additional parameters to pass into the Test Runner
* @throws TimeoutException in case of a timeout on the connection.
* @throws AdbCommandRejectedException if adb rejects the command
* @throws ShellCommandUnresponsiveException if the device did not output anything for
* a period longer than the max time to output.
* @throws IOException if connection to device was lost.
+ * @return the {@link CollectingTestRunListener}
*/
- private CollectingTestRunListener doRunTests(String pkgName) throws IOException,
- TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
- RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
- pkgName, mDevice);
+ private CollectingTestRunListener doRunTests(String pkgName, String className,
+ String methodName, String runnerName, Map<String, String> params) throws IOException,
+ TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, runnerName,
+ mDevice);
+
+ if (className != null && methodName != null) {
+ testRunner.setMethodName(className, methodName);
+ }
+
+ // Add in any additional args to pass into the test
+ if (params != null) {
+ for (Entry<String, String> argPair : params.entrySet()) {
+ testRunner.addInstrumentationArg(argPair.getKey(), argPair.getValue());
+ }
+ }
+
CollectingTestRunListener listener = new CollectingTestRunListener();
try {
testRunner.run(listener);
@@ -142,16 +164,34 @@
* Runs the specified packages tests, and returns whether all tests passed or not.
*
* @param pkgName Android application package for tests
- * @return true if every test passed, false otherwise.
+ * @param className The class containing the method to test
+ * @param methodName The method in the class of which to test
+ * @param runnerName The name of the TestRunner of the test on the device to be run
+ * @param params Any additional parameters to pass into the Test Runner
+ * @return true if test passed, false otherwise.
+ */
+ public boolean runDeviceTestsDidAllTestsPass(String pkgName, String className,
+ String methodName, String runnerName, Map<String, String> params) throws IOException,
+ TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
+ CollectingTestRunListener listener = doRunTests(pkgName, className, methodName,
+ runnerName, params);
+ return listener.didAllTestsPass();
+ }
+
+ /**
+ * Runs the specified packages tests, and returns whether all tests passed or not.
+ *
+ * @param pkgName Android application package for tests
* @throws TimeoutException in case of a timeout on the connection.
* @throws AdbCommandRejectedException if adb rejects the command
* @throws ShellCommandUnresponsiveException if the device did not output anything for
* a period longer than the max time to output.
* @throws IOException if connection to device was lost.
+ * @return true if every test passed, false otherwise.
*/
public boolean runDeviceTestsDidAllTestsPass(String pkgName) throws IOException,
TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
- CollectingTestRunListener listener = doRunTests(pkgName);
+ CollectingTestRunListener listener = doRunTests(pkgName, null, null, null, null);
return listener.didAllTestsPass();
}
@@ -535,7 +575,7 @@
}
// For collecting results from running device tests
- private static class CollectingTestRunListener implements ITestRunListener {
+ public static class CollectingTestRunListener implements ITestRunListener {
private boolean mAllTestsPassed = true;
private String mTestRunErrorMessage = null;
diff --git a/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java
new file mode 100644
index 0000000..cfabb6c
--- /dev/null
+++ b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.pm.PackageManagerHostTestUtils;
+
+import com.android.ddmlib.Log;
+import com.android.hosttest.DeviceTestCase;
+import com.android.hosttest.DeviceTestSuite;
+
+import java.io.File;
+import java.util.Hashtable;
+
+import junit.framework.Test;
+
+/**
+ * Host-based tests of the DownloadManager API. (Uses a device-based app to actually invoke the
+ * various tests.)
+ */
+public class DownloadManagerHostTests extends DeviceTestCase {
+ protected PackageManagerHostTestUtils mPMUtils = null;
+
+ private static final String LOG_TAG = "android.net.DownloadManagerHostTests";
+ private static final String FILE_DOWNLOAD_APK = "DownloadManagerTestApp.apk";
+ private static final String FILE_DOWNLOAD_PKG = "com.android.frameworks.downloadmanagertests";
+ private static final String FILE_DOWNLOAD_CLASS =
+ "com.android.frameworks.downloadmanagertests.DownloadManagerTestApp";
+ private static final String DOWNLOAD_TEST_RUNNER_NAME =
+ "com.android.frameworks.downloadmanagertests.DownloadManagerTestRunner";
+
+ // Extra parameters to pass to the TestRunner
+ private static final String EXTERNAL_DOWNLOAD_URI_KEY = "external_download_uri";
+ // Note: External environment variable ANDROID_TEST_EXTERNAL_URI must be set to point to the
+ // external URI under which the files downloaded by the tests can be found. Note that the Uri
+ // must be accessible by the device during a test run.
+ private static String EXTERNAL_DOWNLOAD_URI_VALUE = null;
+
+ Hashtable<String, String> mExtraParams = null;
+
+ public static Test suite() {
+ return new DeviceTestSuite(DownloadManagerHostTests.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // ensure apk path has been set before test is run
+ assertNotNull(getTestAppPath());
+ mPMUtils = new PackageManagerHostTestUtils(getDevice());
+ EXTERNAL_DOWNLOAD_URI_VALUE = System.getenv("ANDROID_TEST_EXTERNAL_URI");
+ assertNotNull(EXTERNAL_DOWNLOAD_URI_VALUE);
+ mExtraParams = getExtraParams();
+ }
+
+ /**
+ * Helper function to get extra params that can be used to pass into the helper app.
+ */
+ protected Hashtable<String, String> getExtraParams() {
+ Hashtable<String, String> extraParams = new Hashtable<String, String>();
+ extraParams.put(EXTERNAL_DOWNLOAD_URI_KEY, EXTERNAL_DOWNLOAD_URI_VALUE);
+ return extraParams;
+ }
+
+ /**
+ * Tests that a large download over WiFi
+ * @throws Exception if the test failed at any point
+ */
+ public void testLargeDownloadOverWiFi() throws Exception {
+ mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+ File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+ boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+ FILE_DOWNLOAD_CLASS, "runLargeDownloadOverWiFi", DOWNLOAD_TEST_RUNNER_NAME,
+ mExtraParams);
+
+ assertTrue("Failed to install large file over WiFi in < 10 minutes!", testPassed);
+ }
+
+ /**
+ * Spawns a device-based function to initiate a download on the device, reboots the device,
+ * then waits and verifies the download succeeded.
+ *
+ * @throws Exception if the test failed at any point
+ */
+ public void testDownloadManagerSingleReboot() throws Exception {
+ mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+ File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+ boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+ FILE_DOWNLOAD_CLASS, "initiateDownload", DOWNLOAD_TEST_RUNNER_NAME,
+ mExtraParams);
+
+ assertTrue("Failed to initiate download properly!", testPassed);
+ mPMUtils.rebootDevice();
+ testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+ FILE_DOWNLOAD_CLASS, "verifyFileDownloadSucceeded", DOWNLOAD_TEST_RUNNER_NAME,
+ mExtraParams);
+ assertTrue("Failed to verify initiated download completed properyly!", testPassed);
+ }
+
+ /**
+ * Spawns a device-based function to initiate a download on the device, reboots the device three
+ * times (using different intervals), then waits and verifies the download succeeded.
+ *
+ * @throws Exception if the test failed at any point
+ */
+ public void testDownloadManagerMultipleReboots() throws Exception {
+ mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+ File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+ boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+ FILE_DOWNLOAD_CLASS, "initiateDownload", DOWNLOAD_TEST_RUNNER_NAME,
+ mExtraParams);
+
+ assertTrue("Failed to initiate download properly!", testPassed);
+ Thread.sleep(5000);
+
+ // Do 3 random reboots - after 13, 9, and 19 seconds
+ Log.i(LOG_TAG, "First reboot...");
+ mPMUtils.rebootDevice();
+ Thread.sleep(13000);
+ Log.i(LOG_TAG, "Second reboot...");
+ mPMUtils.rebootDevice();
+ Thread.sleep(9000);
+ Log.i(LOG_TAG, "Third reboot...");
+ mPMUtils.rebootDevice();
+ Thread.sleep(19000);
+ testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+ FILE_DOWNLOAD_CLASS, "verifyFileDownloadSucceeded", DOWNLOAD_TEST_RUNNER_NAME,
+ mExtraParams);
+ assertTrue("Failed to verify initiated download completed properyly!", testPassed);
+ }
+
+ /**
+ * Spawns a device-based function to test download while WiFi is enabled/disabled multiple times
+ * during the download.
+ *
+ * @throws Exception if the test failed at any point
+ */
+ public void testDownloadMultipleWiFiEnableDisable() throws Exception {
+ mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+ File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+ boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+ FILE_DOWNLOAD_CLASS, "runDownloadMultipleWiFiEnableDisable",
+ DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+ assertTrue(testPassed);
+ }
+
+ /**
+ * Spawns a device-based function to test switching on/off both airplane mode and WiFi
+ *
+ * @throws Exception if the test failed at any point
+ */
+ public void testDownloadMultipleSwitching() throws Exception {
+ mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+ File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+ boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+ FILE_DOWNLOAD_CLASS, "runDownloadMultipleSwitching",
+ DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+ assertTrue(testPassed);
+ }
+
+ /**
+ * Spawns a device-based function to test switching on/off airplane mode multiple times
+ *
+ * @throws Exception if the test failed at any point
+ */
+ public void testDownloadMultipleAirplaneModeEnableDisable() throws Exception {
+ mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+ File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+ boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+ FILE_DOWNLOAD_CLASS, "runDownloadMultipleAirplaneModeEnableDisable",
+ DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+ assertTrue(testPassed);
+ }
+}
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
new file mode 100644
index 0000000..576765c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ ../../../coretests/src/android/net/DownloadManagerBaseTest.java
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := DownloadManagerTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..3f2be3c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.downloadmanagertests">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+ <application android:label="DownloadManagerTestApp">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name=".DownloadManagerTestRunner"
+ android:targetPackage="com.android.frameworks.downloadmanagertests"
+ android:label="Frameworks Download Manager Test App" />
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java
new file mode 100644
index 0000000..ef81353
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.frameworks.downloadmanagertests;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.DownloadManager;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.DownloadManagerBaseTest;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+import coretestutils.http.RecordedRequest;
+
+/**
+ * Class to test downloading files from a real (not mock) external server.
+ */
+public class DownloadManagerTestApp extends DownloadManagerBaseTest {
+ protected static String DOWNLOAD_STARTED_FLAG = "DMTEST_DOWNLOAD_STARTED";
+ protected static String LOG_TAG =
+ "com.android.frameworks.downloadmanagertests.DownloadManagerTestApp";
+
+ protected static String DOWNLOAD_500K_FILENAME = "External541kb.apk";
+ protected static long DOWNLOAD_500K_FILESIZE = 570927;
+ protected static String DOWNLOAD_1MB_FILENAME = "External1mb.apk";
+ protected static long DOWNLOAD_1MB_FILESIZE = 1041262;
+ protected static String DOWNLOAD_10MB_FILENAME = "External10mb.apk";
+ protected static long DOWNLOAD_10MB_FILESIZE = 10258741;
+
+ // Values to be obtained from TestRunner
+ private String externalDownloadUriValue = null;
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ DownloadManagerTestRunner mRunner = (DownloadManagerTestRunner)getInstrumentation();
+ externalDownloadUriValue = mRunner.externalDownloadUriValue;
+ assertNotNull(externalDownloadUriValue);
+
+ if (!externalDownloadUriValue.endsWith("/")) {
+ externalDownloadUriValue += "/";
+ }
+ }
+
+ /**
+ * Gets the external URL of the file to download
+ *
+ * @return the Uri of the external file to download
+ */
+ private Uri getExternalFileUri(String file) {
+ return Uri.parse(externalDownloadUriValue + file);
+ }
+
+ /**
+ * Gets the path to the file that flags that a download has started. The file contains the
+ * DownloadManager id of the download being trackted between reboot sessions.
+ *
+ * @return The path of the file tracking that a download has started
+ * @throws InterruptedException if interrupted
+ * @throws Exception if timed out while waiting for SD card to mount
+ */
+ protected String getDownloadStartedFilePath() {
+ String path = Environment.getExternalStorageDirectory().getPath();
+ return path + File.separatorChar + DOWNLOAD_STARTED_FLAG;
+ }
+
+ /**
+ * Common setup steps for downloads.
+ *
+ * Note that these are not included in setUp, so that individual tests can control their own
+ * state between reboots, etc.
+ */
+ protected void doCommonDownloadSetup() throws Exception {
+ setWiFiStateOn(true);
+ setAirplaneModeOn(false);
+ waitForExternalStoreMount();
+ removeAllCurrentDownloads();
+ }
+
+ /**
+ * Initiates a download.
+ *
+ * Queues up a download to the download manager, and saves the DownloadManager's assigned
+ * download ID for this download to a file.
+ *
+ * @throws Exception if unsuccessful
+ */
+ public void initiateDownload() throws Exception {
+ String filename = DOWNLOAD_1MB_FILENAME;
+ mContext.deleteFile(DOWNLOAD_STARTED_FLAG);
+ FileOutputStream fileOutput = mContext.openFileOutput(DOWNLOAD_STARTED_FLAG, 0);
+ DataOutputStream outputFile = null;
+ doCommonDownloadSetup();
+
+ try {
+ long dlRequest = -1;
+
+ // Make sure there are no pending downloads currently going on
+ removeAllCurrentDownloads();
+
+ Uri remoteUri = getExternalFileUri(filename);
+ Request request = new Request(remoteUri);
+
+ dlRequest = mDownloadManager.enqueue(request);
+ waitForDownloadToStart(dlRequest);
+ assertTrue(dlRequest != -1);
+
+ // Store ID of download for later retrieval
+ outputFile = new DataOutputStream(fileOutput);
+ outputFile.writeLong(dlRequest);
+ } finally {
+ if (outputFile != null) {
+ outputFile.flush();
+ outputFile.close();
+ }
+ }
+ }
+
+ /**
+ * Waits for a previously-initiated download and verifies it has completed successfully.
+ *
+ * @throws Exception if unsuccessful
+ */
+ public void verifyFileDownloadSucceeded() throws Exception {
+ String filename = DOWNLOAD_1MB_FILENAME;
+ long filesize = DOWNLOAD_1MB_FILESIZE;
+ long dlRequest = -1;
+ boolean rebootMarkerValid = false;
+ DataInputStream dataInputFile = null;
+
+ setWiFiStateOn(true);
+ setAirplaneModeOn(false);
+
+ try {
+ FileInputStream inFile = mContext.openFileInput(DOWNLOAD_STARTED_FLAG);
+ dataInputFile = new DataInputStream(inFile);
+ dlRequest = dataInputFile.readLong();
+ } catch (Exception e) {
+ // The file was't valid so we just leave the flag false
+ Log.i(LOG_TAG, "Unable to determine initial download id.");
+ throw e;
+ } finally {
+ if (dataInputFile != null) {
+ dataInputFile.close();
+ }
+ mContext.deleteFile(DOWNLOAD_STARTED_FLAG);
+ }
+
+ assertTrue(dlRequest != -1);
+ Cursor cursor = getCursor(dlRequest);
+ ParcelFileDescriptor pfd = null;
+ try {
+ assertTrue("Unable to query last initiated download!", cursor.moveToFirst());
+
+ int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+ int status = cursor.getInt(columnIndex);
+ int currentWaitTime = 0;
+
+ // Wait until the download finishes
+ waitForDownloadOrTimeout(dlRequest);
+
+ Log.i(LOG_TAG, "Verifying download information...");
+ // Verify specific info about the file (size, name, etc)...
+ pfd = mDownloadManager.openDownloadedFile(dlRequest);
+ verifyFileSize(pfd, filesize);
+ } catch (Exception e) {
+ Log.i(LOG_TAG, "error: " + e.toString());
+ throw e;
+ } finally {
+ // Clean up...
+ cursor.close();
+ mDownloadManager.remove(dlRequest);
+ if (pfd != null) {
+ pfd.close();
+ }
+ }
+ }
+
+ /**
+ * Tests downloading a large file over WiFi (~10 Mb).
+ *
+ * @throws Exception if unsuccessful
+ */
+ public void runLargeDownloadOverWiFi() throws Exception {
+ String filename = DOWNLOAD_10MB_FILENAME;
+ long filesize = DOWNLOAD_10MB_FILESIZE;
+ long dlRequest = -1;
+ doCommonDownloadSetup();
+
+ // Make sure there are no pending downloads currently going on
+ removeAllCurrentDownloads();
+
+ Uri remoteUri = getExternalFileUri(filename);
+ Request request = new Request(remoteUri);
+ request.setMediaType(getMimeMapping(DownloadFileType.APK));
+
+ dlRequest = mDownloadManager.enqueue(request);
+
+ // Rather large file, so wait up to 15 mins...
+ waitForDownloadOrTimeout(dlRequest, WAIT_FOR_DOWNLOAD_POLL_TIME, 15 * 60 * 1000);
+
+ Cursor cursor = getCursor(dlRequest);
+ ParcelFileDescriptor pfd = null;
+ try {
+ Log.i(LOG_TAG, "Verifying download information...");
+ // Verify specific info about the file (size, name, etc)...
+ pfd = mDownloadManager.openDownloadedFile(dlRequest);
+ verifyFileSize(pfd, filesize);
+ } finally {
+ if (pfd != null) {
+ pfd.close();
+ }
+ mDownloadManager.remove(dlRequest);
+ cursor.close();
+ }
+ }
+
+ /**
+ * Tests that downloads resume when switching back and forth from having connectivity to
+ * having no connectivity using both WiFi and airplane mode.
+ *
+ * Note: Device has no mobile access when running this test.
+ *
+ * @throws Exception if unsuccessful
+ */
+ public void runDownloadMultipleSwitching() throws Exception {
+ String filename = DOWNLOAD_500K_FILENAME;
+ long filesize = DOWNLOAD_500K_FILESIZE;
+ doCommonDownloadSetup();
+
+ String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+ File downloadedFile = new File(localDownloadDirectory, filename);
+
+ long dlRequest = -1;
+ try {
+ downloadedFile.delete();
+
+ // Make sure there are no pending downloads currently going on
+ removeAllCurrentDownloads();
+
+ Uri remoteUri = getExternalFileUri(filename);
+ Request request = new Request(remoteUri);
+
+ // Local destination of downloaded file
+ Uri localUri = Uri.fromFile(downloadedFile);
+ Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+ request.setDestinationUri(localUri);
+
+ request.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI);
+
+ dlRequest = mDownloadManager.enqueue(request);
+ waitForDownloadToStart(dlRequest);
+ // make sure we're starting to download some data...
+ waitForFileToGrow(downloadedFile);
+
+ // download disable
+ setWiFiStateOn(false);
+
+ // download disable
+ Log.i(LOG_TAG, "Turning on airplane mode...");
+ setAirplaneModeOn(true);
+ Thread.sleep(30 * 1000); // wait 30 secs
+
+ // download disable
+ setWiFiStateOn(true);
+ Thread.sleep(30 * 1000); // wait 30 secs
+
+ // download enable
+ Log.i(LOG_TAG, "Turning off airplane mode...");
+ setAirplaneModeOn(false);
+ Thread.sleep(5 * 1000); // wait 5 seconds
+
+ // download disable
+ Log.i(LOG_TAG, "Turning off WiFi...");
+ setWiFiStateOn(false);
+ Thread.sleep(30 * 1000); // wait 30 secs
+
+ // finally, turn WiFi back on and finish up the download
+ Log.i(LOG_TAG, "Turning on WiFi...");
+ setWiFiStateOn(true);
+ Log.i(LOG_TAG, "Waiting up to 3 minutes for download to complete...");
+ waitForDownloadsOrTimeout(dlRequest, 3 * 60 * 1000);
+ ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+ verifyFileSize(pfd, filesize);
+ } finally {
+ Log.i(LOG_TAG, "Cleaning up files...");
+ if (dlRequest != -1) {
+ mDownloadManager.remove(dlRequest);
+ }
+ downloadedFile.delete();
+ }
+ }
+
+ /**
+ * Tests that downloads resume when switching on/off WiFi at various intervals.
+ *
+ * Note: Device has no mobile access when running this test.
+ *
+ * @throws Exception if unsuccessful
+ */
+ public void runDownloadMultipleWiFiEnableDisable() throws Exception {
+ String filename = DOWNLOAD_500K_FILENAME;
+ long filesize = DOWNLOAD_500K_FILESIZE;
+ doCommonDownloadSetup();
+
+ String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+ File downloadedFile = new File(localDownloadDirectory, filename);
+ long dlRequest = -1;
+ try {
+ downloadedFile.delete();
+
+ // Make sure there are no pending downloads currently going on
+ removeAllCurrentDownloads();
+
+ Uri remoteUri = getExternalFileUri(filename);
+ Request request = new Request(remoteUri);
+
+ // Local destination of downloaded file
+ Uri localUri = Uri.fromFile(downloadedFile);
+ Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+ request.setDestinationUri(localUri);
+
+ request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+ dlRequest = mDownloadManager.enqueue(request);
+ waitForDownloadToStart(dlRequest);
+ // are we making any progress?
+ waitForFileToGrow(downloadedFile);
+
+ // download disable
+ Log.i(LOG_TAG, "Turning off WiFi...");
+ setWiFiStateOn(false);
+ Thread.sleep(40 * 1000); // wait 40 seconds
+
+ // enable download...
+ Log.i(LOG_TAG, "Turning on WiFi again...");
+ setWiFiStateOn(true);
+ waitForFileToGrow(downloadedFile);
+
+ // download disable
+ Log.i(LOG_TAG, "Turning off WiFi...");
+ setWiFiStateOn(false);
+ Thread.sleep(20 * 1000); // wait 20 seconds
+
+ // enable download...
+ Log.i(LOG_TAG, "Turning on WiFi again...");
+ setWiFiStateOn(true);
+
+ Log.i(LOG_TAG, "Waiting up to 3 minutes for download to complete...");
+ waitForDownloadsOrTimeout(dlRequest, 3 * 60 * 1000);
+ ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+ verifyFileSize(pfd, filesize);
+ } finally {
+ Log.i(LOG_TAG, "Cleaning up files...");
+ if (dlRequest != -1) {
+ mDownloadManager.remove(dlRequest);
+ }
+ downloadedFile.delete();
+ }
+ }
+
+ /**
+ * Tests that downloads resume when switching on/off Airplane mode numerous times at
+ * various intervals.
+ *
+ * Note: Device has no mobile access when running this test.
+ *
+ * @throws Exception if unsuccessful
+ */
+ public void runDownloadMultipleAirplaneModeEnableDisable() throws Exception {
+ String filename = DOWNLOAD_500K_FILENAME;
+ long filesize = DOWNLOAD_500K_FILESIZE;
+ // make sure WiFi is enabled, and airplane mode is not on
+ doCommonDownloadSetup();
+
+ String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+ File downloadedFile = new File(localDownloadDirectory, filename);
+ long dlRequest = -1;
+ try {
+ downloadedFile.delete();
+
+ // Make sure there are no pending downloads currently going on
+ removeAllCurrentDownloads();
+
+ Uri remoteUri = getExternalFileUri(filename);
+ Request request = new Request(remoteUri);
+
+ // Local destination of downloaded file
+ Uri localUri = Uri.fromFile(downloadedFile);
+ Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+ request.setDestinationUri(localUri);
+
+ request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+ dlRequest = mDownloadManager.enqueue(request);
+ waitForDownloadToStart(dlRequest);
+ // are we making any progress?
+ waitForFileToGrow(downloadedFile);
+
+ // download disable
+ Log.i(LOG_TAG, "Turning on Airplane mode...");
+ setAirplaneModeOn(true);
+ Thread.sleep(60 * 1000); // wait 1 minute
+
+ // download enable
+ Log.i(LOG_TAG, "Turning off Airplane mode...");
+ setAirplaneModeOn(false);
+ // make sure we're starting to download some data...
+ waitForFileToGrow(downloadedFile);
+
+ // reenable the connection to start up the download again
+ Log.i(LOG_TAG, "Turning on Airplane mode again...");
+ setAirplaneModeOn(true);
+ Thread.sleep(20 * 1000); // wait 20 seconds
+
+ // Finish up the download...
+ Log.i(LOG_TAG, "Turning off Airplane mode again...");
+ setAirplaneModeOn(false);
+
+ Log.i(LOG_TAG, "Waiting up to 3 minutes for donwload to complete...");
+ waitForDownloadsOrTimeout(dlRequest, 180 * 1000); // wait up to 3 mins before timeout
+ ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+ verifyFileSize(pfd, filesize);
+ } finally {
+ Log.i(LOG_TAG, "Cleaning up files...");
+ if (dlRequest != -1) {
+ mDownloadManager.remove(dlRequest);
+ }
+ downloadedFile.delete();
+ }
+ }
+}
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java
new file mode 100644
index 0000000..0f16619
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.downloadmanagertests;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+
+import com.android.frameworks.downloadmanagertests.DownloadManagerTestApp;
+
+import junit.framework.TestSuite;
+
+/**
+ * Instrumentation Test Runner for all download manager tests.
+ *
+ * To run the download manager tests:
+ *
+ * adb shell am instrument -e external_download_1mb_uri <uri> external_download_500k_uri <uri> \
+ * -w com.android.frameworks.downloadmanagertests/.DownloadManagerTestRunner
+ */
+
+public class DownloadManagerTestRunner extends InstrumentationTestRunner {
+ private static final String EXTERNAL_DOWNLOAD_URI_KEY = "external_download_uri";
+ public String externalDownloadUriValue = null;
+
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(DownloadManagerTestApp.class);
+ return suite;
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return DownloadManagerTestRunner.class.getClassLoader();
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ // Extract the extra params passed in from the bundle...
+ String externalDownloadUri = (String) icicle.get(EXTERNAL_DOWNLOAD_URI_KEY);
+ if (externalDownloadUri != null) {
+ externalDownloadUriValue = externalDownloadUri;
+ }
+ super.onCreate(icicle);
+ }
+
+}
diff --git a/core/tests/utillib/Android.mk b/core/tests/utillib/Android.mk
new file mode 100644
index 0000000..299ea5a
--- /dev/null
+++ b/core/tests/utillib/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_MODULE := frameworks-core-util-lib
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/core/tests/utillib/src/coretestutils/http/MockResponse.java b/core/tests/utillib/src/coretestutils/http/MockResponse.java
new file mode 100644
index 0000000..5b03e5f
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/MockResponse.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 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 coretestutils.http;
+
+import static coretestutils.http.MockWebServer.ASCII;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.util.Log;
+
+/**
+ * A scripted response to be replayed by the mock web server.
+ */
+public class MockResponse {
+ private static final byte[] EMPTY_BODY = new byte[0];
+ static final String LOG_TAG = "coretestutils.http.MockResponse";
+
+ private String status = "HTTP/1.1 200 OK";
+ private Map<String, String> headers = new HashMap<String, String>();
+ private byte[] body = EMPTY_BODY;
+ private boolean closeConnectionAfter = false;
+ private String closeConnectionAfterHeader = null;
+ private int closeConnectionAfterXBytes = -1;
+ private int pauseConnectionAfterXBytes = -1;
+ private File bodyExternalFile = null;
+
+ public MockResponse() {
+ addHeader("Content-Length", 0);
+ }
+
+ /**
+ * Returns the HTTP response line, such as "HTTP/1.1 200 OK".
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ public MockResponse setResponseCode(int code) {
+ this.status = "HTTP/1.1 " + code + " OK";
+ return this;
+ }
+
+ /**
+ * Returns the HTTP headers, such as "Content-Length: 0".
+ */
+ public List<String> getHeaders() {
+ List<String> headerStrings = new ArrayList<String>();
+ for (String header : headers.keySet()) {
+ headerStrings.add(header + ": " + headers.get(header));
+ }
+ return headerStrings;
+ }
+
+ public MockResponse addHeader(String header, String value) {
+ headers.put(header.toLowerCase(), value);
+ return this;
+ }
+
+ public MockResponse addHeader(String header, long value) {
+ return addHeader(header, Long.toString(value));
+ }
+
+ public MockResponse removeHeader(String header) {
+ headers.remove(header.toLowerCase());
+ return this;
+ }
+
+ /**
+ * Returns true if the body should come from an external file, false otherwise.
+ */
+ private boolean bodyIsExternal() {
+ return bodyExternalFile != null;
+ }
+
+ /**
+ * Returns an input stream containing the raw HTTP payload.
+ */
+ public InputStream getBody() {
+ if (bodyIsExternal()) {
+ try {
+ return new FileInputStream(bodyExternalFile);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "File not found: " + bodyExternalFile.getAbsolutePath());
+ }
+ }
+ return new ByteArrayInputStream(this.body);
+ }
+
+ public MockResponse setBody(File body) {
+ addHeader("Content-Length", body.length());
+ this.bodyExternalFile = body;
+ return this;
+ }
+
+ public MockResponse setBody(byte[] body) {
+ addHeader("Content-Length", body.length);
+ this.body = body;
+ return this;
+ }
+
+ public MockResponse setBody(String body) {
+ try {
+ return setBody(body.getBytes(ASCII));
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Sets the body as chunked.
+ *
+ * Currently chunked body is not supported for external files as bodies.
+ */
+ public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException {
+ addHeader("Transfer-encoding", "chunked");
+
+ ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+ int pos = 0;
+ while (pos < body.length) {
+ int chunkSize = Math.min(body.length - pos, maxChunkSize);
+ bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII));
+ bytesOut.write("\r\n".getBytes(ASCII));
+ bytesOut.write(body, pos, chunkSize);
+ bytesOut.write("\r\n".getBytes(ASCII));
+ pos += chunkSize;
+ }
+ bytesOut.write("0\r\n".getBytes(ASCII));
+ this.body = bytesOut.toByteArray();
+ return this;
+ }
+
+ public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException {
+ return setChunkedBody(body.getBytes(ASCII), maxChunkSize);
+ }
+
+ @Override public String toString() {
+ return status;
+ }
+
+ public boolean shouldCloseConnectionAfter() {
+ return closeConnectionAfter;
+ }
+
+ public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) {
+ this.closeConnectionAfter = closeConnectionAfter;
+ return this;
+ }
+
+ /**
+ * Sets the header after which sending the server should close the connection.
+ */
+ public MockResponse setCloseConnectionAfterHeader(String header) {
+ closeConnectionAfterHeader = header;
+ setCloseConnectionAfter(true);
+ return this;
+ }
+
+ /**
+ * Returns the header after which sending the server should close the connection.
+ */
+ public String getCloseConnectionAfterHeader() {
+ return closeConnectionAfterHeader;
+ }
+
+ /**
+ * Sets the number of bytes in the body to send before which the server should close the
+ * connection. Set to -1 to unset and send the entire body (default).
+ */
+ public MockResponse setCloseConnectionAfterXBytes(int position) {
+ closeConnectionAfterXBytes = position;
+ setCloseConnectionAfter(true);
+ return this;
+ }
+
+ /**
+ * Returns the number of bytes in the body to send before which the server should close the
+ * connection. Returns -1 if the entire body should be sent (default).
+ */
+ public int getCloseConnectionAfterXBytes() {
+ return closeConnectionAfterXBytes;
+ }
+
+ /**
+ * Sets the number of bytes in the body to send before which the server should pause the
+ * connection (stalls in sending data). Only one pause per response is supported.
+ * Set to -1 to unset pausing (default).
+ */
+ public MockResponse setPauseConnectionAfterXBytes(int position) {
+ pauseConnectionAfterXBytes = position;
+ return this;
+ }
+
+ /**
+ * Returns the number of bytes in the body to send before which the server should pause the
+ * connection (stalls in sending data). (Returns -1 if it should not pause).
+ */
+ public int getPauseConnectionAfterXBytes() {
+ return pauseConnectionAfterXBytes;
+ }
+
+ /**
+ * Returns true if this response is flagged to pause the connection mid-stream, false otherwise
+ */
+ public boolean getShouldPause() {
+ return (pauseConnectionAfterXBytes != -1);
+ }
+
+ /**
+ * Returns true if this response is flagged to close the connection mid-stream, false otherwise
+ */
+ public boolean getShouldClose() {
+ return (closeConnectionAfterXBytes != -1);
+ }
+}
diff --git a/core/tests/utillib/src/coretestutils/http/MockWebServer.java b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
new file mode 100644
index 0000000..c329ffa
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2010 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 coretestutils.http;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import android.util.Log;
+
+/**
+ * A scriptable web server. Callers supply canned responses and the server
+ * replays them upon request in sequence.
+ *
+ * TODO: merge with the version from libcore/support/src/tests/java once it's in.
+ */
+public final class MockWebServer {
+ static final String ASCII = "US-ASCII";
+ static final String LOG_TAG = "coretestutils.http.MockWebServer";
+
+ private final BlockingQueue<RecordedRequest> requestQueue
+ = new LinkedBlockingQueue<RecordedRequest>();
+ private final BlockingQueue<MockResponse> responseQueue
+ = new LinkedBlockingQueue<MockResponse>();
+ private int bodyLimit = Integer.MAX_VALUE;
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+ // keep Futures around so we can rethrow any exceptions thrown by Callables
+ private final Queue<Future<?>> futures = new LinkedList<Future<?>>();
+ private final Object downloadPauseLock = new Object();
+ // global flag to signal when downloads should resume on the server
+ private volatile boolean downloadResume = false;
+
+ private int port = -1;
+
+ public int getPort() {
+ if (port == -1) {
+ throw new IllegalStateException("Cannot retrieve port before calling play()");
+ }
+ return port;
+ }
+
+ /**
+ * Returns a URL for connecting to this server.
+ *
+ * @param path the request path, such as "/".
+ */
+ public URL getUrl(String path) throws MalformedURLException {
+ return new URL("http://localhost:" + getPort() + path);
+ }
+
+ /**
+ * Sets the number of bytes of the POST body to keep in memory to the given
+ * limit.
+ */
+ public void setBodyLimit(int maxBodyLength) {
+ this.bodyLimit = maxBodyLength;
+ }
+
+ public void enqueue(MockResponse response) {
+ responseQueue.add(response);
+ }
+
+ /**
+ * Awaits the next HTTP request, removes it, and returns it. Callers should
+ * use this to verify the request sent was as intended.
+ */
+ public RecordedRequest takeRequest() throws InterruptedException {
+ return requestQueue.take();
+ }
+
+ public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException {
+ return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ public List<RecordedRequest> drainRequests() {
+ List<RecordedRequest> requests = new ArrayList<RecordedRequest>();
+ requestQueue.drainTo(requests);
+ return requests;
+ }
+
+ /**
+ * Starts the server, serves all enqueued requests, and shuts the server
+ * down using the default (server-assigned) port.
+ */
+ public void play() throws IOException {
+ play(0);
+ }
+
+ /**
+ * Starts the server, serves all enqueued requests, and shuts the server
+ * down.
+ *
+ * @param port The port number to use to listen to connections on; pass in 0 to have the
+ * server automatically assign a free port
+ */
+ public void play(int portNumber) throws IOException {
+ final ServerSocket ss = new ServerSocket(portNumber);
+ ss.setReuseAddress(true);
+ port = ss.getLocalPort();
+ submitCallable(new Callable<Void>() {
+ public Void call() throws Exception {
+ int count = 0;
+ while (true) {
+ if (count > 0 && responseQueue.isEmpty()) {
+ ss.close();
+ executor.shutdown();
+ return null;
+ }
+
+ serveConnection(ss.accept());
+ count++;
+ }
+ }
+ });
+ }
+
+ private void serveConnection(final Socket s) {
+ submitCallable(new Callable<Void>() {
+ public Void call() throws Exception {
+ InputStream in = new BufferedInputStream(s.getInputStream());
+ OutputStream out = new BufferedOutputStream(s.getOutputStream());
+
+ int sequenceNumber = 0;
+ while (true) {
+ RecordedRequest request = readRequest(in, sequenceNumber);
+ if (request == null) {
+ if (sequenceNumber == 0) {
+ throw new IllegalStateException("Connection without any request!");
+ } else {
+ break;
+ }
+ }
+ requestQueue.add(request);
+ MockResponse response = computeResponse(request);
+ writeResponse(out, response);
+ if (response.shouldCloseConnectionAfter()) {
+ break;
+ }
+ sequenceNumber++;
+ }
+
+ in.close();
+ out.close();
+ return null;
+ }
+ });
+ }
+
+ private void submitCallable(Callable<?> callable) {
+ Future<?> future = executor.submit(callable);
+ futures.add(future);
+ }
+
+ /**
+ * Check for and raise any exceptions that have been thrown by child threads. Will not block on
+ * children still running.
+ * @throws ExecutionException for the first child thread that threw an exception
+ */
+ public void checkForExceptions() throws ExecutionException, InterruptedException {
+ final int originalSize = futures.size();
+ for (int i = 0; i < originalSize; i++) {
+ Future<?> future = futures.remove();
+ try {
+ future.get(0, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ futures.add(future); // still running
+ }
+ }
+ }
+
+ /**
+ * @param sequenceNumber the index of this request on this connection.
+ */
+ private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
+ String request = readAsciiUntilCrlf(in);
+ if (request.equals("")) {
+ return null; // end of data; no more requests
+ }
+
+ List<String> headers = new ArrayList<String>();
+ int contentLength = -1;
+ boolean chunked = false;
+ String header;
+ while (!(header = readAsciiUntilCrlf(in)).equals("")) {
+ headers.add(header);
+ String lowercaseHeader = header.toLowerCase();
+ if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
+ contentLength = Integer.parseInt(header.substring(15).trim());
+ }
+ if (lowercaseHeader.startsWith("transfer-encoding:") &&
+ lowercaseHeader.substring(18).trim().equals("chunked")) {
+ chunked = true;
+ }
+ }
+
+ boolean hasBody = false;
+ TruncatingOutputStream requestBody = new TruncatingOutputStream();
+ List<Integer> chunkSizes = new ArrayList<Integer>();
+ if (contentLength != -1) {
+ hasBody = true;
+ transfer(contentLength, in, requestBody);
+ } else if (chunked) {
+ hasBody = true;
+ while (true) {
+ int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
+ if (chunkSize == 0) {
+ readEmptyLine(in);
+ break;
+ }
+ chunkSizes.add(chunkSize);
+ transfer(chunkSize, in, requestBody);
+ readEmptyLine(in);
+ }
+ }
+
+ if (request.startsWith("GET ")) {
+ if (hasBody) {
+ throw new IllegalArgumentException("GET requests should not have a body!");
+ }
+ } else if (request.startsWith("POST ")) {
+ if (!hasBody) {
+ throw new IllegalArgumentException("POST requests must have a body!");
+ }
+ } else {
+ throw new UnsupportedOperationException("Unexpected method: " + request);
+ }
+
+ return new RecordedRequest(request, headers, chunkSizes,
+ requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
+ }
+
+ /**
+ * Returns a response to satisfy {@code request}.
+ */
+ private MockResponse computeResponse(RecordedRequest request) throws InterruptedException {
+ if (responseQueue.isEmpty()) {
+ throw new IllegalStateException("Unexpected request: " + request);
+ }
+ return responseQueue.take();
+ }
+
+ private void writeResponse(OutputStream out, MockResponse response) throws IOException {
+ out.write((response.getStatus() + "\r\n").getBytes(ASCII));
+ boolean doCloseConnectionAfterHeader = (response.getCloseConnectionAfterHeader() != null);
+
+ // Send headers
+ String closeConnectionAfterHeader = response.getCloseConnectionAfterHeader();
+ for (String header : response.getHeaders()) {
+ out.write((header + "\r\n").getBytes(ASCII));
+
+ if (doCloseConnectionAfterHeader && header.startsWith(closeConnectionAfterHeader)) {
+ Log.i(LOG_TAG, "Closing connection after header" + header);
+ break;
+ }
+ }
+
+ // Send actual body data
+ if (!doCloseConnectionAfterHeader) {
+ out.write(("\r\n").getBytes(ASCII));
+
+ InputStream body = response.getBody();
+ final int READ_BLOCK_SIZE = 10000; // process blocks this size
+ byte[] currentBlock = new byte[READ_BLOCK_SIZE];
+ int currentBlockSize = 0;
+ int writtenSoFar = 0;
+
+ boolean shouldPause = response.getShouldPause();
+ boolean shouldClose = response.getShouldClose();
+ int pause = response.getPauseConnectionAfterXBytes();
+ int close = response.getCloseConnectionAfterXBytes();
+
+ // Don't bother pausing if it's set to pause -after- the connection should be dropped
+ if (shouldPause && shouldClose && (pause > close)) {
+ shouldPause = false;
+ }
+
+ // Process each block we read in...
+ while ((currentBlockSize = body.read(currentBlock)) != -1) {
+ int startIndex = 0;
+ int writeLength = currentBlockSize;
+
+ // handle the case of pausing
+ if (shouldPause && (writtenSoFar + currentBlockSize >= pause)) {
+ writeLength = pause - writtenSoFar;
+ out.write(currentBlock, 0, writeLength);
+ out.flush();
+ writtenSoFar += writeLength;
+
+ // now pause...
+ try {
+ Log.i(LOG_TAG, "Pausing connection after " + pause + " bytes");
+ // Wait until someone tells us to resume sending...
+ synchronized(downloadPauseLock) {
+ while (!downloadResume) {
+ downloadPauseLock.wait();
+ }
+ // reset resume back to false
+ downloadResume = false;
+ }
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Server was interrupted during pause in download.");
+ }
+
+ startIndex = writeLength;
+ writeLength = currentBlockSize - writeLength;
+ }
+
+ // handle the case of closing the connection
+ if (shouldClose && (writtenSoFar + writeLength > close)) {
+ writeLength = close - writtenSoFar;
+ out.write(currentBlock, startIndex, writeLength);
+ writtenSoFar += writeLength;
+ Log.i(LOG_TAG, "Closing connection after " + close + " bytes");
+ break;
+ }
+ out.write(currentBlock, startIndex, writeLength);
+ writtenSoFar += writeLength;
+ }
+ }
+ out.flush();
+ }
+
+ /**
+ * Transfer bytes from {@code in} to {@code out} until either {@code length}
+ * bytes have been transferred or {@code in} is exhausted.
+ */
+ private void transfer(int length, InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ while (length > 0) {
+ int count = in.read(buffer, 0, Math.min(buffer.length, length));
+ if (count == -1) {
+ return;
+ }
+ out.write(buffer, 0, count);
+ length -= count;
+ }
+ }
+
+ /**
+ * Returns the text from {@code in} until the next "\r\n", or null if
+ * {@code in} is exhausted.
+ */
+ private String readAsciiUntilCrlf(InputStream in) throws IOException {
+ StringBuilder builder = new StringBuilder();
+ while (true) {
+ int c = in.read();
+ if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ } else if (c == -1) {
+ return builder.toString();
+ } else {
+ builder.append((char) c);
+ }
+ }
+ }
+
+ private void readEmptyLine(InputStream in) throws IOException {
+ String line = readAsciiUntilCrlf(in);
+ if (!line.equals("")) {
+ throw new IllegalStateException("Expected empty but was: " + line);
+ }
+ }
+
+ /**
+ * An output stream that drops data after bodyLimit bytes.
+ */
+ private class TruncatingOutputStream extends ByteArrayOutputStream {
+ private int numBytesReceived = 0;
+ @Override public void write(byte[] buffer, int offset, int len) {
+ numBytesReceived += len;
+ super.write(buffer, offset, Math.min(len, bodyLimit - count));
+ }
+ @Override public void write(int oneByte) {
+ numBytesReceived++;
+ if (count < bodyLimit) {
+ super.write(oneByte);
+ }
+ }
+ }
+
+ /**
+ * Trigger the server to resume sending the download
+ */
+ public void doResumeDownload() {
+ synchronized (downloadPauseLock) {
+ downloadResume = true;
+ downloadPauseLock.notifyAll();
+ }
+ }
+}
diff --git a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
new file mode 100644
index 0000000..293ff80
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 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 coretestutils.http;
+
+import java.util.List;
+
+/**
+ * An HTTP request that came into the mock web server.
+ */
+public final class RecordedRequest {
+ private final String requestLine;
+ private final List<String> headers;
+ private final List<Integer> chunkSizes;
+ private final int bodySize;
+ private final byte[] body;
+ private final int sequenceNumber;
+
+ RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
+ int bodySize, byte[] body, int sequenceNumber) {
+ this.requestLine = requestLine;
+ this.headers = headers;
+ this.chunkSizes = chunkSizes;
+ this.bodySize = bodySize;
+ this.body = body;
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ public String getRequestLine() {
+ return requestLine;
+ }
+
+ public List<String> getHeaders() {
+ return headers;
+ }
+
+ /**
+ * Returns the sizes of the chunks of this request's body, or an empty list
+ * if the request's body was empty or unchunked.
+ */
+ public List<Integer> getChunkSizes() {
+ return chunkSizes;
+ }
+
+ /**
+ * Returns the total size of the body of this POST request (before
+ * truncation).
+ */
+ public int getBodySize() {
+ return bodySize;
+ }
+
+ /**
+ * Returns the body of this POST request. This may be truncated.
+ */
+ public byte[] getBody() {
+ return body;
+ }
+
+ /**
+ * Returns the index of this request on its HTTP connection. Since a single
+ * HTTP connection may serve multiple requests, each request is assigned its
+ * own sequence number.
+ */
+ public int getSequenceNumber() {
+ return sequenceNumber;
+ }
+
+ @Override public String toString() {
+ return requestLine;
+ }
+
+ public String getMethod() {
+ return getRequestLine().split(" ")[0];
+ }
+
+ public String getPath() {
+ return getRequestLine().split(" ")[1];
+ }
+}
diff --git a/docs/html/guide/developing/eclipse-adt.jd b/docs/html/guide/developing/eclipse-adt.jd
index 9c77ece..d0fc9b8 100644
--- a/docs/html/guide/developing/eclipse-adt.jd
+++ b/docs/html/guide/developing/eclipse-adt.jd
@@ -21,6 +21,7 @@
<li><a href="#librarySetup">Setting up a library project</a></li>
<li><a href="#libraryReference">Referencing a library project</a></li>
<li><a href="#considerations">Development considerations</a></li>
+ <li><a href="#libraryMigrating">Migrating library projects to ADT 0.9.8</a></li>
</ol>
</li>
<li><a href="#Tips">Eclipse Tips</a></li>
@@ -644,10 +645,6 @@
is because the library project is compiled by the main project to use the
correct resource IDs.</p>
-<p><strong>One library project cannot reference another</strong></p>
-
-<p>A library cannot depend on another library.</p>
-
<p><strong>A library project can include a JAR library</strong></p>
<p>You can develop a library project that itself includes a JAR library, however
@@ -664,13 +661,6 @@
href="{@docRoot}guide/topics/manifest/uses-library-element.html"><code><uses-library></code></a>
element. </p>
-<p><strong>Library project can not include AIDL files</strong></p>
-
-<p>The tools do not support the use of <a
-href="{@docRoot}guide/developing/tools/aidl.html">AIDL</a> files in a library project.
-Any AIDL files used by an application must be stored in the application project
-itself.</p>
-
<p><strong>Library project can not include raw assets</strong></p>
<p>The tools do not support the use of raw asset files in a library project.
@@ -730,8 +720,76 @@
library project What is important is that the main project can reference the
library project through a relative link.</p>
+<h3 id="libraryMigrating">Migrating library projects to ADT 0.9.8</h3>
-<h2 id="Tips">Eclipse Tips </h2>
+<p>This section provides information about how to migrate a library project
+created with ADT 0.9.7 to ADT 0.9.8 (or higher). The migration is needed only if
+you are developing in Eclipse with ADT and assumes that you have also upgraded
+to SDK Tools r7 (or higher). </p>
+
+<p>The way that ADT handles library projects has changed between
+ADT 0.9.7 and ADT 0.9.8. Specifically, in ADT 0.9.7, the <code>src/</code>
+source folder of the library was linked into the dependent application project
+as a folder that had the same name as the library project. This worked because
+of two restrictions on the library projects:</p>
+
+<ul>
+<li>The library was only able to contain a single source folder (excluding the
+special <code>gen/</code> source folder), and</li>
+<li>The source folder was required to have the name <code>src/</code> and be
+stored at the root of the project.</li>
+</ul>
+
+<p>In ADT 0.9.8, both of those restrictions were removed. A library project can
+have as many source folders as needed and each can have any name. Additionally,
+a library project can store source folders in any location of the project. For
+example, you could store sources in a <code>src/java/</code> directory. In order
+to support this, the name of the linked source folders in the main project are
+now called <<em>library-name</em>>_<<em>folder-name</em>> For
+example: <code>MyLibrary_src/</code> or <code>MyLibrary_src_java/</code>.</p>
+
+<p>Additionally, the linking process now flags those folders in order for ADT to
+recognize that it created them. This will allow ADT to automatically migrate the
+project to new versions of ADT, should they contain changes to the handling of
+library projects. ADT 0.9.7 did not flag the linked source folders, so ADT 0.9.8
+cannot be sure whether the old linked folders can be removed safely. After
+upgrading ADT to 0.9.8, you will need to remove the old linked folders manually
+in a simple two-step process, as described below.</p>
+
+<p>Before you begin, make sure to create a backup copy of your application or
+save the latest version to your code version control system. This ensures that
+you will be able to easily revert the migration changes in case there is a
+problem in your environment.</p>
+
+<p>When you first upgrade to ADT 0.9.8, your main project will look as shown
+below, with two linked folders (in this example, <code>MyLibrary</code> and
+<code>MyLibrary_src</code> — both of which link to
+<code>MyLibrary/src</code>. Eclipse shows an error on one of them because they
+are duplicate links to a single class.</p>
+
+<img src="{@docRoot}images/developing/lib-migration-0.png" alt="">
+
+<p>To fix the error, remove the linked folder that <em>does not</em> contain the
+<code>_src</code> suffix. </p>
+
+<ol>
+<li>Right click the folder that you want to remove (in this case, the
+<code>MyLibrary</code> folder) and choose <strong>Build Path</strong> >
+<strong>Remove from Build Path</strong>, as shown below.</li>
+
+<img src="{@docRoot}images/developing/lib-migration-1.png" style="height:600px"
+alt="">
+
+<li>Next, When asked about unlinking the folder from the project, select
+<strong>Yes</strong>, as shown below.</li>
+
+<img src="{@docRoot}images/developing/lib-migration-2.png" alt="">
+</ol>
+
+<p>This should resolve the error and migrate your library project to the new
+ADT environment. </p>
+
+<h2 id="Tips">Eclipse Tips</h2>
<h3 id="arbitraryexpressions">Executing arbitrary Java expressions in Eclipse</h3>
diff --git a/docs/html/guide/developing/other-ide.jd b/docs/html/guide/developing/other-ide.jd
index 1d67aa9..ff13f43 100644
--- a/docs/html/guide/developing/other-ide.jd
+++ b/docs/html/guide/developing/other-ide.jd
@@ -838,10 +838,6 @@
is because the library project is compiled by the main project to use the
correct resource IDs.</p>
-<p><strong>One library project cannot reference another</strong></p>
-
-<p>A library cannot depend on another library.</p>
-
<p><strong>A library project can include a JAR library</strong></p>
<p>You can develop a library project that itself includes a JAR library. When
@@ -858,13 +854,6 @@
href="{@docRoot}guide/topics/manifest/uses-library-element.html"><code><uses-library></code></a>
element. </p>
-<p><strong>Library project cannot include AIDL files</strong></p>
-
-<p>The tools do not support the use of <a
-href="{@docRoot}guide/developing/tools/aidl.html">AIDL</a> files in a library project.
-Any AIDL files used by an application must be stored in the application project
-itself.</p>
-
<p><strong>Library project cannot include raw assets</strong></p>
<p>The tools do not support the use of raw asset files in a library project.
diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index 74b544b..d047b2d 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -472,18 +472,25 @@
progressDialog = new ProgressDialog(NotificationTest.this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("Loading...");
- progressThread = new ProgressThread(handler);
- progressThread.start();
return progressDialog;
default:
return null;
}
}
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ switch(id) {
+ case PROGRESS_DIALOG:
+ progressDialog.setProgress(0);
+ progressThread = new ProgressThread(handler);
+ progressThread.start();
+ }
+
// Define the Handler that receives messages from the thread and update the progress
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
- int total = msg.getData().getInt("total");
+ int total = msg.arg1;
progressDialog.setProgress(total);
if (total >= 100){
dismissDialog(PROGRESS_DIALOG);
@@ -514,9 +521,7 @@
Log.e("ERROR", "Thread Interrupted");
}
Message msg = mHandler.obtainMessage();
- Bundle b = new Bundle();
- b.putInt("total", total);
- msg.setData(b);
+ msg.arg1 = total;
mHandler.sendMessage(msg);
total++;
}
diff --git a/docs/html/images/developing/lib-migration-0.png b/docs/html/images/developing/lib-migration-0.png
new file mode 100644
index 0000000..226b0a5
--- /dev/null
+++ b/docs/html/images/developing/lib-migration-0.png
Binary files differ
diff --git a/docs/html/images/developing/lib-migration-1.png b/docs/html/images/developing/lib-migration-1.png
new file mode 100644
index 0000000..f413dab
--- /dev/null
+++ b/docs/html/images/developing/lib-migration-1.png
Binary files differ
diff --git a/docs/html/images/developing/lib-migration-2.png b/docs/html/images/developing/lib-migration-2.png
new file mode 100644
index 0000000..0aa5849
--- /dev/null
+++ b/docs/html/images/developing/lib-migration-2.png
Binary files differ
diff --git a/docs/html/sdk/adt_download.jd b/docs/html/sdk/adt_download.jd
index f98caf5..126c052 100644
--- a/docs/html/sdk/adt_download.jd
+++ b/docs/html/sdk/adt_download.jd
@@ -22,11 +22,18 @@
<th>Notes</th>
</tr>
<tr>
- <td>0.9.7</td>
- <td><a href="http://dl-ssl.google.com/android/ADT-0.9.7.zip">ADT-0.9.7.zip</a></td>
+ <td>0.9.8</td>
+ <td><a href="http://dl-ssl.google.com/android/ADT-0.9.8.zip">ADT-0.9.8.zip</a></td>
<td><nobr>{@adtZipBytes} bytes</nobr></td>
<td>{@adtZipChecksum}</td>
- <td>Requires SDK Tools, Revision 6 <em><nobr>May 2010</nobr></em></td>
+ <td>Requires SDK Tools, Revision 7 <em><nobr>September 2010</nobr></em></td>
+ </tr>
+ <tr>
+ <td>0.9.7</td>
+ <td><a href="http://dl-ssl.google.com/android/ADT-0.9.7.zip">ADT-0.9.7.zip</a></td>
+ <td><nobr>8033750 bytes</nobr></td>
+ <td>de2431c8d4786d127ae5bfc95b4605df</td>
+ <td>Requires SDK Tools, Revision 5 <em><nobr>May 2010</nobr></em></td>
</tr>
<tr>
<td>0.9.6</td>
diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd
index bd7eeed..9d6c3ab 100644
--- a/docs/html/sdk/eclipse-adt.jd
+++ b/docs/html/sdk/eclipse-adt.jd
@@ -95,8 +95,62 @@
}
</style>
+
+
+
<div class="toggleable opened">
<a href="#" onclick="return toggleDiv(this)">
+ <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px" />
+ADT 0.9.8</a> <em>(August 2010)</em>
+ <div class="toggleme">
+
+
+</ul>
+</dd>
+
+<dl>
+
+<dt>Dependencies:</dt>
+
+<dd><p>ADT 0.9.8 is designed for use with SDK Tools r7 and later. Before
+updating to ADT 0.9.8, we highly recommend that you use the Android SDK and
+AVD Manager to install SDK Tools r7 into your SDK.</p></dd>
+
+<dt>General notes:</dt>
+<dd>
+<ul>
+<li>Adds a new Action, "Rename Application Package", to the Android Tools
+contextual menu. The Action does a full application package refactoring.
+<li>Adds support for library projects that don't have a source folder
+called <code>src/</code>. There is now support for any number of source folders,
+with no name restriction. They can even be in subfolder such as
+<code>src/java</code>. If you are already working with library projects created
+in ADT 0.9.7, see <a
+href="{@docRoot}guide/developing/eclipse-adt.html#libraryMigrating">Migrating
+library projects to ADT 0.9.8</a> for important information about moving
+to the new ADT environment.</li>
+<li>Adds support for library projects that depend on other library
+projects.</li>
+<li>Adds support for additional resource qualifiers:
+<code>car</code>/<code>desk</code>, <code>night</code>/<code>notnight</code> and
+<code>navexposed</code>/<code>navhidden</code>.</li>
+<li>Adds more device screen types in the layout editor. All screen
+resolution/density combinations listed in the <a
+href="{@docRoot}guide/practices/screens_support.html#range">Supporting
+Multiple Screens</a> are now available.</li>
+<li>Fixes problems with handling of library project names that
+contain characters that are incompatible with the Eclipse path variable.
+Now properly sets up the link between the main project and the library
+project.</li>
+</ul>
+</dd>
+</dl>
+ </div>
+</div>
+
+
+<div class="toggleable closed">
+ <a href="#" onclick="return toggleDiv(this)">
<img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px" />
ADT 0.9.7</a> <em>(May 2010)</em>
<div class="toggleme">
@@ -120,6 +174,7 @@
</div>
</div>
+
<div class="toggleable closed">
<a href="#" onclick="return toggleDiv(this)">
<img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px" />
diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd
index 5e92253..7016eee 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -1,17 +1,17 @@
page.title=Android SDK
sdk.redirect=0
-sdk.win_download=android-sdk_r06-windows.zip
-sdk.win_bytes=23293160
-sdk.win_checksum=7c7fcec3c6b5c7c3df6ae654b27effb5
+sdk.win_download=android-sdk_r07-windows.zip
+sdk.win_bytes=23669664
+sdk.win_checksum=69c40c2d2e408b623156934f9ae574f0
-sdk.mac_download=android-sdk_r06-mac_86.zip
-sdk.mac_bytes=19108077
-sdk.mac_checksum=c92abf66a82c7a3f2b8493ebe025dd22
+sdk.mac_download=android-sdk_r07-mac_x86.zip
+sdk.mac_bytes=19229546
+sdk.mac_checksum=0f330ed3ebb36786faf6dc72b8acf819
-sdk.linux_download=android-sdk_r06-linux_86.tgz
-sdk.linux_bytes=16971139
-sdk.linux_checksum=848371e4bf068dbb582b709f4e56d903
+sdk.linux_download=android-sdk_r07-linux_x86.tgz
+sdk.linux_bytes=17114517
+sdk.linux_checksum=e10c75da3d1aa147ddd4a5c58bfc3646
@jd:body
@@ -50,7 +50,7 @@
<p><strong>4. Add Android platforms and other components to your SDK</strong></p>
<p>Use the Android SDK and AVD Manager, included in the SDK starter package, to
-add one or more Android platforms (for example, Android 1.6 or Android 2.0) and
+add one or more Android platforms (for example, Android 1.6 or Android 2.2) and
other components to your SDK. If you aren't sure what to add, see <a
href="installing.html#which">Which components do I need?</a></p>
diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs
index 404e938..a665e95 100644
--- a/docs/html/sdk/sdk_toc.cs
+++ b/docs/html/sdk/sdk_toc.cs
@@ -75,8 +75,8 @@
</li>
</ul>
<ul>
- <li><a href="<?cs var:toroot ?>sdk/tools-notes.html">SDK Tools, r6</a>
- </li>
+ <li><a href="<?cs var:toroot ?>sdk/tools-notes.html">SDK Tools, r7</a>
+ <span class="new">new!</span></li>
<li><a href="<?cs var:toroot ?>sdk/win-usb.html">USB Driver for
Windows, r3</a>
</li>
@@ -94,7 +94,7 @@
<span style="display:none" class="zh-TW"></span>
</h2>
<ul>
- <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 0.9.7
+ <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 0.9.8
<span style="display:none" class="de"></span>
<span style="display:none" class="es"></span>
<span style="display:none" class="fr"></span>
@@ -102,7 +102,7 @@
<span style="display:none" class="ja"></span>
<span style="display:none" class="zh-CN"></span>
<span style="display:none" class="zh-TW"></span></a>
- </li>
+ <span class="new">new!</span></li>
</ul>
</li>
<li>
diff --git a/docs/html/sdk/tools-notes.jd b/docs/html/sdk/tools-notes.jd
index c9be6ff..dc58801 100644
--- a/docs/html/sdk/tools-notes.jd
+++ b/docs/html/sdk/tools-notes.jd
@@ -64,6 +64,39 @@
<div class="toggleable opened">
<a href="#" onclick="return toggleDiv(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px" />
+SDK Tools, Revision 7</a> <em>(September 2010)</em>
+ <div class="toggleme">
+
+<dl>
+<dt>Dependencies:</dt>
+<dd>
+<p>If you are developing in Eclipse with ADT, note that SDK Tools r7 is
+designed for use with ADT 0.9.8 and later. After installing SDK Tools r7, we
+highly recommend updating your ADT Plugin to 0.9.8.</p>
+</dd>
+
+<dt>General notes:</dt>
+<dd>
+<ul>
+<li>Added support for library projects that depend on other library projects.</li>
+<li>Adds support for aidl files in library projects.</li>
+<li>Adds support for extension targets in Ant build to perform tasks between the
+normal tasks: <code>-pre-build</code>, <code>-pre-compile</code>, and
+<code>-post-compile</code>.</li>
+<li>Adds support for "headless" SDK update. See <code>android -h update sdk</code>
+for more information.</li>
+<li>Fixes location control in DDMS to work in any locale not using '.' as a
+decimal point.</li>
+</li>
+</ul>
+</dd>
+</dl>
+ </div>
+</div>
+
+<div class="toggleable closed">
+ <a href="#" onclick="return toggleDiv(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px" />
SDK Tools, Revision 6</a> <em>(May 2010)</em>
<div class="toggleme">
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index d9ee3ec..9a19056 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -446,28 +446,30 @@
Rect srcR = new Rect(x, y, x + width, y + height);
RectF dstR = new RectF(0, 0, width, height);
+ final Config newConfig = source.getConfig() == Config.ARGB_8888 ?
+ Config.ARGB_8888 : Config.RGB_565;
+
if (m == null || m.isIdentity()) {
- bitmap = createBitmap(neww, newh,
- source.hasAlpha() ? Config.ARGB_8888 : Config.RGB_565);
+ bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
paint = null; // not needed
} else {
- /* the dst should have alpha if the src does, or if our matrix
- doesn't preserve rectness
- */
- boolean hasAlpha = source.hasAlpha() || !m.rectStaysRect();
+ final boolean transformed = !m.rectStaysRect();
+
RectF deviceR = new RectF();
m.mapRect(deviceR, dstR);
+
neww = Math.round(deviceR.width());
newh = Math.round(deviceR.height());
- bitmap = createBitmap(neww, newh, hasAlpha ? Config.ARGB_8888 : Config.RGB_565);
- if (hasAlpha) {
- bitmap.eraseColor(0);
- }
+
+ bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
+ transformed || source.hasAlpha());
+
canvas.translate(-deviceR.left, -deviceR.top);
canvas.concat(m);
+
paint = new Paint();
paint.setFilterBitmap(filter);
- if (!m.rectStaysRect()) {
+ if (transformed) {
paint.setAntiAlias(true);
}
}
@@ -492,8 +494,30 @@
* @throws IllegalArgumentException if the width or height are <= 0
*/
public static Bitmap createBitmap(int width, int height, Config config) {
+ return createBitmap(width, height, config, true);
+ }
+
+ /**
+ * Returns a mutable bitmap with the specified width and height. Its
+ * initial density is as per {@link #getDensity}.
+ *
+ * @param width The width of the bitmap
+ * @param height The height of the bitmap
+ * @param config The bitmap config to create.
+ * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the
+ * bitmap as opaque. Doing so will clear the bitmap in black
+ * instead of transparent.
+ *
+ * @throws IllegalArgumentException if the width or height are <= 0
+ */
+ private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) {
Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
- bm.eraseColor(0); // start with black/transparent pixels
+ if (config == Config.ARGB_8888 && !hasAlpha) {
+ bm.eraseColor(0xff000000);
+ nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha);
+ } else {
+ bm.eraseColor(0);
+ }
return bm;
}
@@ -1094,7 +1118,7 @@
private static native void nativePrepareToDraw(int nativeBitmap);
private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha);
private static native boolean nativeSameAs(int nb0, int nb1);
-
+
/* package */ final int ni() {
return mNativeBitmap;
}
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 5dbbd70..dc21a72 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -83,7 +83,7 @@
/**
* The pixel density to use for the bitmap. This will always result
* in the returned bitmap having a density set for it (see
- * {@link Bitmap#setDensity(int) Bitmap.setDensity(int))}. In addition,
+ * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)}). In addition,
* if {@link #inScaled} is set (which it is by default} and this
* density does not match {@link #inTargetDensity}, then the bitmap
* will be scaled to the target density before being returned.
diff --git a/include/media/EffectEnvironmentalReverbApi.h b/include/media/EffectEnvironmentalReverbApi.h
index 2233e3f..36accd8 100644
--- a/include/media/EffectEnvironmentalReverbApi.h
+++ b/include/media/EffectEnvironmentalReverbApi.h
@@ -48,16 +48,16 @@
//t_reverb_settings is equal to SLEnvironmentalReverbSettings defined in OpenSL ES specification.
typedef struct s_reverb_settings {
- int16_t roomLevel;
- int16_t roomHFLevel;
- int32_t decayTime;
- int16_t decayHFRatio;
- int16_t reflectionsLevel;
- int32_t reflectionsDelay;
- int16_t reverbLevel;
- int32_t reverbDelay;
- int16_t diffusion;
- int16_t density;
+ int16_t roomLevel;
+ int16_t roomHFLevel;
+ uint32_t decayTime;
+ int16_t decayHFRatio;
+ int16_t reflectionsLevel;
+ uint32_t reflectionsDelay;
+ int16_t reverbLevel;
+ uint32_t reverbDelay;
+ int16_t diffusion;
+ int16_t density;
} __attribute__((packed)) t_reverb_settings;
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index 1e447f1..1594e31 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -49,6 +49,7 @@
kKeyNTPTime = 'ntpT', // uint64_t (ntp-timestamp)
kKeyTargetTime = 'tarT', // int64_t (usecs)
kKeyDriftTime = 'dftT', // int64_t (usecs)
+ kKeyAnchorTime = 'ancT', // int64_t (usecs)
kKeyDuration = 'dura', // int64_t (usecs)
kKeyColorFormat = 'colf',
kKeyPlatformPrivate = 'priv', // pointer
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 3fa825f..b587e94 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -78,6 +78,11 @@
POLICY_FLAG_RAW_MASK = 0x0000ffff,
+ /* These flags are set by the input dispatcher. */
+
+ // Indicates that the input event was injected.
+ POLICY_FLAG_INJECTED = 0x01000000,
+
/* These flags are set by the input reader policy as it intercepts each event. */
// Indicates that the screen was off when the event was received and the event
@@ -225,6 +230,8 @@
inline int32_t getAction() const { return mAction; }
+ inline int32_t getFlags() const { return mFlags; }
+
inline int32_t getEdgeFlags() const { return mEdgeFlags; }
inline int32_t getMetaState() const { return mMetaState; }
@@ -343,6 +350,7 @@
int32_t deviceId,
int32_t source,
int32_t action,
+ int32_t flags,
int32_t edgeFlags,
int32_t metaState,
float xOffset,
@@ -370,6 +378,7 @@
private:
int32_t mAction;
+ int32_t mFlags;
int32_t mEdgeFlags;
int32_t mMetaState;
float mXOffset;
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index aed4fa1..f00f2db 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -84,14 +84,22 @@
* current event is delivered to this target or a timeout occurs. */
FLAG_SYNC = 0x01,
- /* This flag indicates that a MotionEvent with ACTION_DOWN falls outside of the area of
- * this target and so should instead be delivered as an ACTION_OUTSIDE to this target. */
+ /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+ * of the area of this target and so should instead be delivered as an
+ * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
FLAG_OUTSIDE = 0x02,
/* This flag indicates that a KeyEvent or MotionEvent is being canceled.
- * In the case of a key event, it should be delivered with KeyEvent.FLAG_CANCELED set.
- * In the case of a motion event, it should be delivered as MotionEvent.ACTION_CANCEL. */
- FLAG_CANCEL = 0x04
+ * In the case of a key event, it should be delivered with flag
+ * AKEY_EVENT_FLAG_CANCELED set.
+ * In the case of a motion event, it should be delivered with action
+ * AMOTION_EVENT_ACTION_CANCEL instead. */
+ FLAG_CANCEL = 0x04,
+
+ /* This flag indicates that the target of a MotionEvent is partly or wholly
+ * obscured by another visible window above it. The motion event should be
+ * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+ FLAG_WINDOW_IS_OBSCURED = 0x08,
};
// The input channel to be targeted.
@@ -139,9 +147,12 @@
/* Notifies the system that an input channel recovered from ANR. */
virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) = 0;
- /* Gets the key repeat timeout or -1 if automatic key repeating is disabled. */
+ /* Gets the key repeat initial timeout or -1 if automatic key repeating is disabled. */
virtual nsecs_t getKeyRepeatTimeout() = 0;
+ /* Gets the key repeat inter-key delay. */
+ virtual nsecs_t getKeyRepeatDelay() = 0;
+
/* Waits for key event input targets to become available.
* If the event is being injected, injectorPid and injectorUid should specify the
* process id and used id of the injecting application, otherwise they should both
@@ -193,7 +204,8 @@
uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
- uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+ uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t edgeFlags,
uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
float xPrecision, float yPrecision, nsecs_t downTime) = 0;
@@ -257,7 +269,8 @@
uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
int32_t scanCode, int32_t metaState, nsecs_t downTime);
virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
- uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+ uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t edgeFlags,
uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
float xPrecision, float yPrecision, nsecs_t downTime);
@@ -327,6 +340,7 @@
int32_t source;
uint32_t policyFlags;
int32_t action;
+ int32_t flags;
int32_t metaState;
int32_t edgeFlags;
float xPrecision;
@@ -458,7 +472,8 @@
int32_t repeatCount, nsecs_t downTime);
MotionEntry* obtainMotionEntry(nsecs_t eventTime,
int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
- int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
+ int32_t flags, int32_t metaState, int32_t edgeFlags,
+ float xPrecision, float yPrecision,
nsecs_t downTime, uint32_t pointerCount,
const int32_t* pointerIds, const PointerCoords* pointerCoords);
DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry);
diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h
index 31ec701..82831e29 100644
--- a/include/ui/InputTransport.h
+++ b/include/ui/InputTransport.h
@@ -135,6 +135,7 @@
struct {
int32_t action;
+ int32_t flags;
int32_t metaState;
int32_t edgeFlags;
nsecs_t downTime;
@@ -218,6 +219,7 @@
int32_t deviceId,
int32_t source,
int32_t action,
+ int32_t flags,
int32_t edgeFlags,
int32_t metaState,
float xOffset,
diff --git a/include/utils/Singleton.h b/include/utils/Singleton.h
index 3b975b4..e1ee8eb 100644
--- a/include/utils/Singleton.h
+++ b/include/utils/Singleton.h
@@ -37,6 +37,11 @@
}
return *instance;
}
+
+ static bool hasInstance() {
+ Mutex::Autolock _l(sLock);
+ return sInstance != 0;
+ }
protected:
~Singleton() { };
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index fda57b8..9c67885 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -17,7 +17,9 @@
#ifndef ANDROID_UI_CACHES_H
#define ANDROID_UI_CACHES_H
-#define LOG_TAG "OpenGLRenderer"
+#ifndef LOG_TAG
+ #define LOG_TAG "OpenGLRenderer"
+#endif
#include <utils/Singleton.h>
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
index c358c80..070e33f 100644
--- a/libs/hwui/GenerationCache.h
+++ b/libs/hwui/GenerationCache.h
@@ -61,6 +61,7 @@
bool contains(K key) const;
V get(K key);
+ K getKeyAt(uint32_t index) const;
void put(K key, V value);
V remove(K key);
V removeOldest();
@@ -122,6 +123,11 @@
}
template<typename K, typename V>
+K GenerationCache<K, V>::getKeyAt(uint32_t index) const {
+ return mCache.keyAt(index);
+}
+
+template<typename K, typename V>
V GenerationCache<K, V>::get(K key) {
ssize_t index = mCache.indexOfKey(key);
if (index >= 0) {
@@ -143,11 +149,7 @@
}
ssize_t index = mCache.indexOfKey(key);
- if (index >= 0) {
- sp<Entry<K, V> > entry = mCache.valueAt(index);
- detachFromCache(entry);
- addToCache(entry, key, value);
- } else {
+ if (index < 0) {
sp<Entry<K, V> > entry = new Entry<K, V>;
addToCache(entry, key, value);
}
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 58920bd..9957370 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -21,6 +21,8 @@
#include <SkCanvas.h>
#include <SkGradientShader.h>
+#include <utils/threads.h>
+
#include "GradientCache.h"
#include "Properties.h"
@@ -52,6 +54,7 @@
}
GradientCache::~GradientCache() {
+ Mutex::Autolock _l(mLock);
mCache.clear();
}
@@ -60,14 +63,17 @@
///////////////////////////////////////////////////////////////////////////////
uint32_t GradientCache::getSize() {
+ Mutex::Autolock _l(mLock);
return mSize;
}
uint32_t GradientCache::getMaxSize() {
+ Mutex::Autolock _l(mLock);
return mMaxSize;
}
void GradientCache::setMaxSize(uint32_t maxSize) {
+ Mutex::Autolock _l(mLock);
mMaxSize = maxSize;
while (mSize > mMaxSize) {
mCache.removeOldest();
@@ -79,6 +85,7 @@
///////////////////////////////////////////////////////////////////////////////
void GradientCache::operator()(SkShader*& shader, Texture*& texture) {
+ // Already locked here
if (shader) {
const uint32_t size = texture->width * texture->height * 4;
mSize -= size;
@@ -95,14 +102,17 @@
///////////////////////////////////////////////////////////////////////////////
Texture* GradientCache::get(SkShader* shader) {
+ Mutex::Autolock _l(mLock);
return mCache.get(shader);
}
void GradientCache::remove(SkShader* shader) {
+ Mutex::Autolock _l(mLock);
mCache.remove(shader);
}
void GradientCache::clear() {
+ Mutex::Autolock _l(mLock);
mCache.clear();
}
@@ -128,17 +138,21 @@
canvas.drawRectCoords(0.0f, 0.0f, bitmap.width(), 1.0f, p);
+ mLock.lock();
// Asume the cache is always big enough
const uint32_t size = bitmap.rowBytes() * bitmap.height();
while (mSize + size > mMaxSize) {
mCache.removeOldest();
}
+ mLock.unlock();
Texture* texture = new Texture;
generateTexture(&bitmap, texture);
+ mLock.lock();
mSize += size;
mCache.put(shader, texture);
+ mLock.unlock();
return texture;
}
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 8795920..51a8c01 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -82,6 +82,12 @@
uint32_t mSize;
uint32_t mMaxSize;
+
+ /**
+ * Used to access mCache and mSize. All methods are accessed from a single
+ * thread except for remove().
+ */
+ mutable Mutex mLock;
}; // class GradientCache
}; // namespace uirenderer
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 47ab355..f70bca7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -133,6 +133,7 @@
glViewport(0, 0, mWidth, mHeight);
+ glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
index e874a16..6dad831 100644
--- a/libs/hwui/PatchCache.h
+++ b/libs/hwui/PatchCache.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_UI_PATCH_CACHE_H
#define ANDROID_UI_PATCH_CACHE_H
+#include <utils/ResourceTypes.h>
+
#include "Patch.h"
#include "GenerationCache.h"
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 158c0cc..70e06a1 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -21,6 +21,8 @@
#include <SkCanvas.h>
#include <SkRect.h>
+#include <utils/threads.h>
+
#include "PathCache.h"
#include "Properties.h"
@@ -51,6 +53,7 @@
}
PathCache::~PathCache() {
+ Mutex::Autolock _l(mLock);
mCache.clear();
}
@@ -67,14 +70,17 @@
///////////////////////////////////////////////////////////////////////////////
uint32_t PathCache::getSize() {
+ Mutex::Autolock _l(mLock);
return mSize;
}
uint32_t PathCache::getMaxSize() {
+ Mutex::Autolock _l(mLock);
return mMaxSize;
}
void PathCache::setMaxSize(uint32_t maxSize) {
+ Mutex::Autolock _l(mLock);
mMaxSize = maxSize;
while (mSize > mMaxSize) {
mCache.removeOldest();
@@ -99,14 +105,29 @@
// Caching
///////////////////////////////////////////////////////////////////////////////
+void PathCache::remove(SkPath* path) {
+ Mutex::Autolock _l(mLock);
+ // TODO: Linear search...
+ for (uint32_t i = 0; i < mCache.size(); i++) {
+ if (mCache.getKeyAt(i).path == path) {
+ mCache.removeAt(i);
+ }
+ }
+}
+
PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
PathCacheEntry entry(path, paint);
+
+ mLock.lock();
PathTexture* texture = mCache.get(entry);
+ mLock.unlock();
if (!texture) {
texture = addTexture(entry, path, paint);
} else if (path->getGenerationID() != texture->generation) {
+ mLock.lock();
mCache.remove(entry);
+ mLock.unlock();
texture = addTexture(entry, path, paint);
}
@@ -132,9 +153,11 @@
const uint32_t size = width * height;
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
+ mLock.lock();
while (mSize + size > mMaxSize) {
mCache.removeOldest();
}
+ mLock.unlock();
}
PathTexture* texture = new PathTexture;
@@ -157,8 +180,10 @@
generateTexture(bitmap, texture);
if (size < mMaxSize) {
+ mLock.lock();
mSize += size;
mCache.put(entry, texture);
+ mLock.unlock();
} else {
texture->cleanup = true;
}
@@ -167,6 +192,7 @@
}
void PathCache::clear() {
+ Mutex::Autolock _l(mLock);
mCache.clear();
}
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 522e5e0..bde0e7d 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -114,6 +114,10 @@
* Clears the cache. This causes all textures to be deleted.
*/
void clear();
+ /**
+ * Removes an entry.
+ */
+ void remove(SkPath* path);
/**
* Sets the maximum size of the cache in bytes.
@@ -143,6 +147,12 @@
uint32_t mSize;
uint32_t mMaxSize;
GLuint mMaxTextureSize;
+
+ /**
+ * Used to access mCache and mSize. All methods are accessed from a single
+ * thread except for remove().
+ */
+ mutable Mutex mLock;
}; // class PathCache
}; // namespace uirenderer
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 90f548b..817f143 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -28,6 +28,7 @@
struct Texture {
Texture() {
cleanup = false;
+ bitmapSize = 0;
}
/**
@@ -54,6 +55,10 @@
* Indicates whether this texture should be cleaned up after use.
*/
bool cleanup;
+ /**
+ * Optional, size of the original bitmap.
+ */
+ uint32_t bitmapSize;
}; // struct Texture
class AutoTexture {
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 753c544..927070a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -18,6 +18,8 @@
#include <GLES2/gl2.h>
+#include <utils/threads.h>
+
#include "TextureCache.h"
#include "Properties.h"
@@ -49,6 +51,7 @@
}
TextureCache::~TextureCache() {
+ Mutex::Autolock _l(mLock);
mCache.clear();
}
@@ -64,14 +67,17 @@
///////////////////////////////////////////////////////////////////////////////
uint32_t TextureCache::getSize() {
+ Mutex::Autolock _l(mLock);
return mSize;
}
uint32_t TextureCache::getMaxSize() {
+ Mutex::Autolock _l(mLock);
return mMaxSize;
}
void TextureCache::setMaxSize(uint32_t maxSize) {
+ Mutex::Autolock _l(mLock);
mMaxSize = maxSize;
while (mSize > mMaxSize) {
mCache.removeOldest();
@@ -83,12 +89,9 @@
///////////////////////////////////////////////////////////////////////////////
void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) {
- if (bitmap) {
- const uint32_t size = bitmap->rowBytes() * bitmap->height();
- mSize -= size;
- }
-
+ // This will be called already locked
if (texture) {
+ mSize -= texture->bitmapSize;
glDeleteTextures(1, &texture->id);
delete texture;
}
@@ -99,7 +102,10 @@
///////////////////////////////////////////////////////////////////////////////
Texture* TextureCache::get(SkBitmap* bitmap) {
+ mLock.lock();
Texture* texture = mCache.get(bitmap);
+ mLock.unlock();
+
if (!texture) {
if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
LOGW("Bitmap too large to be uploaded into a texture");
@@ -109,17 +115,22 @@
const uint32_t size = bitmap->rowBytes() * bitmap->height();
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
+ mLock.lock();
while (mSize + size > mMaxSize) {
mCache.removeOldest();
}
+ mLock.unlock();
}
texture = new Texture;
+ texture->bitmapSize = size;
generateTexture(bitmap, texture, false);
if (size < mMaxSize) {
+ mLock.lock();
mSize += size;
mCache.put(bitmap, texture);
+ mLock.unlock();
} else {
texture->cleanup = true;
}
@@ -131,15 +142,18 @@
}
void TextureCache::remove(SkBitmap* bitmap) {
+ Mutex::Autolock _l(mLock);
mCache.remove(bitmap);
}
void TextureCache::clear() {
+ Mutex::Autolock _l(mLock);
mCache.clear();
}
void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate) {
SkAutoLockPixels alp(*bitmap);
+
if (!bitmap->readyToDraw()) {
LOGE("Cannot generate texture from bitmap");
return;
@@ -159,6 +173,7 @@
switch (bitmap->getConfig()) {
case SkBitmap::kA8_Config:
texture->blend = true;
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap->rowBytesAsPixels(), texture->height, 0,
GL_ALPHA, GL_UNSIGNED_BYTE, bitmap->getPixels());
break;
@@ -175,6 +190,7 @@
texture->blend = !bitmap->isOpaque();
break;
default:
+ LOGW("Unsupported bitmap config");
break;
}
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index b5e4c7c..a63789a 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -86,6 +86,12 @@
uint32_t mSize;
uint32_t mMaxSize;
GLint mMaxTextureSize;
+
+ /**
+ * Used to access mCache and mSize. All methods are accessed from a single
+ * thread except for remove().
+ */
+ mutable Mutex mLock;
}; // class TextureCache
}; // namespace uirenderer
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index 4973cd8..811edaf 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -129,6 +129,7 @@
int32_t deviceId,
int32_t source,
int32_t action,
+ int32_t flags,
int32_t edgeFlags,
int32_t metaState,
float xOffset,
@@ -142,6 +143,7 @@
const PointerCoords* pointerCoords) {
InputEvent::initialize(deviceId, source);
mAction = action;
+ mFlags = flags;
mEdgeFlags = edgeFlags;
mMetaState = metaState;
mXOffset = xOffset;
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index 886c785..df232d4 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -97,6 +97,7 @@
void InputDispatcher::dispatchOnce() {
nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
+ nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
bool skipPoll = false;
nsecs_t currentTime;
@@ -146,7 +147,7 @@
if (mInboundQueue.isEmpty()) {
if (mKeyRepeatState.lastKeyEntry) {
if (currentTime >= mKeyRepeatState.nextRepeatTime) {
- processKeyRepeatLockedInterruptible(currentTime, keyRepeatTimeout);
+ processKeyRepeatLockedInterruptible(currentTime, keyRepeatDelay);
skipPoll = true;
} else {
if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
@@ -335,7 +336,7 @@
}
void InputDispatcher::processKeyRepeatLockedInterruptible(
- nsecs_t currentTime, nsecs_t keyRepeatTimeout) {
+ nsecs_t currentTime, nsecs_t keyRepeatDelay) {
KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
// Search the inbound queue for a key up corresponding to this device.
@@ -352,7 +353,7 @@
}
}
- // Synthesize a key repeat after the repeat timeout expired.
+ // Synthesize a key repeat.
// Reuse the repeated key entry if it is otherwise unreferenced.
uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
if (entry->refCount == 1) {
@@ -375,7 +376,7 @@
entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
}
- mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatTimeout;
+ mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay;
#if DEBUG_OUTBOUND_EVENT_DETAILS
LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
@@ -392,9 +393,11 @@
void InputDispatcher::processMotionLockedInterruptible(
nsecs_t currentTime, MotionEntry* entry) {
#if DEBUG_OUTBOUND_EVENT_DETAILS
- LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
+ LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, "
"metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
- entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags,
entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision,
entry->downTime);
@@ -406,7 +409,7 @@
}
for (uint32_t i = 0; i < entry->pointerCount; i++) {
LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
- "touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
"orientation=%f",
i, entry->pointerIds[i],
sample->pointerCoords[i].x, sample->pointerCoords[i].y,
@@ -465,7 +468,7 @@
mCurrentInputTargetsValid = false;
mLock.unlock();
- mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action,
+ mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
entry->edgeFlags, entry->metaState,
0, 0, entry->xPrecision, entry->yPrecision,
entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
@@ -698,12 +701,16 @@
// Apply target flags.
int32_t action = motionEntry->action;
+ int32_t flags = motionEntry->flags;
if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) {
action = AMOTION_EVENT_ACTION_OUTSIDE;
}
if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
action = AMOTION_EVENT_ACTION_CANCEL;
}
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+ flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ }
// If headMotionSample is non-NULL, then it points to the first new sample that we
// were unable to dispatch during the previous cycle so we resume dispatching from
@@ -726,7 +733,7 @@
// Publish the motion event and the first motion sample.
status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
- motionEntry->source, action, motionEntry->edgeFlags, motionEntry->metaState,
+ motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
xOffset, yOffset,
motionEntry->xPrecision, motionEntry->yPrecision,
motionEntry->downTime, firstMotionSample->eventTime,
@@ -1073,18 +1080,18 @@
}
void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
- uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+ uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags,
uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
float xPrecision, float yPrecision, nsecs_t downTime) {
#if DEBUG_INBOUND_EVENT_DETAILS
LOGD("notifyMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
- "action=0x%x, metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, "
- "downTime=%lld",
- eventTime, deviceId, source, policyFlags, action, metaState, edgeFlags,
+ "action=0x%x, flags=0x%x, metaState=0x%x, edgeFlags=0x%x, "
+ "xPrecision=%f, yPrecision=%f, downTime=%lld",
+ eventTime, deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
xPrecision, yPrecision, downTime);
for (uint32_t i = 0; i < pointerCount; i++) {
LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
- "touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
"orientation=%f",
i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
pointerCoords[i].pressure, pointerCoords[i].size,
@@ -1209,7 +1216,7 @@
// Just enqueue a new motion event.
MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
- deviceId, source, policyFlags, action, metaState, edgeFlags,
+ deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
xPrecision, yPrecision, downTime,
pointerCount, pointerIds, pointerCoords);
@@ -1359,7 +1366,7 @@
switch (event->getType()) {
case AINPUT_EVENT_TYPE_KEY: {
const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
- uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
+ uint32_t policyFlags = POLICY_FLAG_INJECTED;
KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
keyEvent->getDeviceId(), keyEvent->getSource(), policyFlags,
@@ -1371,7 +1378,7 @@
case AINPUT_EVENT_TYPE_MOTION: {
const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
- uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
+ uint32_t policyFlags = POLICY_FLAG_INJECTED;
const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
@@ -1379,7 +1386,8 @@
MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
- motionEvent->getAction(), motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
+ motionEvent->getAction(), motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
motionEvent->getXPrecision(), motionEvent->getYPrecision(),
motionEvent->getDownTime(), uint32_t(pointerCount),
motionEvent->getPointerIds(), samplePointerCoords);
@@ -1664,7 +1672,7 @@
}
InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime,
- int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
+ int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags,
int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
nsecs_t downTime, uint32_t pointerCount,
const int32_t* pointerIds, const PointerCoords* pointerCoords) {
@@ -1676,6 +1684,7 @@
entry->source = source;
entry->policyFlags = policyFlags;
entry->action = action;
+ entry->flags = flags;
entry->metaState = metaState;
entry->edgeFlags = edgeFlags;
entry->xPrecision = xPrecision;
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 8ffb48d..d57b38c 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -1153,7 +1153,7 @@
int32_t pointerId = 0;
getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TRACKBALL, policyFlags,
- motionEventAction, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ motionEventAction, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
1, & pointerId, pointerCoords, mXPrecision, mYPrecision, downTime);
}
@@ -2324,7 +2324,7 @@
} // release lock
getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TOUCHSCREEN, policyFlags,
- motionEventAction, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
+ motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
pointerCount, pointerIds, pointerCoords,
xPrecision, yPrecision, mDownTime);
}
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index cf0f63e..4c402dc 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -318,8 +318,8 @@
nsecs_t downTime,
nsecs_t eventTime) {
#if DEBUG_TRANSPORT_ACTIONS
- LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, source=%d, "
- "action=%d, flags=%d, keyCode=%d, scanCode=%d, metaState=%d, repeatCount=%d,"
+ LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, source=0x%x, "
+ "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
"downTime=%lld, eventTime=%lld",
mChannel->getName().string(),
deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
@@ -346,6 +346,7 @@
int32_t deviceId,
int32_t source,
int32_t action,
+ int32_t flags,
int32_t edgeFlags,
int32_t metaState,
float xOffset,
@@ -358,12 +359,12 @@
const int32_t* pointerIds,
const PointerCoords* pointerCoords) {
#if DEBUG_TRANSPORT_ACTIONS
- LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, source=%d, "
- "action=%d, edgeFlags=%d, metaState=%d, xOffset=%f, yOffset=%f, "
+ LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, source=0x%x, "
+ "action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, xOffset=%f, yOffset=%f, "
"xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, "
"pointerCount=%d",
mChannel->getName().string(),
- deviceId, source, action, edgeFlags, metaState, xOffset, yOffset,
+ deviceId, source, action, flags, edgeFlags, metaState, xOffset, yOffset,
xPrecision, yPrecision, downTime, eventTime, pointerCount);
#endif
@@ -379,6 +380,7 @@
}
mSharedMessage->motion.action = action;
+ mSharedMessage->motion.flags = flags;
mSharedMessage->motion.edgeFlags = edgeFlags;
mSharedMessage->motion.metaState = metaState;
mSharedMessage->motion.xOffset = xOffset;
@@ -664,6 +666,7 @@
mSharedMessage->deviceId,
mSharedMessage->source,
mSharedMessage->motion.action,
+ mSharedMessage->motion.flags,
mSharedMessage->motion.edgeFlags,
mSharedMessage->motion.metaState,
mSharedMessage->motion.xOffset,
diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
index 3bc21fa..952b974 100644
--- a/libs/ui/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
@@ -138,6 +138,7 @@
const int32_t deviceId = 1;
const int32_t source = AINPUT_SOURCE_TOUCHSCREEN;
const int32_t action = AMOTION_EVENT_ACTION_MOVE;
+ const int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
const float xOffset = -10;
@@ -167,7 +168,7 @@
}
}
- status = mPublisher->publishMotionEvent(deviceId, source, action, edgeFlags,
+ status = mPublisher->publishMotionEvent(deviceId, source, action, flags, edgeFlags,
metaState, xOffset, yOffset, xPrecision, yPrecision,
downTime, sampleEventTimes[0], pointerCount, pointerIds, samplePointerCoords.array());
ASSERT_EQ(OK, status)
@@ -213,6 +214,7 @@
EXPECT_EQ(deviceId, motionEvent->getDeviceId());
EXPECT_EQ(source, motionEvent->getSource());
EXPECT_EQ(action, motionEvent->getAction());
+ EXPECT_EQ(flags, motionEvent->getFlags());
EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags());
EXPECT_EQ(metaState, motionEvent->getMetaState());
EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
@@ -322,12 +324,12 @@
int32_t pointerIds[pointerCount] = { 0 };
PointerCoords pointerCoords[pointerCount] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
- status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
pointerCount, pointerIds, pointerCoords);
ASSERT_EQ(OK, status)
<< "publisher publishMotionEvent should return OK";
- status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
pointerCount, pointerIds, pointerCoords);
ASSERT_EQ(INVALID_OPERATION, status)
<< "publisher publishMotionEvent should return INVALID_OPERATION because ";
@@ -342,7 +344,7 @@
int32_t pointerIds[pointerCount];
PointerCoords pointerCoords[pointerCount];
- status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
pointerCount, pointerIds, pointerCoords);
ASSERT_EQ(BAD_VALUE, status)
<< "publisher publishMotionEvent should return BAD_VALUE";
@@ -356,7 +358,7 @@
int32_t pointerIds[pointerCount];
PointerCoords pointerCoords[pointerCount];
- status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
pointerCount, pointerIds, pointerCoords);
ASSERT_EQ(BAD_VALUE, status)
<< "publisher publishMotionEvent should return BAD_VALUE";
@@ -402,7 +404,7 @@
PointerCoords pointerCoords[pointerCount];
status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_DOWN,
- 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
ASSERT_EQ(OK, status);
status = mPublisher->appendMotionSample(0, pointerCoords);
@@ -419,7 +421,7 @@
PointerCoords pointerCoords[pointerCount];
status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_MOVE,
- 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
ASSERT_EQ(OK, status);
status = mPublisher->sendDispatchSignal();
@@ -446,7 +448,7 @@
PointerCoords pointerCoords[pointerCount];
status = mPublisher->publishMotionEvent(0, 0, AMOTION_EVENT_ACTION_MOVE,
- 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
ASSERT_EQ(OK, status);
for (int count = 1;; count++) {
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 7d8ef58..8ca6237 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -1175,9 +1175,12 @@
mGenreCache = new HashMap<String, Uri>();
mGenresUri = Genres.getContentUri(volumeName);
mPlaylistsUri = Playlists.getContentUri(volumeName);
- // assuming external storage is FAT (case insensitive), except on the simulator.
- if ( Process.supportsProcesses()) {
- mCaseInsensitivePaths = true;
+
+ mCaseInsensitivePaths = !mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_caseSensitiveExternalStorage);
+ if (!Process.supportsProcesses()) {
+ // Simulator uses host file system, so it should be case sensitive.
+ mCaseInsensitivePaths = false;
}
}
}
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
index 3487b0f..536d49f 100644
--- a/media/java/android/media/MtpDatabase.java
+++ b/media/java/android/media/MtpDatabase.java
@@ -246,6 +246,8 @@
return new int[] {
// allow transfering arbitrary files
MtpConstants.FORMAT_UNDEFINED,
+ MtpConstants.FORMAT_ASSOCIATION,
+ MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
};
}
diff --git a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
index 45ef416..b3e1531 100755
--- a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
+++ b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
@@ -1133,7 +1133,7 @@
//LOGV("\tReverbSetDecayTime() just Got -> %d\n", ActiveParams.T60);
if (time <= LVREV_MAX_T60) {
- ActiveParams.T60 = time;
+ ActiveParams.T60 = (LVM_UINT16)time;
}
else {
ActiveParams.T60 = LVREV_MAX_T60;
@@ -1146,7 +1146,7 @@
pContext->SamplesToExitCount = (ActiveParams.T60 * pContext->config.inputCfg.samplingRate)/1000;
//LOGV("\tReverbSetDecayTime() just Set SamplesToExitCount-> %d\n",pContext->SamplesToExitCount);
- pContext->SavedDecayTime = time;
+ pContext->SavedDecayTime = (int16_t)time;
//LOGV("\tReverbSetDecayTime end");
return;
}
@@ -1162,7 +1162,7 @@
//
//----------------------------------------------------------------------------
-int32_t ReverbGetDecayTime(ReverbContext *pContext){
+uint32_t ReverbGetDecayTime(ReverbContext *pContext){
//LOGV("\tReverbGetDecayTime start");
LVREV_ControlParams_st ActiveParams; /* Current control Parameters */
@@ -1181,7 +1181,7 @@
}
//LOGV("\tReverbGetDecayTime end");
- return ActiveParams.T60;
+ return (uint32_t)ActiveParams.T60;
}
//----------------------------------------------------------------------------
@@ -1606,7 +1606,7 @@
// *(int16_t *)pValue);
break;
case REVERB_PARAM_DECAY_TIME:
- *(int32_t *)pValue = ReverbGetDecayTime(pContext);
+ *(uint32_t *)pValue = ReverbGetDecayTime(pContext);
//LOGV("\tReverb_getParameter() REVERB_PARAM_DECAY_TIME Value is %d",
// *(int32_t *)pValue);
@@ -1671,6 +1671,7 @@
int Reverb_setParameter (ReverbContext *pContext, void *pParam, void *pValue){
int status = 0;
int16_t level;
+ int16_t ratio;
uint32_t time;
t_reverb_settings *pProperties;
int32_t *pParamTemp = (int32_t *)pParam;
@@ -1688,6 +1689,7 @@
return -EINVAL;
}
pContext->nextPreset = preset;
+ return 0;
}
switch (param){
@@ -1724,10 +1726,10 @@
//LOGV("\tReverb_setParameter() Called ReverbSetDecayTime");
break;
case REVERB_PARAM_DECAY_HF_RATIO:
- time = *(int16_t *)pValue;
- //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_HF_RATIO value is %d", time);
+ ratio = *(int16_t *)pValue;
+ //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_HF_RATIO value is %d", ratio);
//LOGV("\tReverb_setParameter() Calling ReverbSetDecayHfRatio");
- ReverbSetDecayHfRatio(pContext, time);
+ ReverbSetDecayHfRatio(pContext, ratio);
//LOGV("\tReverb_setParameter() Called ReverbSetDecayHfRatio");
break;
case REVERB_PARAM_REVERB_LEVEL:
@@ -1738,17 +1740,17 @@
//LOGV("\tReverb_setParameter() Called ReverbSetReverbLevel");
break;
case REVERB_PARAM_DIFFUSION:
- time = *(int16_t *)pValue;
- //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_DIFFUSION value is %d", time);
+ ratio = *(int16_t *)pValue;
+ //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_DIFFUSION value is %d", ratio);
//LOGV("\tReverb_setParameter() Calling ReverbSetDiffusion");
- ReverbSetDiffusion(pContext, time);
+ ReverbSetDiffusion(pContext, ratio);
//LOGV("\tReverb_setParameter() Called ReverbSetDiffusion");
break;
case REVERB_PARAM_DENSITY:
- time = *(int16_t *)pValue;
- //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_DENSITY value is %d", time);
+ ratio = *(int16_t *)pValue;
+ //LOGV("\tReverb_setParameter() REVERB_PARAM_DECAY_DENSITY value is %d", ratio);
//LOGV("\tReverb_setParameter() Calling ReverbSetDensity");
- ReverbSetDensity(pContext, time);
+ ReverbSetDensity(pContext, ratio);
//LOGV("\tReverb_setParameter() Called ReverbSetDensity");
break;
break;
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index c2f79e8..8d7ada3 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -316,8 +316,10 @@
}
if (numFramesRecorded == 0) {
- buffer->meta_data()->setInt64(kKeyTime, mStartTimeUs);
+ buffer->meta_data()->setInt64(kKeyAnchorTime, mStartTimeUs);
}
+
+ buffer->meta_data()->setInt64(kKeyTime, mStartTimeUs + mPrevSampleTimeUs);
buffer->meta_data()->setInt64(kKeyDriftTime, readTimeUs - mInitialReadTimeUs);
CHECK(timestampUs > mPrevSampleTimeUs);
mPrevSampleTimeUs = timestampUs;
diff --git a/media/libstagefright/codecs/aacenc/AACEncoder.cpp b/media/libstagefright/codecs/aacenc/AACEncoder.cpp
index e391c72..df9f107 100644
--- a/media/libstagefright/codecs/aacenc/AACEncoder.cpp
+++ b/media/libstagefright/codecs/aacenc/AACEncoder.cpp
@@ -243,7 +243,7 @@
if (mInputBuffer->meta_data()->findInt64(kKeyDriftTime, &timeUs)) {
wallClockTimeUs = timeUs;
}
- if (mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
+ if (mInputBuffer->meta_data()->findInt64(kKeyAnchorTime, &timeUs)) {
mAnchorTimeUs = timeUs;
}
readFromSource = true;
diff --git a/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp b/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
index 858e6d0..94a79ab 100644
--- a/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
+++ b/media/libstagefright/codecs/amrnb/enc/AMRNBEncoder.cpp
@@ -174,7 +174,7 @@
if (mInputBuffer->meta_data()->findInt64(kKeyDriftTime, &timeUs)) {
wallClockTimeUs = timeUs;
}
- if (mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
+ if (mInputBuffer->meta_data()->findInt64(kKeyAnchorTime, &timeUs)) {
mAnchorTimeUs = timeUs;
}
} else {
diff --git a/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp b/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
index cd28413..002f055 100644
--- a/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
+++ b/media/libstagefright/codecs/amrwbenc/AMRWBEncoder.cpp
@@ -224,7 +224,7 @@
if (mInputBuffer->meta_data()->findInt64(kKeyDriftTime, &timeUs)) {
wallClockTimeUs = timeUs;
}
- if (mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
+ if (mInputBuffer->meta_data()->findInt64(kKeyAnchorTime, &timeUs)) {
mAnchorTimeUs = timeUs;
}
readFromSource = true;
diff --git a/native/android/input.cpp b/native/android/input.cpp
index c79f913..57f0072 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -84,6 +84,10 @@
return static_cast<const MotionEvent*>(motion_event)->getAction();
}
+int32_t AMotionEvent_getFlags(const AInputEvent* motion_event) {
+ return static_cast<const MotionEvent*>(motion_event)->getFlags();
+}
+
int32_t AMotionEvent_getMetaState(const AInputEvent* motion_event) {
return static_cast<const MotionEvent*>(motion_event)->getMetaState();
}
diff --git a/native/include/android/input.h b/native/include/android/input.h
index 5b62da4..9da122b 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -247,6 +247,22 @@
};
/*
+ * Motion event flags.
+ */
+enum {
+ /* This flag indicates that the window that received this motion event is partly
+ * or wholly obscured by another visible window above it. This flag is set to true
+ * even if the event did not directly pass through the obscured area.
+ * A security sensitive application can check this flag to identify situations in which
+ * a malicious application may have covered up part of its content for the purpose
+ * of misleading the user or hijacking touches. An appropriate response might be
+ * to drop the suspect touches or to take additional precautions to confirm the user's
+ * actual intent.
+ */
+ AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1,
+};
+
+/*
* Motion event edge touch flags.
*/
enum {
@@ -395,6 +411,9 @@
/* Get the combined motion event action code and pointer index. */
int32_t AMotionEvent_getAction(const AInputEvent* motion_event);
+/* Get the motion event flags. */
+int32_t AMotionEvent_getFlags(const AInputEvent* motion_event);
+
/* Get the state of any meta / modifier keys that were in effect when the
* event was generated. */
int32_t AMotionEvent_getMetaState(const AInputEvent* motion_event);
diff --git a/opengl/tests/angeles/app-linux.cpp b/opengl/tests/angeles/app-linux.cpp
index 06fa0c2..9f80ed4 100644
--- a/opengl/tests/angeles/app-linux.cpp
+++ b/opengl/tests/angeles/app-linux.cpp
@@ -190,24 +190,33 @@
}
appInit();
+
+ struct timeval timeTemp;
+ int frameCount = 0;
+ gettimeofday(&timeTemp, NULL);
+ double totalTime = timeTemp.tv_usec/1000000.0 + timeTemp.tv_sec;
while (gAppAlive)
{
struct timeval timeNow;
- if (gAppAlive)
- {
- gettimeofday(&timeNow, NULL);
- appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
- sWindowWidth, sWindowHeight);
- checkGLErrors();
- eglSwapBuffers(sEglDisplay, sEglSurface);
- checkEGLErrors();
- }
+ gettimeofday(&timeNow, NULL);
+ appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
+ sWindowWidth, sWindowHeight);
+ checkGLErrors();
+ eglSwapBuffers(sEglDisplay, sEglSurface);
+ checkEGLErrors();
+ frameCount++;
}
+ gettimeofday(&timeTemp, NULL);
+
appDeinit();
deinitGraphics();
+ totalTime = (timeTemp.tv_usec/1000000.0 + timeTemp.tv_sec) - totalTime;
+ printf("totalTime=%f s, frameCount=%d, %.2f fps\n",
+ totalTime, frameCount, frameCount/totalTime);
+
return EXIT_SUCCESS;
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c81da70..f1f31cf 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -29,7 +29,7 @@
android:excludeFromRecents="true">
</activity>
- <activity android:name=".statusbar.RecentApplicationsActivity"
+ <activity android:name=".recent.RecentApplicationsActivity"
android:theme="@android:style/Theme.NoTitleBar"
android:excludeFromRecents="true"
android:exported="true">
diff --git a/core/res/res/layout/recent_apps_activity.xml b/packages/SystemUI/res/layout/recent_apps_activity.xml
similarity index 87%
rename from core/res/res/layout/recent_apps_activity.xml
rename to packages/SystemUI/res/layout/recent_apps_activity.xml
index 45a200e..ec661e8 100644
--- a/core/res/res/layout/recent_apps_activity.xml
+++ b/packages/SystemUI/res/layout/recent_apps_activity.xml
@@ -32,7 +32,7 @@
android:textColor="#80FFFFFF"
android:textStyle="bold"
android:singleLine="true"
- android:text="@android:string/recent_tasks_title"
+ android:text="@string/recent_tasks_title"
android:visibility="gone"/>
<!-- This is only intended to be visible when carousel is invisible -->
@@ -42,14 +42,14 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:text="@android:string/no_recent_tasks"
+ android:text="@string/recent_tasks_empty"
android:visibility="gone"/>
- <com.android.systemui.statusbar.RecentApplicationsCarouselView
+ <com.android.systemui.recent.RecentApplicationsCarouselView
android:id="@+id/carousel"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1">
- </com.android.systemui.statusbar.RecentApplicationsCarouselView>
+ </com.android.systemui.recent.RecentApplicationsCarouselView>
</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2df3b6d..037dc4a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -54,4 +54,17 @@
<!-- Name of the button that links to the Settings app. [MAXCHARS=NONE] -->
<string name="system_panel_settings_button">Settings</string>
+
+ <!-- Recent Tasks dialog: title
+ TODO: this should move to SystemUI.apk, but the code for the old
+ recent dialog is still in the framework
+ -->
+ <string name="recent_tasks_title">Recent</string>
+ <!-- Recent Tasks dialog: message when there are no recent applications
+ TODO: this should move to SystemUI.apk, but the code for the old
+ recent dialog is still in the framework
+ -->
+ <string name="recent_tasks_empty">No recent applications.</string>
+
+
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsActivity.java
rename to packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
index 6e5d241..6838dda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
@@ -15,11 +15,12 @@
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.recent;
+
+import com.android.systemui.R;
import com.android.ex.carousel.CarouselView;
import com.android.ex.carousel.CarouselRS.CarouselCallback;
-import com.android.internal.R;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsCarouselView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsCarouselView.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsCarouselView.java
rename to packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsCarouselView.java
index f5fcb11..1c8ec95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsCarouselView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsCarouselView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.recent;
import android.content.Context;
import android.util.AttributeSet;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
index fe29dea..b01c5e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
@@ -84,7 +84,7 @@
switch (action) {
case MotionEvent.ACTION_DOWN:
- Slog.d("KeyButtonView", "press");
+ //Slog.d("KeyButtonView", "press");
mDownTime = SystemClock.uptimeMillis();
mRepeat = 0;
mSending = true;
@@ -132,7 +132,7 @@
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, mRepeat,
0, 0, 0, flags, InputDevice.SOURCE_KEYBOARD);
try {
- Slog.d(StatusBarService.TAG, "injecting event " + ev);
+ //Slog.d(StatusBarService.TAG, "injecting event " + ev);
mWindowManager.injectInputEventNoWait(ev);
} catch (RemoteException ex) {
// System process is dead
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
index e828f68..c5688e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
@@ -36,6 +36,7 @@
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
+import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiManager;
@@ -91,6 +92,8 @@
private static final int AM_PM_STYLE = AM_PM_STYLE_GONE;
+ private static final int INET_CONDITION_THRESHOLD = 50;
+
private final Context mContext;
private final StatusBarManager mService;
private final Handler mHandler = new StatusBarHandler();
@@ -232,42 +235,62 @@
};
//***** Data connection icons
- private int[] mDataIconList = sDataNetType_g;
+ private int[] mDataIconList = sDataNetType_g[0];
//GSM/UMTS
- private static final int[] sDataNetType_g = new int[] {
- R.drawable.stat_sys_data_connected_g,
- R.drawable.stat_sys_data_in_g,
- R.drawable.stat_sys_data_out_g,
- R.drawable.stat_sys_data_inandout_g,
+ private static final int[][] sDataNetType_g = {
+ { R.drawable.stat_sys_data_connected_g,
+ R.drawable.stat_sys_data_in_g,
+ R.drawable.stat_sys_data_out_g,
+ R.drawable.stat_sys_data_inandout_g },
+ { R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0 }
};
- private static final int[] sDataNetType_3g = new int[] {
- R.drawable.stat_sys_data_connected_3g,
- R.drawable.stat_sys_data_in_3g,
- R.drawable.stat_sys_data_out_3g,
- R.drawable.stat_sys_data_inandout_3g,
+ private static final int[][] sDataNetType_3g = {
+ { R.drawable.stat_sys_data_connected_3g,
+ R.drawable.stat_sys_data_in_3g,
+ R.drawable.stat_sys_data_out_3g,
+ R.drawable.stat_sys_data_inandout_3g },
+ { R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0 }
};
- private static final int[] sDataNetType_e = new int[] {
- R.drawable.stat_sys_data_connected_e,
- R.drawable.stat_sys_data_in_e,
- R.drawable.stat_sys_data_out_e,
- R.drawable.stat_sys_data_inandout_e,
+ private static final int[][] sDataNetType_e = {
+ { R.drawable.stat_sys_data_connected_e,
+ R.drawable.stat_sys_data_in_e,
+ R.drawable.stat_sys_data_out_e,
+ R.drawable.stat_sys_data_inandout_e },
+ { R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0 }
};
//3.5G
- private static final int[] sDataNetType_h = new int[] {
- R.drawable.stat_sys_data_connected_h,
- R.drawable.stat_sys_data_in_h,
- R.drawable.stat_sys_data_out_h,
- R.drawable.stat_sys_data_inandout_h,
+ private static final int[][] sDataNetType_h = {
+ { R.drawable.stat_sys_data_connected_h,
+ R.drawable.stat_sys_data_in_h,
+ R.drawable.stat_sys_data_out_h,
+ R.drawable.stat_sys_data_inandout_h },
+ { R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0 }
};
//CDMA
// Use 3G icons for EVDO data and 1x icons for 1XRTT data
- private static final int[] sDataNetType_1x = new int[] {
- R.drawable.stat_sys_data_connected_1x,
- R.drawable.stat_sys_data_in_1x,
- R.drawable.stat_sys_data_out_1x,
- R.drawable.stat_sys_data_inandout_1x,
- };
+ private static final int[][] sDataNetType_1x = {
+ { R.drawable.stat_sys_data_connected_1x,
+ R.drawable.stat_sys_data_in_1x,
+ R.drawable.stat_sys_data_out_1x,
+ R.drawable.stat_sys_data_inandout_1x },
+ { R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0,
+ R.drawable.stat_sys_roaming_cdma_0 }
+ };
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -292,17 +315,22 @@
private boolean mBluetoothEnabled;
// wifi
- private static final int[] sWifiSignalImages = new int[] {
- R.drawable.stat_sys_wifi_signal_1,
- R.drawable.stat_sys_wifi_signal_2,
- R.drawable.stat_sys_wifi_signal_3,
- R.drawable.stat_sys_wifi_signal_4,
+ private static final int[][] sWifiSignalImages = {
+ { R.drawable.stat_sys_wifi_signal_1,
+ R.drawable.stat_sys_wifi_signal_2,
+ R.drawable.stat_sys_wifi_signal_3,
+ R.drawable.stat_sys_wifi_signal_4 },
+ { R.drawable.stat_sys_data_in_e,
+ R.drawable.stat_sys_data_in_e,
+ R.drawable.stat_sys_data_in_e,
+ R.drawable.stat_sys_data_in_e }
};
private static final int sWifiTemporarilyNotConnectedImage =
R.drawable.stat_sys_wifi_signal_0;
private int mLastWifiSignalLevel = -1;
private boolean mIsWifiConnected = false;
+ private int mLastWifiInetConnectivityState = 0;
// sync state
// If sync is active the SyncActive icon is displayed. If sync is not active but
@@ -353,6 +381,10 @@
else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) {
updateTTY(intent);
}
+ else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ // TODO - stop using other means to get wifi/mobile info
+ updateConnectivity(intent);
+ }
}
};
@@ -389,7 +421,7 @@
mService.setIconVisibility("data_connection", false);
// wifi
- mService.setIcon("wifi", sWifiSignalImages[0], 0);
+ mService.setIcon("wifi", sWifiSignalImages[0][0], 0);
mService.setIconVisibility("wifi", false);
// wifi will get updated by the sticky intents
@@ -456,6 +488,7 @@
filter.addAction(LocationManager.GPS_FIX_CHANGE_ACTION);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
// load config to determine if to distinguish Hspa data icon
@@ -659,6 +692,50 @@
}
}
+ private void updateConnectivity(Intent intent) {
+ NetworkInfo info = (NetworkInfo)(intent.getParcelableExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO));
+ int connectionStatus = intent.getIntExtra(ConnectivityManager.EXTRA_INET_CONDITION, 0);
+ Slog.d(TAG, "got CONNECTIVITY_ACTION - info=" + info + ", status = " + connectionStatus);
+ if (info.isConnected() == false) return;
+
+ switch (info.getType()) {
+ case ConnectivityManager.TYPE_MOBILE:
+ if (info.isConnected()) {
+ updateDataNetType(info.getSubtype(), connectionStatus);
+ updateDataIcon();
+ }
+ break;
+ case ConnectivityManager.TYPE_WIFI:
+ if (info.isConnected()) {
+ mIsWifiConnected = true;
+ mLastWifiInetConnectivityState =
+ (connectionStatus > INET_CONDITION_THRESHOLD ? 1 : 0);
+ int iconId;
+ if (mLastWifiSignalLevel == -1) {
+ iconId = sWifiSignalImages[mLastWifiInetConnectivityState][0];
+ } else {
+ iconId = sWifiSignalImages[mLastWifiInetConnectivityState]
+ [mLastWifiSignalLevel];
+ }
+
+ mService.setIcon("wifi", iconId, 0);
+ // Show the icon since wi-fi is connected
+ mService.setIconVisibility("wifi", true);
+ } else {
+ mLastWifiSignalLevel = -1;
+ mIsWifiConnected = false;
+ mLastWifiInetConnectivityState = 0;
+ int iconId = sWifiSignalImages[0][0];
+
+ mService.setIcon("wifi", iconId, 0);
+ // Hide the icon since we're not connected
+ mService.setIconVisibility("wifi", false);
+ }
+ break;
+ }
+ }
+
private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
@@ -686,7 +763,7 @@
@Override
public void onDataConnectionStateChanged(int state, int networkType) {
mDataState = state;
- updateDataNetType(networkType);
+ updateDataNetType(networkType, 0);
updateDataIcon();
}
@@ -848,37 +925,38 @@
return (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr;
}
- private final void updateDataNetType(int net) {
+ private final void updateDataNetType(int net, int inetCondition) {
+ int connected = (inetCondition > INET_CONDITION_THRESHOLD ? 1 : 0);
switch (net) {
case TelephonyManager.NETWORK_TYPE_EDGE:
- mDataIconList = sDataNetType_e;
+ mDataIconList = sDataNetType_e[connected];
break;
case TelephonyManager.NETWORK_TYPE_UMTS:
- mDataIconList = sDataNetType_3g;
+ mDataIconList = sDataNetType_3g[connected];
break;
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
if (mHspaDataDistinguishable) {
- mDataIconList = sDataNetType_h;
+ mDataIconList = sDataNetType_h[connected];
} else {
- mDataIconList = sDataNetType_3g;
+ mDataIconList = sDataNetType_3g[connected];
}
break;
case TelephonyManager.NETWORK_TYPE_CDMA:
// display 1xRTT for IS95A/B
- mDataIconList = this.sDataNetType_1x;
+ mDataIconList = sDataNetType_1x[connected];
break;
case TelephonyManager.NETWORK_TYPE_1xRTT:
- mDataIconList = this.sDataNetType_1x;
+ mDataIconList = sDataNetType_1x[connected];
break;
case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
- mDataIconList = sDataNetType_3g;
+ mDataIconList = sDataNetType_3g[connected];
break;
default:
- mDataIconList = sDataNetType_g;
+ mDataIconList = sDataNetType_g[connected];
break;
}
}
@@ -1019,34 +1097,6 @@
if (!enabled) {
mService.setIconVisibility("wifi", false);
}
- } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
-
- final NetworkInfo networkInfo = (NetworkInfo)
- intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
-
- int iconId;
- if (networkInfo != null && networkInfo.isConnected()) {
- mIsWifiConnected = true;
- if (mLastWifiSignalLevel == -1) {
- iconId = sWifiSignalImages[0];
- } else {
- iconId = sWifiSignalImages[mLastWifiSignalLevel];
- }
-
- mService.setIcon("wifi", iconId, 0);
- // Show the icon since wi-fi is connected
- mService.setIconVisibility("wifi", true);
-
- } else {
- mLastWifiSignalLevel = -1;
- mIsWifiConnected = false;
- iconId = sWifiSignalImages[0];
-
- mService.setIcon("wifi", iconId, 0);
- // Hide the icon since we're not connected
- mService.setIconVisibility("wifi", false);
- }
-
} else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
int iconId;
final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
@@ -1055,7 +1105,7 @@
if (newSignalLevel != mLastWifiSignalLevel) {
mLastWifiSignalLevel = newSignalLevel;
if (mIsWifiConnected) {
- iconId = sWifiSignalImages[newSignalLevel];
+ iconId = sWifiSignalImages[mLastWifiInetConnectivityState][newSignalLevel];
} else {
iconId = sWifiTemporarilyNotConnectedImage;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
index 814d375..6f74924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
@@ -47,6 +47,7 @@
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.statusbar.*;
+import com.android.systemui.recent.RecentApplicationsActivity;
import com.android.systemui.R;
public class TabletStatusBarService extends StatusBarService {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 3db5dc1..e454c08 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -668,7 +668,7 @@
while (true) {
String packageName = in.readUTF();
Slog.i(TAG, " + " + packageName);
- dataChanged(packageName);
+ dataChangedImpl(packageName);
}
} catch (EOFException e) {
// no more data; we're done
@@ -740,7 +740,7 @@
int uid = mBackupParticipants.keyAt(i);
HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
for (ApplicationInfo app: participants) {
- dataChanged(app.packageName);
+ dataChangedImpl(app.packageName);
}
}
}
@@ -896,7 +896,7 @@
if (!mEverStoredApps.contains(pkg.packageName)) {
if (DEBUG) Slog.i(TAG, "New app " + pkg.packageName
+ " never backed up; scheduling");
- dataChanged(pkg.packageName);
+ dataChangedImpl(pkg.packageName);
}
}
}
@@ -1327,7 +1327,7 @@
if (status != BackupConstants.TRANSPORT_OK) {
Slog.w(TAG, "Backup pass unsuccessful, restaging");
for (BackupRequest req : mQueue) {
- dataChanged(req.appInfo.packageName);
+ dataChangedImpl(req.appInfo.packageName);
}
// We also want to reset the backup schedule based on whatever
@@ -1997,25 +1997,66 @@
}
}
+ private void dataChangedImpl(String packageName) {
+ HashSet<ApplicationInfo> targets = dataChangedTargets(packageName);
+ dataChangedImpl(packageName, targets);
+ }
- // ----- IBackupManager binder interface -----
-
- public void dataChanged(String packageName) {
+ private void dataChangedImpl(String packageName, HashSet<ApplicationInfo> targets) {
// Record that we need a backup pass for the caller. Since multiple callers
// may share a uid, we need to note all candidates within that uid and schedule
// a backup pass for each of them.
EventLog.writeEvent(EventLogTags.BACKUP_DATA_CHANGED, packageName);
+ if (targets == null) {
+ Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
+ + " uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (mQueueLock) {
+ // Note that this client has made data changes that need to be backed up
+ for (ApplicationInfo app : targets) {
+ // validate the caller-supplied package name against the known set of
+ // packages associated with this uid
+ if (app.packageName.equals(packageName)) {
+ // Add the caller to the set of pending backups. If there is
+ // one already there, then overwrite it, but no harm done.
+ BackupRequest req = new BackupRequest(app, false);
+ if (mPendingBackups.put(app, req) == null) {
+ // Journal this request in case of crash. The put()
+ // operation returned null when this package was not already
+ // in the set; we want to avoid touching the disk redundantly.
+ writeToJournalLocked(packageName);
+
+ if (DEBUG) {
+ int numKeys = mPendingBackups.size();
+ Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
+ for (BackupRequest b : mPendingBackups.values()) {
+ Slog.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Note: packageName is currently unused, but may be in the future
+ private HashSet<ApplicationInfo> dataChangedTargets(String packageName) {
// If the caller does not hold the BACKUP permission, it can only request a
// backup of its own data.
- HashSet<ApplicationInfo> targets;
if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
- targets = mBackupParticipants.get(Binder.getCallingUid());
- } else {
- // a caller with full permission can ask to back up any participating app
- // !!! TODO: allow backup of ANY app?
- targets = new HashSet<ApplicationInfo>();
+ synchronized (mBackupParticipants) {
+ return mBackupParticipants.get(Binder.getCallingUid());
+ }
+ }
+
+ // a caller with full permission can ask to back up any participating app
+ // !!! TODO: allow backup of ANY app?
+ HashSet<ApplicationInfo> targets = new HashSet<ApplicationInfo>();
+ synchronized (mBackupParticipants) {
int N = mBackupParticipants.size();
for (int i = 0; i < N; i++) {
HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i);
@@ -2024,37 +2065,7 @@
}
}
}
- if (targets != null) {
- synchronized (mQueueLock) {
- // Note that this client has made data changes that need to be backed up
- for (ApplicationInfo app : targets) {
- // validate the caller-supplied package name against the known set of
- // packages associated with this uid
- if (app.packageName.equals(packageName)) {
- // Add the caller to the set of pending backups. If there is
- // one already there, then overwrite it, but no harm done.
- BackupRequest req = new BackupRequest(app, false);
- if (mPendingBackups.put(app, req) == null) {
- // Journal this request in case of crash. The put()
- // operation returned null when this package was not already
- // in the set; we want to avoid touching the disk redundantly.
- writeToJournalLocked(packageName);
-
- if (DEBUG) {
- int numKeys = mPendingBackups.size();
- Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
- for (BackupRequest b : mPendingBackups.values()) {
- Slog.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName);
- }
- }
- }
- }
- }
- }
- } else {
- Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
- + " uid=" + Binder.getCallingUid());
- }
+ return targets;
}
private void writeToJournalLocked(String str) {
@@ -2072,6 +2083,23 @@
}
}
+ // ----- IBackupManager binder interface -----
+
+ public void dataChanged(final String packageName) {
+ final HashSet<ApplicationInfo> targets = dataChangedTargets(packageName);
+ if (targets == null) {
+ Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
+ + " uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ mBackupHandler.post(new Runnable() {
+ public void run() {
+ dataChangedImpl(packageName, targets);
+ }
+ });
+ }
+
// Clear the given package's backup data from the current transport
public void clearBackupData(String packageName) {
if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index b6d725f..579051c 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -102,6 +102,11 @@
private Context mContext;
private int mNetworkPreference;
private int mActiveDefaultNetwork = -1;
+ // 0 is full bad, 100 is full good
+ private int mDefaultInetCondition = 0;
+ private int mDefaultInetConditionPublished = 0;
+ private boolean mInetConditionChangeInFlight = false;
+ private int mDefaultConnectionSequence = 0;
private int mNumDnsEntries;
@@ -1077,6 +1082,7 @@
intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
info.getExtraInfo());
}
+ intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
sendStickyBroadcast(intent);
}
@@ -1203,6 +1209,14 @@
}
}
mActiveDefaultNetwork = type;
+ // this will cause us to come up initially as unconnected and switching
+ // to connected after our normal pause unless somebody reports us as reall
+ // disconnected
+ mDefaultInetConditionPublished = 0;
+ mDefaultConnectionSequence++;
+ mInetConditionChangeInFlight = false;
+ // Don't do this - if we never sign in stay, grey
+ //reportNetworkCondition(mActiveDefaultNetwork, 100);
}
thisNet.setTeardownRequested(false);
updateNetworkSettings(thisNet);
@@ -1623,6 +1637,7 @@
FeatureUser u = (FeatureUser)msg.obj;
u.expire();
break;
+<<<<<<< HEAD:services/java/com/android/server/ConnectivityService.java
case NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK:
String causedBy = null;
synchronized (ConnectivityService.this) {
@@ -1636,6 +1651,71 @@
Slog.d(TAG, "NetTransition Wakelock for " +
causedBy + " released by timeout");
}
+=======
+ case NetworkStateTracker.EVENT_INET_CONDITION_CHANGE:
+ if (DBG) {
+ Slog.d(TAG, "Inet connectivity change, net=" +
+ msg.arg1 + ", condition=" + msg.arg2 +
+ ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
+ }
+ if (mActiveDefaultNetwork == -1) {
+ if (DBG) Slog.d(TAG, "no active default network - aborting");
+ break;
+ }
+ if (mActiveDefaultNetwork != msg.arg1) {
+ if (DBG) Slog.d(TAG, "given net not default - aborting");
+ break;
+ }
+ mDefaultInetCondition = msg.arg2;
+ int delay;
+ if (mInetConditionChangeInFlight == false) {
+ if (DBG) Slog.d(TAG, "starting a change hold");
+ // setup a new hold to debounce this
+ if (mDefaultInetCondition > 50) {
+ delay = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
+ } else {
+ delay = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
+ }
+ mInetConditionChangeInFlight = true;
+ sendMessageDelayed(obtainMessage(
+ NetworkStateTracker.EVENT_INET_CONDITION_HOLD_END,
+ mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
+ } else {
+ // we've set the new condition, when this hold ends that will get
+ // picked up
+ if (DBG) Slog.d(TAG, "currently in hold - not setting new end evt");
+ }
+ break;
+ case NetworkStateTracker.EVENT_INET_CONDITION_HOLD_END:
+ if (DBG) {
+ Slog.d(TAG, "Inet hold end, net=" + msg.arg1 +
+ ", condition =" + mDefaultInetCondition +
+ ", published condition =" + mDefaultInetConditionPublished);
+ }
+ mInetConditionChangeInFlight = false;
+
+ if (mActiveDefaultNetwork == -1) {
+ if (DBG) Slog.d(TAG, "no active default network - aborting");
+ break;
+ }
+ if (mDefaultConnectionSequence != msg.arg2) {
+ if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting");
+ break;
+ }
+ if (mDefaultInetConditionPublished == mDefaultInetCondition) {
+ if (DBG) Slog.d(TAG, "no change in condition - aborting");
+ break;
+ }
+ NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+ if (networkInfo.isConnected() == false) {
+ if (DBG) Slog.d(TAG, "default network not connected - aborting");
+ break;
+ }
+ mDefaultInetConditionPublished = mDefaultInetCondition;
+ sendConnectedBroadcast(networkInfo);
+>>>>>>> ec52c98d:services/java/com/android/server/ConnectivityService.java
break;
}
}
@@ -1748,4 +1828,15 @@
mNetTransitionWakeLockTimeout);
return;
}
+
+ // 100 percent is full good, 0 is full bad.
+ public void reportInetCondition(int networkType, int percentage) {
+ if (DBG) Slog.d(TAG, "reportNetworkCondition(" + networkType + ", " + percentage + ")");
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.STATUS_BAR,
+ "ConnectivityService");
+
+ mHandler.sendMessage(mHandler.obtainMessage(
+ NetworkStateTracker.EVENT_INET_CONDITION_CHANGE, networkType, percentage));
+ }
}
diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java
index 4a0df59..0b1a4a3 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/DeviceStorageMonitorService.java
@@ -69,10 +69,12 @@
private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
+ private static final int DEFAULT_FULL_THRESHOLD_BYTES = 1024*1024; // 1MB
private long mFreeMem; // on /data
private long mLastReportedFreeMem;
private long mLastReportedFreeMemTime;
private boolean mLowMemFlag=false;
+ private boolean mMemFullFlag=false;
private Context mContext;
private ContentResolver mContentResolver;
private long mTotalMemory; // on /data
@@ -87,9 +89,13 @@
private boolean mClearingCache;
private Intent mStorageLowIntent;
private Intent mStorageOkIntent;
+ private Intent mStorageFullIntent;
+ private Intent mStorageNotFullIntent;
private CachePackageDataObserver mClearCacheObserver;
private static final int _TRUE = 1;
private static final int _FALSE = 0;
+ private long mMemLowThreshold;
+ private int mMemFullThreshold;
/**
* This string is used for ServiceManager access to this class.
@@ -103,7 +109,7 @@
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
- //dont handle an invalid message
+ //don't handle an invalid message
if (msg.what != DEVICE_MEMORY_WHAT) {
Slog.e(TAG, "Will not process invalid message");
return;
@@ -184,7 +190,7 @@
try {
if (localLOGV) Slog.i(TAG, "Clearing cache");
IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
- freeStorageAndNotify(getMemThreshold(), mClearCacheObserver);
+ freeStorageAndNotify(mMemLowThreshold, mClearCacheObserver);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
mClearingCache = false;
@@ -209,8 +215,7 @@
if (localLOGV) Slog.v(TAG, "freeMemory="+mFreeMem);
//post intent to NotificationManager to display icon if necessary
- long memThreshold = getMemThreshold();
- if (mFreeMem < memThreshold) {
+ if (mFreeMem < mMemLowThreshold) {
if (!mLowMemFlag) {
if (checkCache) {
// See if clearing cache helps
@@ -235,6 +240,17 @@
mLowMemFlag = false;
}
}
+ if (mFreeMem < mMemFullThreshold) {
+ if (!mMemFullFlag) {
+ sendFullNotification();
+ mMemFullFlag = true;
+ }
+ } else {
+ if (mMemFullFlag) {
+ cancelFullNotification();
+ mMemFullFlag = false;
+ }
+ }
}
if(localLOGV) Slog.i(TAG, "Posting Message again");
//keep posting messages to itself periodically
@@ -264,6 +280,20 @@
return mTotalMemory*value;
}
+ /*
+ * just query settings to retrieve the memory full threshold.
+ * Preferred this over using a ContentObserver since Settings.Secure caches the value
+ * any way
+ */
+ private int getMemFullThreshold() {
+ int value = Settings.Secure.getInt(
+ mContentResolver,
+ Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+ DEFAULT_FULL_THRESHOLD_BYTES);
+ if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value);
+ return value;
+ }
+
/**
* Constructor to run service. initializes the disk space threshold value
* and posts an empty message to kickstart the process.
@@ -283,6 +313,13 @@
mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
+ mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
+ mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ // cache storage thresholds
+ mMemLowThreshold = getMemThreshold();
+ mMemFullThreshold = getMemFullThreshold();
checkMemory(true);
}
@@ -332,6 +369,23 @@
mContext.sendBroadcast(mStorageOkIntent);
}
+ /**
+ * Send a notification when storage is full.
+ */
+ private final void sendFullNotification() {
+ if(localLOGV) Slog.i(TAG, "Sending memory full notification");
+ mContext.sendStickyBroadcast(mStorageFullIntent);
+ }
+
+ /**
+ * Cancels memory full notification and sends "not full" intent.
+ */
+ private final void cancelFullNotification() {
+ if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
+ mContext.removeStickyBroadcast(mStorageFullIntent);
+ mContext.sendBroadcast(mStorageNotFullIntent);
+ }
+
public void updateMemory() {
int callingUid = getCallingUid();
if(callingUid != Process.SYSTEM_UID) {
diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java
index 8da0cf1..dbc59ef 100644
--- a/services/java/com/android/server/InputWindow.java
+++ b/services/java/com/android/server/InputWindow.java
@@ -34,9 +34,17 @@
// Dispatching timeout.
public long dispatchingTimeoutNanos;
- // Window frame position.
+ // Window frame area.
public int frameLeft;
public int frameTop;
+ public int frameRight;
+ public int frameBottom;
+
+ // Window visible frame area.
+ public int visibleFrameLeft;
+ public int visibleFrameTop;
+ public int visibleFrameRight;
+ public int visibleFrameBottom;
// Window touchable area.
public int touchableAreaLeft;
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 79bde7c..2ab8091 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -5208,6 +5208,14 @@
final Rect frame = child.mFrame;
inputWindow.frameLeft = frame.left;
inputWindow.frameTop = frame.top;
+ inputWindow.frameRight = frame.right;
+ inputWindow.frameBottom = frame.bottom;
+
+ final Rect visibleFrame = child.mVisibleFrame;
+ inputWindow.visibleFrameLeft = visibleFrame.left;
+ inputWindow.visibleFrameTop = visibleFrame.top;
+ inputWindow.visibleFrameRight = visibleFrame.right;
+ inputWindow.visibleFrameBottom = visibleFrame.bottom;
switch (child.mTouchableInsets) {
default:
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 2ce8c08..7d8c307 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -132,7 +132,8 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
-public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor {
+public final class ActivityManagerService extends ActivityManagerNative
+ implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
static final String TAG = "ActivityManager";
static final boolean DEBUG = false;
static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
@@ -751,6 +752,7 @@
boolean mBooting = false;
boolean mWaitingUpdate = false;
boolean mDidUpdate = false;
+ boolean mOnBattery = false;
Context mContext;
@@ -1382,8 +1384,10 @@
systemDir, "batterystats.bin").toString());
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.getActiveStatistics().writeLocked();
+ mOnBattery = mBatteryStatsService.getActiveStatistics().getIsOnBattery();
+ mBatteryStatsService.getActiveStatistics().setCallback(this);
- mUsageStatsService = new UsageStatsService( new File(
+ mUsageStatsService = new UsageStatsService(new File(
systemDir, "usagestats").toString());
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
@@ -1496,25 +1500,36 @@
synchronized(bstats) {
synchronized(mPidsSelfLocked) {
if (haveNewCpuStats) {
- if (mBatteryStatsService.isOnBattery()) {
+ if (mOnBattery) {
+ int perc = bstats.startAddingCpuLocked();
+ int totalUTime = 0;
+ int totalSTime = 0;
final int N = mProcessStats.countWorkingStats();
for (int i=0; i<N; i++) {
ProcessStats.Stats st
= mProcessStats.getWorkingStats(i);
ProcessRecord pr = mPidsSelfLocked.get(st.pid);
+ int otherUTime = (st.rel_utime*perc)/100;
+ int otherSTime = (st.rel_stime*perc)/100;
+ totalUTime += otherUTime;
+ totalSTime += otherSTime;
if (pr != null) {
BatteryStatsImpl.Uid.Proc ps = pr.batteryStats;
- ps.addCpuTimeLocked(st.rel_utime, st.rel_stime);
+ ps.addCpuTimeLocked(st.rel_utime-otherUTime,
+ st.rel_stime-otherSTime);
ps.addSpeedStepTimes(cpuSpeedTimes);
} else {
BatteryStatsImpl.Uid.Proc ps =
bstats.getProcessStatsLocked(st.name, st.pid);
if (ps != null) {
- ps.addCpuTimeLocked(st.rel_utime, st.rel_stime);
+ ps.addCpuTimeLocked(st.rel_utime-otherUTime,
+ st.rel_stime-otherSTime);
ps.addSpeedStepTimes(cpuSpeedTimes);
}
}
}
+ bstats.finishAddingCpuLocked(perc, totalUTime,
+ totalSTime, cpuSpeedTimes);
}
}
}
@@ -1527,6 +1542,23 @@
}
}
+ @Override
+ public void batteryNeedsCpuUpdate() {
+ updateCpuStatsNow();
+ }
+
+ @Override
+ public void batteryPowerChanged(boolean onBattery) {
+ // When plugging in, update the CPU stats first before changing
+ // the plug state.
+ updateCpuStatsNow();
+ synchronized (this) {
+ synchronized(mPidsSelfLocked) {
+ mOnBattery = onBattery;
+ }
+ }
+ }
+
/**
* Initialize the application bind args. These are passed to each
* process when the bindApplication() IPC is sent to the process. They're
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 6d1fbab..67df707 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -131,6 +131,13 @@
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
+ long wtime;
+ synchronized (batteryStats.getBatteryStats()) {
+ wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid,
+ pid, SystemClock.elapsedRealtime());
+ }
+ long timeUsed = wtime - lastWakeTime;
+
if (info.className != null) {
pw.print(prefix); pw.print("class="); pw.println(info.className);
}
@@ -182,7 +189,9 @@
pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
pw.print(" lruSeq="); pw.println(lruSeq);
pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
- pw.print(" lastRequestedGc=");
+ pw.print(" time used=");
+ TimeUtils.formatDuration(timeUsed, pw); pw.println("");
+ pw.print(prefix); pw.print("lastRequestedGc=");
TimeUtils.formatDuration(lastRequestedGc, now, pw);
pw.print(" lastLowMemory=");
TimeUtils.formatDuration(lastLowMemory, now, pw);
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
index 563ce58..eee97c3 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -490,7 +490,7 @@
private class KeepAliveProcess implements Runnable {
private static final String TAG = "\\KEEPALIVE/";
- private static final int INTERVAL = 15;
+ private static final int INTERVAL = 10;
private SipSessionGroup.SipSessionImpl mSession;
public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index a237ee9..7af5e95 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -169,6 +169,12 @@
jfieldID dispatchingTimeoutNanos;
jfieldID frameLeft;
jfieldID frameTop;
+ jfieldID frameRight;
+ jfieldID frameBottom;
+ jfieldID visibleFrameLeft;
+ jfieldID visibleFrameTop;
+ jfieldID visibleFrameRight;
+ jfieldID visibleFrameBottom;
jfieldID touchableAreaLeft;
jfieldID touchableAreaTop;
jfieldID touchableAreaRight;
@@ -269,6 +275,7 @@
nsecs_t& outNewTimeout);
virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel);
virtual nsecs_t getKeyRepeatTimeout();
+ virtual nsecs_t getKeyRepeatDelay();
virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
@@ -283,6 +290,12 @@
nsecs_t dispatchingTimeout;
int32_t frameLeft;
int32_t frameTop;
+ int32_t frameRight;
+ int32_t frameBottom;
+ int32_t visibleFrameLeft;
+ int32_t visibleFrameTop;
+ int32_t visibleFrameRight;
+ int32_t visibleFrameBottom;
int32_t touchableAreaLeft;
int32_t touchableAreaTop;
int32_t touchableAreaRight;
@@ -294,10 +307,8 @@
int32_t ownerPid;
int32_t ownerUid;
- inline bool touchableAreaContainsPoint(int32_t x, int32_t y) {
- return x >= touchableAreaLeft && x <= touchableAreaRight
- && y >= touchableAreaTop && y <= touchableAreaBottom;
- }
+ bool visibleFrameIntersects(const InputWindow* other) const;
+ bool touchableAreaContainsPoint(int32_t x, int32_t y) const;
};
struct InputApplication {
@@ -370,9 +381,13 @@
// Focus tracking for touch.
bool mTouchDown;
InputWindow* mTouchedWindow; // primary target for current down
+ bool mTouchedWindowIsObscured; // true if other windows may obscure the target
Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets
-
- Vector<InputWindow*> mTempTouchedOutsideWindows; // temporary outside touch targets
+ struct OutsideTarget {
+ InputWindow* window;
+ bool obscured;
+ };
+ Vector<OutsideTarget> mTempTouchedOutsideTargets; // temporary outside touch targets
Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets
// Focused application.
@@ -391,6 +406,7 @@
int32_t waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags,
int32_t injectorPid, int32_t injectorUid,
Vector<InputTarget>& outTargets, InputWindow*& outTouchedWindow);
+ bool isWindowObscuredLocked(const InputWindow* window);
void releaseTouchedWindowLd();
@@ -996,6 +1012,10 @@
}
}
+nsecs_t NativeInputManager::getKeyRepeatDelay() {
+ return milliseconds_to_nanoseconds(50);
+}
+
int32_t NativeInputManager::getMaxEventsPerSecond() {
if (mMaxEventsPerSecond < 0) {
JNIEnv* env = jniEnv();
@@ -1117,6 +1137,18 @@
gInputWindowClassInfo.frameLeft);
jint frameTop = env->GetIntField(windowObj,
gInputWindowClassInfo.frameTop);
+ jint frameRight = env->GetIntField(windowObj,
+ gInputWindowClassInfo.frameRight);
+ jint frameBottom = env->GetIntField(windowObj,
+ gInputWindowClassInfo.frameBottom);
+ jint visibleFrameLeft = env->GetIntField(windowObj,
+ gInputWindowClassInfo.visibleFrameLeft);
+ jint visibleFrameTop = env->GetIntField(windowObj,
+ gInputWindowClassInfo.visibleFrameTop);
+ jint visibleFrameRight = env->GetIntField(windowObj,
+ gInputWindowClassInfo.visibleFrameRight);
+ jint visibleFrameBottom = env->GetIntField(windowObj,
+ gInputWindowClassInfo.visibleFrameBottom);
jint touchableAreaLeft = env->GetIntField(windowObj,
gInputWindowClassInfo.touchableAreaLeft);
jint touchableAreaTop = env->GetIntField(windowObj,
@@ -1144,6 +1176,12 @@
outWindow.dispatchingTimeout = dispatchingTimeoutNanos;
outWindow.frameLeft = frameLeft;
outWindow.frameTop = frameTop;
+ outWindow.frameRight = frameRight;
+ outWindow.frameBottom = frameBottom;
+ outWindow.visibleFrameLeft = visibleFrameLeft;
+ outWindow.visibleFrameTop = visibleFrameTop;
+ outWindow.visibleFrameRight = visibleFrameRight;
+ outWindow.visibleFrameBottom = visibleFrameBottom;
outWindow.touchableAreaLeft = touchableAreaLeft;
outWindow.touchableAreaTop = touchableAreaTop;
outWindow.touchableAreaRight = touchableAreaRight;
@@ -1417,11 +1455,12 @@
/* Case 1: ACTION_DOWN */
InputWindow* newTouchedWindow = NULL;
- mTempTouchedOutsideWindows.clear();
+ mTempTouchedOutsideTargets.clear();
int32_t x = int32_t(motionEvent->getX(0));
int32_t y = int32_t(motionEvent->getY(0));
InputWindow* topErrorWindow = NULL;
+ bool obscured = false;
// Traverse windows from front to back to find touched window and outside targets.
size_t numWindows = mWindows.size();
@@ -1442,13 +1481,17 @@
if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
if (! screenWasOff || flags & FLAG_TOUCHABLE_WHEN_WAKING) {
newTouchedWindow = window;
+ obscured = isWindowObscuredLocked(window);
}
break; // found touched window, exit window loop
}
}
if (flags & FLAG_WATCH_OUTSIDE_TOUCH) {
- mTempTouchedOutsideWindows.push(window);
+ OutsideTarget outsideTarget;
+ outsideTarget.window = window;
+ outsideTarget.obscured = isWindowObscuredLocked(window);
+ mTempTouchedOutsideTargets.push(outsideTarget);
}
}
}
@@ -1501,6 +1544,7 @@
releaseTouchedWindowLd();
mTouchedWindow = newTouchedWindow;
+ mTouchedWindowIsObscured = obscured;
if (newTouchedWindow->hasWallpaper) {
mTouchedWallpaperWindows.appendVector(mWallpaperWindows);
@@ -1557,21 +1601,31 @@
if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
size_t numWallpaperWindows = mTouchedWallpaperWindows.size();
for (size_t i = 0; i < numWallpaperWindows; i++) {
- addTarget(mTouchedWallpaperWindows[i], 0, 0, outTargets);
+ addTarget(mTouchedWallpaperWindows[i],
+ InputTarget::FLAG_WINDOW_IS_OBSCURED, 0, outTargets);
}
- size_t numOutsideWindows = mTempTouchedOutsideWindows.size();
- for (size_t i = 0; i < numOutsideWindows; i++) {
- addTarget(mTempTouchedOutsideWindows[i], InputTarget::FLAG_OUTSIDE, 0, outTargets);
+ size_t numOutsideTargets = mTempTouchedOutsideTargets.size();
+ for (size_t i = 0; i < numOutsideTargets; i++) {
+ const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i];
+ int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
+ if (outsideTarget.obscured) {
+ outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+ addTarget(outsideTarget.window, outsideTargetFlags, 0, outTargets);
}
- addTarget(mTouchedWindow, InputTarget::FLAG_SYNC,
+ int32_t targetFlags = InputTarget::FLAG_SYNC;
+ if (mTouchedWindowIsObscured) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+ addTarget(mTouchedWindow, targetFlags,
anrTimer.getTimeSpentWaitingForApplication(), outTargets);
outTouchedWindow = mTouchedWindow;
} else {
outTouchedWindow = NULL;
}
- mTempTouchedOutsideWindows.clear();
+ mTempTouchedOutsideTargets.clear();
// Check injection permission once and for all.
if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
@@ -1616,6 +1670,7 @@
void NativeInputManager::releaseTouchedWindowLd() {
mTouchedWindow = NULL;
+ mTouchedWindowIsObscured = false;
mTouchedWallpaperWindows.clear();
}
@@ -1661,6 +1716,20 @@
return true;
}
+bool NativeInputManager::isWindowObscuredLocked(const InputWindow* window) {
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const InputWindow* other = & mWindows.itemAt(i);
+ if (other == window) {
+ break;
+ }
+ if (other->visible && window->visibleFrameIntersects(other)) {
+ return true;
+ }
+ }
+ return false;
+}
+
int32_t NativeInputManager::waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) {
#if DEBUG_INPUT_DISPATCHER_POLICY
@@ -1935,12 +2004,17 @@
for (size_t i = 0; i < mWindows.size(); i++) {
dump.appendFormat(" windows[%d]: '%s', paused=%d, hasFocus=%d, hasWallpaper=%d, "
"visible=%d, flags=0x%08x, type=0x%08x, "
- "frame=[%d,%d], touchableArea=[%d,%d][%d,%d], "
+ "frame=[%d,%d][%d,%d], "
+ "visibleFrame=[%d,%d][%d,%d], "
+ "touchableArea=[%d,%d][%d,%d], "
"ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
i, mWindows[i].inputChannel->getName().string(),
mWindows[i].paused, mWindows[i].hasFocus, mWindows[i].hasWallpaper,
mWindows[i].visible, mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType,
mWindows[i].frameLeft, mWindows[i].frameTop,
+ mWindows[i].frameRight, mWindows[i].frameBottom,
+ mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop,
+ mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom,
mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop,
mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom,
mWindows[i].ownerPid, mWindows[i].ownerUid,
@@ -1955,6 +2029,20 @@
// ----------------------------------------------------------------------------
+bool NativeInputManager::InputWindow::visibleFrameIntersects(const InputWindow* other) const {
+ return visibleFrameRight > other->visibleFrameLeft
+ && visibleFrameLeft < other->visibleFrameRight
+ && visibleFrameBottom > other->visibleFrameTop
+ && visibleFrameTop < other->visibleFrameBottom;
+}
+
+bool NativeInputManager::InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
+ return x >= touchableAreaLeft && x <= touchableAreaRight
+ && y >= touchableAreaTop && y <= touchableAreaBottom;
+}
+
+// ----------------------------------------------------------------------------
+
NativeInputManager::ANRTimer::ANRTimer() :
mBudget(APPLICATION), mStartTime(now()), mFrozen(false), mPausedWindow(NULL) {
}
@@ -2507,6 +2595,24 @@
GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz,
"frameTop", "I");
+ GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz,
+ "frameRight", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz,
+ "frameBottom", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.visibleFrameLeft, gInputWindowClassInfo.clazz,
+ "visibleFrameLeft", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.visibleFrameTop, gInputWindowClassInfo.clazz,
+ "visibleFrameTop", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.visibleFrameRight, gInputWindowClassInfo.clazz,
+ "visibleFrameRight", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.visibleFrameBottom, gInputWindowClassInfo.clazz,
+ "visibleFrameBottom", "I");
+
GET_FIELD_ID(gInputWindowClassInfo.touchableAreaLeft, gInputWindowClassInfo.clazz,
"touchableAreaLeft", "I");
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 166c528..3e23929 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -292,6 +292,9 @@
void DisplayHardware::releaseScreen() const
{
DisplayHardwareBase::releaseScreen();
+ if (mHwc->initCheck() == NO_ERROR) {
+ mHwc->release();
+ }
}
void DisplayHardware::acquireScreen() const
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 0291d78..129be4e 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -87,6 +87,11 @@
return (status_t)err;
}
+status_t HWComposer::release() const {
+ int err = mHwc->set(mHwc, NULL, NULL, NULL);
+ return (status_t)err;
+}
+
size_t HWComposer::getNumLayers() const {
return mList ? mList->numHwLayers : 0;
}
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index c5d5c2b..22ff10c 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -48,6 +48,8 @@
// commits the list
status_t commit() const;
+ // release hardware resources
+ status_t release() const;
size_t getNumLayers() const;
hwc_layer_t* getLayers() const;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a78d9b9..d820380 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -383,10 +383,10 @@
// inform the h/w that we're done compositing
hw.compositionComplete();
- // release the clients before we flip ('cause flip might block)
+ postFramebuffer();
+
unlockClients();
- postFramebuffer();
} else {
// pretend we did the post
unlockClients();
@@ -885,8 +885,8 @@
*/
for (size_t i=0 ; i<count ; i++) {
if (cur) {
- if (!(cur[i].compositionType == HWC_FRAMEBUFFER) ||
- cur[i].flags & HWC_SKIP_LAYER) {
+ if ((cur[i].compositionType != HWC_FRAMEBUFFER) &&
+ !(cur[i].flags & HWC_SKIP_LAYER)) {
// skip layers handled by the HAL
continue;
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index d899430..0746562 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -53,6 +53,10 @@
public static final int ENCODING_7BIT = 1;
public static final int ENCODING_8BIT = 2;
public static final int ENCODING_16BIT = 3;
+ /**
+ * @hide This value is not defined in global standard. Only in Korea, this is used.
+ */
+ public static final int ENCODING_KSC5601 = 4;
/** The maximum number of payload bytes per message */
public static final int MAX_USER_DATA_BYTES = 140;
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index c1232e8..caec7e1 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -466,6 +466,33 @@
}
/**
+ * Hangup foreground call and resume the specific background call
+ *
+ * Note: this is noop if there is no foreground call or the heldCall is null
+ *
+ * @param heldCall to become foreground
+ * @throws CallStateException
+ */
+ public void hangupForegroundResumeBackground(Call heldCall) throws CallStateException {
+ Phone foregroundPhone = null;
+ Phone backgroundPhone = null;
+
+ if (hasActiveFgCall()) {
+ foregroundPhone = getFgPhone();
+ if (heldCall != null) {
+ backgroundPhone = heldCall.getPhone();
+ if (foregroundPhone == backgroundPhone) {
+ getActiveFgCall().hangup();
+ } else {
+ // the call to be hangup and resumed belongs to different phones
+ getActiveFgCall().hangup();
+ switchHoldingAndActive(heldCall);
+ }
+ }
+ }
+ }
+
+ /**
* Whether or not the phone can conference in the current phone
* state--that is, one call holding and one call active.
* @return true if the phone can conference; false otherwise.
diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
index 75ea116..e42827f 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -16,10 +16,14 @@
package com.android.internal.telephony;
+import android.text.TextUtils;
import android.util.SparseIntArray;
import android.util.Log;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
/**
* This class implements the character set mapping between
* the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
@@ -354,6 +358,32 @@
*/
public static String
gsm8BitUnpackedToString(byte[] data, int offset, int length) {
+ return gsm8BitUnpackedToString(data, offset, length, "");
+ }
+
+ /**
+ * Convert a GSM alphabet string that's stored in 8-bit unpacked
+ * format (as it often appears in SIM records) into a String
+ *
+ * Field may be padded with trailing 0xff's. The decode stops
+ * at the first 0xff encountered.
+ *
+ * Additionally, in some country(ex. Korea), there are non-ASCII or MBCS characters.
+ * If a character set is given, characters in data are treat as MBCS.
+ */
+ public static String
+ gsm8BitUnpackedToString(byte[] data, int offset, int length, String characterset) {
+ boolean isMbcs = false;
+ Charset charset = null;
+ ByteBuffer mbcsBuffer = null;
+
+ if (!TextUtils.isEmpty(characterset)
+ && !characterset.equalsIgnoreCase("us-ascii")
+ && Charset.isSupported(characterset)) {
+ isMbcs = true;
+ charset = Charset.forName(characterset);
+ mbcsBuffer = ByteBuffer.allocate(2);
+ }
boolean prevWasEscape;
StringBuilder ret = new StringBuilder(length);
@@ -379,7 +409,15 @@
if (prevWasEscape) {
ret.append((char)gsmExtendedToChar.get(c, ' '));
} else {
- ret.append((char)gsmToChar.get(c, ' '));
+ if (!isMbcs || c < 0x80 || i + 1 >= offset + length) {
+ ret.append((char)gsmToChar.get(c, ' '));
+ } else {
+ // isMbcs must be true. So both mbcsBuffer and charset are initialized.
+ mbcsBuffer.clear();
+ mbcsBuffer.put(data, i++, 2);
+ mbcsBuffer.flip();
+ ret.append(charset.decode(mbcsBuffer).toString());
+ }
}
prevWasEscape = false;
}
diff --git a/telephony/java/com/android/internal/telephony/IccUtils.java b/telephony/java/com/android/internal/telephony/IccUtils.java
index 005ae37..df579b0 100644
--- a/telephony/java/com/android/internal/telephony/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/IccUtils.java
@@ -16,13 +16,16 @@
package com.android.internal.telephony;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Log;
import com.android.internal.telephony.GsmAlphabet;
-
import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
/**
* Various methods, useful for dealing with SIM data.
@@ -150,6 +153,9 @@
*/
public static String
adnStringFieldToString(byte[] data, int offset, int length) {
+ if (length == 0) {
+ return "";
+ }
if (length >= 1) {
if (data[offset] == (byte) 0x80) {
int ucslen = (length - 1) / 2;
@@ -225,7 +231,15 @@
return ret.toString();
}
- return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length);
+ Resources resource = Resources.getSystem();
+ String defaultCharset = "";
+ try {
+ defaultCharset =
+ resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
+ } catch (NotFoundException e) {
+ // Ignore Exception and defaultCharset is set to a empty string.
+ }
+ return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
}
static int
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index f1a7107..3a7ce47 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -32,9 +32,11 @@
import android.database.SQLException;
import android.net.Uri;
import android.os.AsyncResult;
+import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
+import android.os.StatFs;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.provider.Settings;
@@ -240,11 +242,9 @@
// Register for device storage intents. Use these to notify the RIL
// that storage for SMS is or is not available.
- // TODO: Revisit this for a later release. Storage reporting should
- // rely more on application indication.
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
- filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_FULL);
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
mContext.registerReceiver(mResultReceiver, filter);
}
@@ -966,10 +966,10 @@
private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) {
+ if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) {
mStorageAvailable = false;
mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
- } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) {
+ } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) {
mStorageAvailable = true;
mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
} else {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 47a170a..6e53ec5 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -101,7 +101,6 @@
CdmaCallTracker mCT;
CdmaSMSDispatcher mSMS;
CdmaServiceStateTracker mSST;
- RuimFileHandler mRuimFileHandler;
RuimRecords mRuimRecords;
RuimCard mRuimCard;
ArrayList <CdmaMmiCode> mPendingMmis = new ArrayList<CdmaMmiCode>();
@@ -159,7 +158,7 @@
mDataConnection = new CdmaDataConnectionTracker (this);
mRuimCard = new RuimCard(this);
mRuimPhoneBookInterfaceManager = new RuimPhoneBookInterfaceManager(this);
- mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this);
+ mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this, mSMS);
mSubInfo = new PhoneSubInfo(this);
mEriManager = new EriManager(this, context, EriManager.ERI_FROM_XML);
mCcatService = CatService.getInstance(mCM, mRuimRecords, mContext,
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
index a9df375..d84b6ab 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -27,6 +27,7 @@
import com.android.internal.telephony.IccSmsInterfaceManager;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsRawData;
import java.util.ArrayList;
@@ -80,9 +81,9 @@
}
};
- public RuimSmsInterfaceManager(CDMAPhone phone) {
+ public RuimSmsInterfaceManager(CDMAPhone phone, SMSDispatcher dispatcher) {
super(phone);
- mDispatcher = new CdmaSMSDispatcher(phone);
+ mDispatcher = dispatcher;
}
public void dispose() {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index d64541b..30a6324 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -117,10 +117,6 @@
Thread debugPortThread;
ServerSocket debugSocket;
- private int mReportedRadioResets;
- private int mReportedAttemptedConnects;
- private int mReportedSuccessfulConnects;
-
private String mImei;
private String mImeiSv;
private String mVmNumber;
@@ -151,7 +147,7 @@
mSimCard = new SimCard(this);
if (!unitTestMode) {
mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this);
- mSimSmsIntManager = new SimSmsInterfaceManager(this);
+ mSimSmsIntManager = new SimSmsInterfaceManager(this, mSMS);
mSubInfo = new PhoneSubInfo(this);
}
mStkService = CatService.getInstance(mCM, mSIMRecords, mContext,
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 4414460..885e5bc 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -58,6 +58,7 @@
import java.io.IOException;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
@@ -1104,8 +1105,8 @@
if (apn.proxy != null && apn.proxy.length() != 0) {
try {
ProxyProperties proxy = new ProxyProperties();
- proxy.setAddress(InetAddress.getByName(apn.proxy));
- proxy.setPort(Integer.parseInt(apn.port));
+ proxy.setSocketAddress(new InetSocketAddress(InetAddress.getByName(apn.proxy),
+ Integer.parseInt(apn.port)));
mLinkProperties.setHttpProxy(proxy);
} catch (UnknownHostException e) {
Log.e(LOG_TAG, "UnknownHostException making ProxyProperties: " + e);
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index f000d79..aab359f 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -25,6 +25,7 @@
import com.android.internal.telephony.IccConstants;
import com.android.internal.telephony.IccSmsInterfaceManager;
import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsRawData;
import java.util.ArrayList;
@@ -78,9 +79,9 @@
}
};
- public SimSmsInterfaceManager(GSMPhone phone) {
+ public SimSmsInterfaceManager(GSMPhone phone, SMSDispatcher dispatcher) {
super(phone);
- mDispatcher = new GsmSMSDispatcher(phone);
+ mDispatcher = dispatcher;
}
public void dispose() {
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index a77484a..e24613f 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -33,6 +33,7 @@
import static android.telephony.SmsMessage.ENCODING_7BIT;
import static android.telephony.SmsMessage.ENCODING_8BIT;
import static android.telephony.SmsMessage.ENCODING_16BIT;
+import static android.telephony.SmsMessage.ENCODING_KSC5601;
import static android.telephony.SmsMessage.ENCODING_UNKNOWN;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
@@ -776,6 +777,27 @@
return ret;
}
+ /**
+ * Interprets the user data payload as KSC-5601 characters, and
+ * decodes them into a String.
+ *
+ * @param byteCount the number of bytes in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataKSC5601(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(pdu, cur, byteCount, "KSC5601");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ cur += byteCount;
+ return ret;
+ }
+
boolean moreDataPresent() {
return (pdu.length > cur);
}
@@ -1111,6 +1133,16 @@
Log.w(LOG_TAG, "MWI for fax, email, or other "
+ (dataCodingScheme & 0xff));
}
+ } else if ((dataCodingScheme & 0xC0) == 0x80) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+ // 0x80..0xBF == Reserved coding groups
+ if (dataCodingScheme == 0x84) {
+ // This value used for KSC5601 by carriers in Korea.
+ encodingType = ENCODING_KSC5601;
+ } else {
+ Log.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+ + (dataCodingScheme & 0xff));
+ }
} else {
Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
+ (dataCodingScheme & 0xff));
@@ -1135,6 +1167,10 @@
case ENCODING_16BIT:
messageBody = p.getUserDataUCS2(count);
break;
+
+ case ENCODING_KSC5601:
+ messageBody = p.getUserDataKSC5601(count);
+ break;
}
if (Config.LOGV) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
index a6b9a2a..7011aeb 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
@@ -16,6 +16,8 @@
package com.android.internal.telephony;
+import com.android.internal.telephony.GsmAlphabet;
+
import junit.framework.TestCase;
import android.test.suitebuilder.annotation.LargeTest;
@@ -307,4 +309,26 @@
assertEquals("a",
GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));
}
+
+ @SmallTest
+ public void testGsm8BitUpackedWithEuckr() throws Exception {
+ // Some feature phones in Korea store contacts as euc-kr.
+ // Test this situations.
+ byte unpacked[];
+
+ // Test general alphabet strings.
+ unpacked = IccUtils.hexStringToBytes("61626320646566FF");
+ assertEquals("abc def",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+
+ // Test korean strings.
+ unpacked = IccUtils.hexStringToBytes("C5D7BDBAC6AEFF");
+ assertEquals("\uD14C\uC2A4\uD2B8",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+
+ // Test gsm Extented Characters.
+ unpacked = GsmAlphabet.stringToGsm8BitPacked(sGsmExtendedChars);
+ assertEquals(sGsmExtendedChars,
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+ }
}
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index bb45a9a..3433dcf 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -588,7 +588,7 @@
// Give device socket a reasonable timeout and buffer size.
timeval tv;
tv.tv_sec = 0;
- tv.tv_usec = 1000 * sampleCount / sampleRate * 1000;
+ tv.tv_usec = 1000 * sampleCount / sampleRate * 500;
if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) ||
setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) ||
setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) {
@@ -793,7 +793,7 @@
status_t status = mRecord.obtainBuffer(&buffer, 1);
if (status == NO_ERROR) {
- int count = (buffer.frameCount < toRead) ?
+ int count = ((int)buffer.frameCount < toRead) ?
buffer.frameCount : toRead;
memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2);
toRead -= count;