Merge "Continuation of the unified account chooser flow." into ics-factoryrom
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index eef658e..7046008 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -83,6 +83,12 @@
String[] getTetheredIfaces();
+ /**
+ * Return list of interface pairs that are actively tethered. Even indexes are
+ * remote interface, and odd indexes are corresponding local interfaces.
+ */
+ String[] getTetheredIfacePairs();
+
String[] getTetheringErroredIfaces();
String[] getTetherableUsbRegexs();
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index d8ac31f..a5cdf70 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -441,10 +441,10 @@
final long curStart = bucketStart[i];
final long curEnd = curStart + bucketDuration;
- // bucket is older than record; we're finished
- if (curEnd < start) break;
- // bucket is newer than record; keep looking
- if (curStart > end) continue;
+ // bucket is older than request; we're finished
+ if (curEnd <= start) break;
+ // bucket is newer than request; keep looking
+ if (curStart >= end) continue;
// include full value for active buckets, otherwise only fractional
final boolean activeBucket = curStart < now && curEnd > now;
@@ -466,7 +466,6 @@
if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
}
-
return entry;
}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 47cfa73..18eb9f6 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -53,25 +53,33 @@
public static final int UID_REMOVED = -4;
/**
+ * Special UID value used when collecting {@link NetworkStatsHistory} for
+ * tethering traffic.
+ *
+ * @hide
+ */
+ public static final int UID_TETHERING = -5;
+
+ /**
* Default tag value for {@link DownloadManager} traffic.
*
* @hide
*/
- public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFF0001;
+ public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFFFF01;
/**
* Default tag value for {@link MediaPlayer} traffic.
*
* @hide
*/
- public static final int TAG_SYSTEM_MEDIA = 0xFFFF0002;
+ public static final int TAG_SYSTEM_MEDIA = 0xFFFFFF02;
/**
* Default tag value for {@link BackupManager} traffic.
*
* @hide
*/
- public static final int TAG_SYSTEM_BACKUP = 0xFFFF0003;
+ public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03;
/**
* Snapshot of {@link NetworkStats} when the currently active profiling
@@ -90,6 +98,10 @@
* <p>
* Changes only take effect during subsequent calls to
* {@link #tagSocket(Socket)}.
+ * <p>
+ * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
+ * used internally by system services like {@link DownloadManager} when
+ * performing traffic on behalf of an application.
*/
public static void setThreadStatsTag(int tag) {
NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 4e5645d..66373fe 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -231,6 +231,13 @@
NetworkStats getNetworkStatsUidDetail(int uid);
/**
+ * Return summary of network statistics for the requested pairs of
+ * tethering interfaces. Even indexes are remote interface, and odd
+ * indexes are corresponding local interfaces.
+ */
+ NetworkStats getNetworkStatsTethering(in String[] ifacePairs);
+
+ /**
* Set quota for an interface.
*/
void setInterfaceQuota(String iface, long quotaBytes);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e5882fc..bc5994e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4031,8 +4031,6 @@
public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";
/** {@hide} */
public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
- /** {@hide} */
- public static final String NETSTATS_FORCE_COMPLETE_POLL = "netstats_force_complete_poll";
/** Preferred NTP server. {@hide} */
public static final String NTP_SERVER = "ntp_server";
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index cfe4cb7..ba89ef3 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3654,7 +3654,8 @@
vt.computeCurrentVelocity(1000, mMaximumVelocity);
final float yvel = -vt.getYVelocity(activeId);
- if (scroller.isScrollingInDirection(0, yvel)) {
+ if (Math.abs(yvel) >= mMinimumVelocity
+ && scroller.isScrollingInDirection(0, yvel)) {
// Keep the fling alive a little longer
postDelayed(this, FLYWHEEL_TIMEOUT);
} else {
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 542a1ef..e571998 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -532,7 +532,7 @@
final int dx = mScrollerX.mFinal - mScrollerX.mStart;
final int dy = mScrollerY.mFinal - mScrollerY.mStart;
return !isFinished() && Math.signum(xvel) == Math.signum(dx) &&
- Math.signum(yvel) == Math.signum(dy);
+ Math.signum(yvel) == Math.signum(dy);
}
static class SplineOverScroller {
diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java
index 29ad15b..f9c2cce 100644
--- a/core/java/com/android/internal/widget/TransportControlView.java
+++ b/core/java/com/android/internal/widget/TransportControlView.java
@@ -21,6 +21,8 @@
import com.android.internal.widget.LockScreenWidgetCallback;
import com.android.internal.widget.LockScreenWidgetInterface;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -68,7 +70,7 @@
private int mClientGeneration;
private Metadata mMetadata = new Metadata();
private boolean mAttached;
- private ComponentName mClientName;
+ private PendingIntent mClientIntent;
private int mTransportControlFlags;
private int mPlayState;
private AudioManager mAudioManager;
@@ -116,7 +118,7 @@
}
}
mClientGeneration = msg.arg1;
- mClientName = (ComponentName) msg.obj;
+ mClientIntent = (PendingIntent) msg.obj;
break;
}
@@ -174,12 +176,12 @@
}
}
- public void setCurrentClientId(int clientGeneration, ComponentName clientEventReceiver,
+ public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
boolean clearing) throws RemoteException {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_SET_GENERATION_ID,
- clientGeneration, (clearing ? 1 : 0), clientEventReceiver).sendToTarget();
+ clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
}
}
};
@@ -365,16 +367,27 @@
}
private void sendMediaButtonClick(int keyCode) {
- // TODO: target to specific player based on mClientName
+ // use the registered PendingIntent that will be processed by the registered
+ // media button event receiver, which is the component of mClientIntent
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- getContext().sendOrderedBroadcast(intent, null);
+ try {
+ mClientIntent.send(getContext(), 0, intent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending intent for media button down: "+e);
+ e.printStackTrace();
+ }
keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- getContext().sendOrderedBroadcast(intent, null);
+ try {
+ mClientIntent.send(getContext(), 0, intent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending intent for media button up: "+e);
+ e.printStackTrace();
+ }
}
public void setCallback(LockScreenWidgetCallback callback) {
diff --git a/core/res/res/drawable-hdpi/scrubber_control_disabled_holo.png b/core/res/res/drawable-hdpi/scrubber_control_disabled_holo.png
index a231195..e0f55bf 100644
--- a/core/res/res/drawable-hdpi/scrubber_control_disabled_holo.png
+++ b/core/res/res/drawable-hdpi/scrubber_control_disabled_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_focused_holo.png b/core/res/res/drawable-hdpi/scrubber_control_focused_holo.png
new file mode 100644
index 0000000..7b264f1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/scrubber_control_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_holo.png b/core/res/res/drawable-hdpi/scrubber_control_holo.png
deleted file mode 100644
index fae05e5..0000000
--- a/core/res/res/drawable-hdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_normal_holo.png b/core/res/res/drawable-hdpi/scrubber_control_normal_holo.png
new file mode 100644
index 0000000..c3a5f7d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/scrubber_control_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_pressed_holo.png b/core/res/res/drawable-hdpi/scrubber_control_pressed_holo.png
index ff4d710..6fa4e00 100644
--- a/core/res/res/drawable-hdpi/scrubber_control_pressed_holo.png
+++ b/core/res/res/drawable-hdpi/scrubber_control_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_disabled_holo.png b/core/res/res/drawable-mdpi/scrubber_control_disabled_holo.png
index 9d7b77c..a035f6f 100644
--- a/core/res/res/drawable-mdpi/scrubber_control_disabled_holo.png
+++ b/core/res/res/drawable-mdpi/scrubber_control_disabled_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_focused_holo.png b/core/res/res/drawable-mdpi/scrubber_control_focused_holo.png
new file mode 100644
index 0000000..5c9720f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/scrubber_control_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_holo.png b/core/res/res/drawable-mdpi/scrubber_control_holo.png
deleted file mode 100644
index 832fa07..0000000
--- a/core/res/res/drawable-mdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_normal_holo.png b/core/res/res/drawable-mdpi/scrubber_control_normal_holo.png
new file mode 100644
index 0000000..2497716
--- /dev/null
+++ b/core/res/res/drawable-mdpi/scrubber_control_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_pressed_holo.png b/core/res/res/drawable-mdpi/scrubber_control_pressed_holo.png
index 4a518f2..5fc3ca2 100644
--- a/core/res/res/drawable-mdpi/scrubber_control_pressed_holo.png
+++ b/core/res/res/drawable-mdpi/scrubber_control_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_disabled_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_disabled_holo.png
index 0b0bf24..d70151a 100644
--- a/core/res/res/drawable-xhdpi/scrubber_control_disabled_holo.png
+++ b/core/res/res/drawable-xhdpi/scrubber_control_disabled_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_focused_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_focused_holo.png
new file mode 100644
index 0000000..017688f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/scrubber_control_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_holo.png
deleted file mode 100644
index 45060cb..0000000
--- a/core/res/res/drawable-xhdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_normal_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_normal_holo.png
new file mode 100644
index 0000000..727fcf1
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/scrubber_control_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_pressed_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_pressed_holo.png
index d1fe115..6d03e42 100644
--- a/core/res/res/drawable-xhdpi/scrubber_control_pressed_holo.png
+++ b/core/res/res/drawable-xhdpi/scrubber_control_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable/scrubber_control_selector_holo.xml b/core/res/res/drawable/scrubber_control_selector_holo.xml
index d146eee..1e6c668 100644
--- a/core/res/res/drawable/scrubber_control_selector_holo.xml
+++ b/core/res/res/drawable/scrubber_control_selector_holo.xml
@@ -15,7 +15,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:state_enabled="true" android:drawable="@android:drawable/scrubber_control_pressed_holo" />
- <item android:state_enabled="true" android:drawable="@android:drawable/scrubber_control_holo" />
- <item android:drawable="@android:drawable/scrubber_control_disabled_holo" />
+ <item android:state_enabled="false" android:drawable="@android:drawable/scrubber_control_disabled_holo" />
+ <item android:state_pressed="true" android:drawable="@android:drawable/scrubber_control_pressed_holo" />
+ <item android:state_selected="true" android:drawable="@android:drawable/scrubber_control_focused_holo" />
+ <item android:drawable="@android:drawable/scrubber_control_normal_holo" />
</selector>
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index a5ba57d..3de75ba 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -1020,6 +1020,8 @@
return true;
}
}
+ *outVx = 0;
+ *outVy = 0;
return false;
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index cd8bb1d..a0881a7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -18,8 +18,10 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.os.Binder;
@@ -1684,17 +1686,42 @@
* Register a component to be the sole receiver of MEDIA_BUTTON intents.
* @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
* that will receive the media button intent. This broadcast receiver must be declared
- * in the application manifest.
+ * in the application manifest. The package of the component must match that of
+ * the context you're registering from.
*/
public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
if (eventReceiver == null) {
return;
}
+ if (!eventReceiver.getPackageName().equals(mContext.getPackageName())) {
+ Log.e(TAG, "registerMediaButtonEventReceiver() error: " +
+ "receiver and context package names don't match");
+ return;
+ }
+ // construct a PendingIntent for the media button and register it
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ // the associated intent will be handled by the component being registered
+ mediaButtonIntent.setComponent(eventReceiver);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+ registerMediaButtonIntent(pi, eventReceiver);
+ }
+
+ /**
+ * @hide
+ * no-op if (pi == null) or (eventReceiver == null)
+ */
+ public void registerMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
+ if ((pi == null) || (eventReceiver == null)) {
+ Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
+ return;
+ }
IAudioService service = getService();
try {
- service.registerMediaButtonEventReceiver(eventReceiver);
+ // pi != null
+ service.registerMediaButtonIntent(pi, eventReceiver);
} catch (RemoteException e) {
- Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e);
+ Log.e(TAG, "Dead object in registerMediaButtonIntent"+e);
}
}
@@ -1707,14 +1734,26 @@
if (eventReceiver == null) {
return;
}
- IAudioService service = getService();
- try {
- service.unregisterMediaButtonEventReceiver(eventReceiver);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e);
- }
+ // construct a PendingIntent for the media button and unregister it
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ // the associated intent will be handled by the component being registered
+ mediaButtonIntent.setComponent(eventReceiver);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+ unregisterMediaButtonIntent(pi, eventReceiver);
}
+ /**
+ * @hide
+ */
+ public void unregisterMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
+ IAudioService service = getService();
+ try {
+ service.unregisterMediaButtonIntent(pi, eventReceiver);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e);
+ }
+ }
/**
* Registers the remote control client for providing information to display on the remote
@@ -1724,14 +1763,13 @@
* @see RemoteControlClient
*/
public void registerRemoteControlClient(RemoteControlClient rcClient) {
- if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
+ if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
return;
}
IAudioService service = getService();
try {
- service.registerRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
+ service.registerRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent */
rcClient.getIRemoteControlClient(), /* rcClient */
- rcClient.toString(), /* clientName */
// used to match media button event receiver and audio focus
mContext.getPackageName()); /* packageName */
} catch (RemoteException e) {
@@ -1746,13 +1784,13 @@
* @see #registerRemoteControlClient(RemoteControlClient)
*/
public void unregisterRemoteControlClient(RemoteControlClient rcClient) {
- if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
+ if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
return;
}
IAudioService service = getService();
try {
- service.unregisterRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
- rcClient.getIRemoteControlClient()); /* rcClient */
+ service.unregisterRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent */
+ rcClient.getIRemoteControlClient()); /* rcClient */
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e);
}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index db27cfd..8895c9e 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -18,6 +18,8 @@
import android.app.ActivityManagerNative;
import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
@@ -35,6 +37,7 @@
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
@@ -2084,7 +2087,7 @@
}
}
- private void persistMediaButtonReceiver(ComponentName receiver) {
+ private void onHandlePersistMediaButtonReceiver(ComponentName receiver) {
Settings.System.putString(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER,
receiver == null ? "" : receiver.flattenToString());
}
@@ -2201,7 +2204,7 @@
break;
case MSG_PERSIST_MEDIABUTTONRECEIVER:
- persistMediaButtonReceiver( (ComponentName) msg.obj );
+ onHandlePersistMediaButtonReceiver( (ComponentName) msg.obj );
break;
case MSG_RCDISPLAY_CLEAR:
@@ -2894,14 +2897,22 @@
}
synchronized(mRCStack) {
if (!mRCStack.empty()) {
- // create a new intent specifically aimed at the current registered listener
+ // create a new intent to fill in the extras of the registered PendingIntent
Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- targetedIntent.putExtras(intent.getExtras());
- targetedIntent.setComponent(mRCStack.peek().mReceiverComponent);
- // trap the current broadcast
- abortBroadcast();
- //Log.v(TAG, " Sending intent" + targetedIntent);
- context.sendBroadcast(targetedIntent, null);
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ targetedIntent.putExtras(extras);
+ // trap the current broadcast
+ abortBroadcast();
+ //Log.v(TAG, " Sending intent" + targetedIntent);
+ // send the intent that was registered by the client
+ try {
+ mRCStack.peek().mMediaIntent.send(context, 0, targetedIntent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending pending intent " + mRCStack.peek());
+ e.printStackTrace();
+ }
+ }
}
}
}
@@ -2936,18 +2947,18 @@
*/
private class RcClientDeathHandler implements IBinder.DeathRecipient {
private IBinder mCb; // To be notified of client's death
- private ComponentName mRcEventReceiver;
+ private PendingIntent mMediaIntent;
- RcClientDeathHandler(IBinder cb, ComponentName eventReceiver) {
+ RcClientDeathHandler(IBinder cb, PendingIntent pi) {
mCb = cb;
- mRcEventReceiver = eventReceiver;
+ mMediaIntent = pi;
}
public void binderDied() {
Log.w(TAG, " RemoteControlClient died");
// remote control client died, make sure the displays don't use it anymore
// by setting its remote control client to null
- registerRemoteControlClient(mRcEventReceiver, null, null, null/*ignored*/);
+ registerRemoteControlClient(mMediaIntent, null, null/*ignored*/);
}
public IBinder getBinder() {
@@ -2956,18 +2967,29 @@
}
private static class RemoteControlStackEntry {
- /** the target for the ACTION_MEDIA_BUTTON events */
- public ComponentName mReceiverComponent;// always non null
+ /**
+ * The target for the ACTION_MEDIA_BUTTON events.
+ * Always non null.
+ */
+ public PendingIntent mMediaIntent;
+ /**
+ * The registered media button event receiver.
+ * Always non null.
+ */
+ public ComponentName mReceiverComponent;
public String mCallingPackageName;
- public String mRcClientName;
public int mCallingUid;
-
- /** provides access to the information to display on the remote control */
+ /**
+ * Provides access to the information to display on the remote control.
+ * May be null (when a media button event receiver is registered,
+ * but no remote control client has been registered) */
public IRemoteControlClient mRcClient;
public RcClientDeathHandler mRcClientDeathHandler;
- public RemoteControlStackEntry(ComponentName r) {
- mReceiverComponent = r;
+ /** precondition: mediaIntent != null, eventReceiver != null */
+ public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) {
+ mMediaIntent = mediaIntent;
+ mReceiverComponent = eventReceiver;
mCallingUid = -1;
mRcClient = null;
}
@@ -3003,7 +3025,8 @@
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = stackIterator.next();
- pw.println(" receiver: " + rcse.mReceiverComponent +
+ pw.println(" pi: " + rcse.mMediaIntent +
+ " -- ercvr: " + rcse.mReceiverComponent +
" -- client: " + rcse.mRcClient +
" -- uid: " + rcse.mCallingUid);
}
@@ -3035,7 +3058,6 @@
mAudioHandler.sendMessage(
mAudioHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0,
null));
- return;
} else if (oldTop != mRCStack.peek()) {
// the top of the stack has changed, save it in the system settings
// by posting a message to persist it
@@ -3049,25 +3071,32 @@
/**
* Helper function:
- * Restore remote control receiver from the system settings
+ * Restore remote control receiver from the system settings.
*/
private void restoreMediaButtonReceiver() {
String receiverName = Settings.System.getString(mContentResolver,
Settings.System.MEDIA_BUTTON_RECEIVER);
if ((null != receiverName) && !receiverName.isEmpty()) {
- ComponentName receiverComponentName = ComponentName.unflattenFromString(receiverName);
- registerMediaButtonEventReceiver(receiverComponentName);
+ ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName);
+ // construct a PendingIntent targeted to the restored component name
+ // for the media button and register it
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ // the associated intent will be handled by the component being registered
+ mediaButtonIntent.setComponent(eventReceiver);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+ registerMediaButtonIntent(pi, eventReceiver);
}
- // upon restoring (e.g. after boot), do we want to refresh all remotes?
}
/**
* Helper function:
- * Set the new remote control receiver at the top of the RC focus stack
+ * Set the new remote control receiver at the top of the RC focus stack.
+ * precondition: mediaIntent != null, target != null
*/
- private void pushMediaButtonReceiver(ComponentName newReceiver) {
+ private void pushMediaButtonReceiver(PendingIntent mediaIntent, ComponentName target) {
// already at top of stack?
- if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) {
+ if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) {
return;
}
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
@@ -3075,31 +3104,32 @@
boolean wasInsideStack = false;
while(stackIterator.hasNext()) {
rcse = (RemoteControlStackEntry)stackIterator.next();
- if(rcse.mReceiverComponent.equals(newReceiver)) {
+ if(rcse.mMediaIntent.equals(mediaIntent)) {
wasInsideStack = true;
stackIterator.remove();
break;
}
}
if (!wasInsideStack) {
- rcse = new RemoteControlStackEntry(newReceiver);
+ rcse = new RemoteControlStackEntry(mediaIntent, target);
}
mRCStack.push(rcse);
// post message to persist the default media button receiver
mAudioHandler.sendMessage( mAudioHandler.obtainMessage(
- MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, newReceiver/*obj*/) );
+ MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
}
/**
* Helper function:
- * Remove the remote control receiver from the RC focus stack
+ * Remove the remote control receiver from the RC focus stack.
+ * precondition: pi != null
*/
- private void removeMediaButtonReceiver(ComponentName newReceiver) {
+ private void removeMediaButtonReceiver(PendingIntent pi) {
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
- if(rcse.mReceiverComponent.equals(newReceiver)) {
+ if(rcse.mMediaIntent.equals(pi)) {
stackIterator.remove();
break;
}
@@ -3110,8 +3140,8 @@
* Helper function:
* Called synchronized on mRCStack
*/
- private boolean isCurrentRcController(ComponentName eventReceiver) {
- if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(eventReceiver)) {
+ private boolean isCurrentRcController(PendingIntent pi) {
+ if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) {
return true;
}
return false;
@@ -3124,12 +3154,12 @@
* Update the remote control displays with the new "focused" client generation
*/
private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
- ComponentName newClientEventReceiver, boolean clearing) {
+ PendingIntent newMediaIntent, boolean clearing) {
// NOTE: Only one IRemoteControlDisplay supported in this implementation
if (mRcDisplay != null) {
try {
mRcDisplay.setCurrentClientId(
- newClientGeneration, newClientEventReceiver, clearing);
+ newClientGeneration, newMediaIntent, clearing);
} catch (RemoteException e) {
Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc() "+e);
// if we had a display before, stop monitoring its death
@@ -3167,10 +3197,9 @@
* where the display should be cleared.
*/
private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
- ComponentName newClientEventReceiver, boolean clearing) {
+ PendingIntent newMediaIntent, boolean clearing) {
// send the new valid client generation ID to all displays
- setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newClientEventReceiver,
- clearing);
+ setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
// send the new valid client generation ID to all clients
setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
}
@@ -3186,7 +3215,7 @@
mCurrentRcClientGen++;
// synchronously update the displays and clients with the new client generation
setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- null /*event receiver*/, true /*clearing*/);
+ null /*newMediaIntent*/, true /*clearing*/);
}
}
}
@@ -3204,7 +3233,7 @@
// synchronously update the displays and clients with
// the new client generation
setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- rcse.mReceiverComponent /*event receiver*/,
+ rcse.mMediaIntent /*newMediaIntent*/,
false /*clearing*/);
// tell the current client that it needs to send info
@@ -3301,27 +3330,34 @@
updateRemoteControlDisplay_syncAfRcs(infoChangedFlags);
}
- /** see AudioManager.registerMediaButtonEventReceiver(ComponentName eventReceiver) */
- public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
- Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver);
+ /**
+ * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
+ * precondition: mediaIntent != null, target != null
+ */
+ public void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) {
+ Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent);
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
- pushMediaButtonReceiver(eventReceiver);
+ pushMediaButtonReceiver(mediaIntent, eventReceiver);
// new RC client, assume every type of information shall be queried
checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
}
- /** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */
- public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
- Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver);
+ /**
+ * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
+ * precondition: mediaIntent != null, eventReceiver != null
+ */
+ public void unregisterMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver)
+ {
+ Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent);
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
- boolean topOfStackWillChange = isCurrentRcController(eventReceiver);
- removeMediaButtonReceiver(eventReceiver);
+ boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
+ removeMediaButtonReceiver(mediaIntent);
if (topOfStackWillChange) {
// current RC client will change, assume every type of info needs to be queried
checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
@@ -3331,8 +3367,8 @@
}
/** see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) */
- public void registerRemoteControlClient(ComponentName eventReceiver,
- IRemoteControlClient rcClient, String clientName, String callingPackageName) {
+ public void registerRemoteControlClient(PendingIntent mediaIntent,
+ IRemoteControlClient rcClient, String callingPackageName) {
if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
@@ -3340,7 +3376,7 @@
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = stackIterator.next();
- if(rcse.mReceiverComponent.equals(eventReceiver)) {
+ if(rcse.mMediaIntent.equals(mediaIntent)) {
// already had a remote control client?
if (rcse.mRcClientDeathHandler != null) {
// stop monitoring the old client's death
@@ -3357,7 +3393,6 @@
}
}
rcse.mCallingPackageName = callingPackageName;
- rcse.mRcClientName = clientName;
rcse.mCallingUid = Binder.getCallingUid();
if (rcClient == null) {
rcse.mRcClientDeathHandler = null;
@@ -3366,7 +3401,7 @@
// monitor the new client's death
IBinder b = rcClient.asBinder();
RcClientDeathHandler rcdh =
- new RcClientDeathHandler(b, rcse.mReceiverComponent);
+ new RcClientDeathHandler(b, rcse.mMediaIntent);
try {
b.linkToDeath(rcdh, 0);
} catch (RemoteException e) {
@@ -3380,7 +3415,7 @@
}
// if the eventReceiver is at the top of the stack
// then check for potential refresh of the remote controls
- if (isCurrentRcController(eventReceiver)) {
+ if (isCurrentRcController(mediaIntent)) {
checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
@@ -3388,24 +3423,23 @@
}
/**
- * see AudioManager.unregisterRemoteControlClient(ComponentName eventReceiver, ...)
+ * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
* rcClient is guaranteed non-null
*/
- public void unregisterRemoteControlClient(ComponentName eventReceiver,
+ public void unregisterRemoteControlClient(PendingIntent mediaIntent,
IRemoteControlClient rcClient) {
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = stackIterator.next();
- if ((rcse.mReceiverComponent.equals(eventReceiver))
+ if ((rcse.mMediaIntent.equals(mediaIntent))
&& rcClient.equals(rcse.mRcClient)) {
// we found the IRemoteControlClient to unregister
// stop monitoring its death
rcse.unlinkToRcClientDeath();
// reset the client-related fields
rcse.mRcClient = null;
- rcse.mRcClientName = null;
rcse.mRcClientDeathHandler = null;
rcse.mCallingPackageName = null;
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7bf9814..5294d36 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -16,6 +16,7 @@
package android.media;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.media.IAudioFocusDispatcher;
import android.media.IRemoteControlClient;
@@ -85,13 +86,12 @@
void unregisterAudioFocusClient(String clientId);
- void registerMediaButtonEventReceiver(in ComponentName eventReceiver);
+ oneway void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
+ oneway void unregisterMediaButtonIntent(in PendingIntent pi, in ComponentName c);
- void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver);
-
- oneway void registerRemoteControlClient(in ComponentName eventReceiver,
- in IRemoteControlClient rcClient, in String clientName, in String callingPackageName);
- oneway void unregisterRemoteControlClient(in ComponentName eventReceiver,
+ oneway void registerRemoteControlClient(in PendingIntent mediaIntent,
+ in IRemoteControlClient rcClient, in String callingPackageName);
+ oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
in IRemoteControlClient rcClient);
oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd);
diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl
index fd50b7e..e15b07c 100644
--- a/media/java/android/media/IRemoteControlDisplay.aidl
+++ b/media/java/android/media/IRemoteControlDisplay.aidl
@@ -16,6 +16,7 @@
package android.media;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.graphics.Bitmap;
import android.os.Bundle;
@@ -31,14 +32,12 @@
/**
* Sets the generation counter of the current client that is displayed on the remote control.
* @param clientGeneration the new RemoteControlClient generation
- * @param clientEventReceiver the media button event receiver associated with the client.
- * May be null, which implies there is no registered media button event receiver. This
- * parameter is supplied as an optimization so a display can directly target media button
- * events to the client.
+ * @param clientMediaIntent the PendingIntent associated with the client.
+ * May be null, which implies there is no registered media button event receiver.
* @param clearing true if the new client generation value maps to a remote control update
* where the display should be cleared.
*/
- void setCurrentClientId(int clientGeneration, in ComponentName clientEventReceiver,
+ void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent,
boolean clearing);
void setPlaybackState(int generationId, int state);
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index cdebba0..5dea87f 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -34,6 +34,7 @@
import java.lang.IllegalArgumentException;
/**
+ * TODO javadoc update for ComponentName - PendingIntent change
* RemoteControlClient enables exposing information meant to be consumed by remote controls
* capable of displaying metadata, artwork and media transport control buttons.
* A remote control client object is associated with a media button event receiver. This
@@ -205,50 +206,6 @@
public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
/**
- * @hide
- * TODO remove after modifying known (internal) media apps using this API
- * Class constructor.
- * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have
- * been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
- * before this new RemoteControlClient can itself be registered with
- * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
- * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
- * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
- */
- public RemoteControlClient(ComponentName mediaButtonEventReceiver) {
- mRcEventReceiver = mediaButtonEventReceiver;
-
- Looper looper;
- if ((looper = Looper.myLooper()) != null) {
- mEventHandler = new EventHandler(this, looper);
- } else if ((looper = Looper.getMainLooper()) != null) {
- mEventHandler = new EventHandler(this, looper);
- } else {
- mEventHandler = null;
- Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
- }
- }
-
- /**
- * @hide
- * TODO remove after modifying known (internal) media apps using this API
- * Class constructor for a remote control client whose internal event handling
- * happens on a user-provided Looper.
- * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have
- * been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
- * before this new RemoteControlClient can itself be registered with
- * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
- * @param looper The Looper running the event loop.
- * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
- * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
- */
- public RemoteControlClient(ComponentName mediaButtonEventReceiver, Looper looper) {
- mRcEventReceiver = mediaButtonEventReceiver;
-
- mEventHandler = new EventHandler(this, looper);
- }
-
- /**
* Class constructor.
* @param mediaButtonIntent The intent that will be sent for the media button events sent
* by remote controls.
@@ -262,8 +219,7 @@
* @see AudioManager#registerRemoteControlClient(RemoteControlClient)
*/
public RemoteControlClient(PendingIntent mediaButtonIntent) {
- // TODO implement using PendingIntent instead of ComponentName
- mRcEventReceiver = null;
+ mRcMediaIntent = mediaButtonIntent;
Looper looper;
if ((looper = Looper.myLooper()) != null) {
@@ -292,8 +248,7 @@
* @see AudioManager#registerRemoteControlClient(RemoteControlClient)
*/
public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
- // TODO implement using PendingIntent instead of ComponentName
- mRcEventReceiver = null;
+ mRcMediaIntent = mediaButtonIntent;
mEventHandler = new EventHandler(this, looper);
}
@@ -614,9 +569,10 @@
private int mInternalClientGenId = -2;
/**
- * The media button event receiver associated with this remote control client
+ * The media button intent description associated with this remote control client
+ * (can / should include target component for intent handling)
*/
- private final ComponentName mRcEventReceiver;
+ private final PendingIntent mRcMediaIntent;
/**
* The remote control display to which this client will send information.
@@ -626,10 +582,10 @@
/**
* @hide
- * Accessor to media button event receiver
+ * Accessor to media button intent description (includes target component)
*/
- public ComponentName getRcEventReceiver() {
- return mRcEventReceiver;
+ public PendingIntent getRcMediaIntent() {
+ return mRcMediaIntent;
}
/**
* @hide
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
index f84cc19..2fe22ff 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
@@ -69,6 +69,12 @@
</FrameLayout>
+ <include layout="@layout/status_bar_no_recent_apps"
+ android:id="@+id/recents_no_apps"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+
<View android:id="@+id/recents_dismiss_button"
android:layout_width="80px"
android:layout_height="@*android:dimen/status_bar_height"
diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
index ed9ea7a..4d49077 100644
--- a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
@@ -67,6 +67,12 @@
</FrameLayout>
+ <include layout="@layout/status_bar_no_recent_apps"
+ android:id="@+id/recents_no_apps"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+
<View android:id="@+id/recents_dismiss_button"
android:layout_width="80px"
android:layout_height="@*android:dimen/status_bar_height"
diff --git a/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml b/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
new file mode 100644
index 0000000..47ffb83
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="24dp"
+ android:textColor="#ffffffff"
+ android:text="@string/status_bar_no_recent_apps"
+ android:gravity="center_horizontal"
+ android:layout_gravity="center"
+ />
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bad7e1f..b9e6d78 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -41,6 +41,10 @@
<!-- Title shown in recents popup for inspecting an application's properties -->
<string name="status_bar_recent_inspect_item_title">App info</string>
+ <!-- Message shown in the middle of the screen after clicking on the recent apps button
+ when there are no recent apps to show [CHAR LIMIT=45]-->
+ <string name="status_bar_no_recent_apps">No recent apps</string>
+
<!-- The label in the bar at the top of the status bar when there are no notifications
showing. [CHAR LIMIT=40]-->
<string name="status_bar_no_notifications_title">No notifications</string>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
index 9749a1d..fbf00d2 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
@@ -38,17 +38,20 @@
View mRootView;
View mScrimView;
View mContentView;
+ View mNoRecentAppsView;
AnimatorSet mContentAnim;
Animator.AnimatorListener mListener;
// the panel will start to appear this many px from the end
final int HYPERSPACE_OFFRAMP = 200;
- public Choreographer(View root, View scrim, View content, Animator.AnimatorListener listener) {
+ public Choreographer(View root, View scrim, View content,
+ View noRecentApps, Animator.AnimatorListener listener) {
mRootView = root;
mScrimView = scrim;
mContentView = content;
mListener = listener;
+ mNoRecentAppsView = noRecentApps;
}
void createAnimation(boolean appearing) {
@@ -81,8 +84,24 @@
: new android.view.animation.DecelerateInterpolator(1.0f));
glowAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
+ Animator noRecentAppsFadeAnim = null;
+ if (mNoRecentAppsView != null && // doesn't exist on large devices
+ mNoRecentAppsView.getVisibility() == View.VISIBLE) {
+ noRecentAppsFadeAnim = ObjectAnimator.ofFloat(mNoRecentAppsView, "alpha",
+ mContentView.getAlpha(), appearing ? 1.0f : 0.0f);
+ noRecentAppsFadeAnim.setInterpolator(appearing
+ ? new android.view.animation.AccelerateInterpolator(1.0f)
+ : new android.view.animation.DecelerateInterpolator(1.0f));
+ noRecentAppsFadeAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
+ }
+
mContentAnim = new AnimatorSet();
final Builder builder = mContentAnim.play(glowAnim).with(posAnim);
+
+ if (noRecentAppsFadeAnim != null) {
+ builder.with(noRecentAppsFadeAnim);
+ }
+
Drawable background = mScrimView.getBackground();
if (background != null) {
Animator bgAnim = ObjectAnimator.ofInt(background,
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 0621b22..6fdc534 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -83,6 +83,7 @@
private int mIconDpi;
private View mRecentsScrim;
private View mRecentsGlowView;
+ private View mRecentsNoApps;
private ViewGroup mRecentsContainer;
private Bitmap mDefaultThumbnailBackground;
@@ -373,8 +374,9 @@
mRecentsGlowView = findViewById(R.id.recents_glow);
- mRecentsScrim = (View) findViewById(R.id.recents_bg_protect);
- mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this);
+ mRecentsScrim = findViewById(R.id.recents_bg_protect);
+ mRecentsNoApps = findViewById(R.id.recents_no_apps);
+ mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, mRecentsNoApps, this);
mRecentsDismissButton = findViewById(R.id.recents_dismiss_button);
mRecentsDismissButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
@@ -581,6 +583,9 @@
mThumbnailLoader.cancel(false);
mThumbnailLoader = null;
}
+ if (mRecentsNoApps != null) { // doesn't exist on large devices
+ mRecentsNoApps.setVisibility(View.INVISIBLE);
+ }
mActivityDescriptions = getRecentTasks();
for (ActivityDescription ad : mActivityDescriptions) {
ad.setThumbnail(mDefaultThumbnailBackground);
@@ -647,7 +652,11 @@
} else {
// Immediately hide this panel
if (DEBUG) Log.v(TAG, "Nothing to show");
- hide(false);
+ if (mRecentsNoApps != null) { // doesn't exist on large devices
+ mRecentsNoApps.setVisibility(View.VISIBLE);
+ } else {
+ hide(false);
+ }
}
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 2348d76..e0a2adc 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -2394,6 +2394,12 @@
return mTethering.getTetheredIfaces();
}
+ @Override
+ public String[] getTetheredIfacePairs() {
+ enforceTetherAccessPermission();
+ return mTethering.getTetheredIfacePairs();
+ }
+
public String[] getTetheringErroredIfaces() {
enforceTetherAccessPermission();
return mTethering.getErroredIfaces();
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index f0b5958..99f6b8e 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -142,5 +142,5 @@
# ---------------------------
# NetworkStatsService.java
# ---------------------------
-51100 netstats_mobile_sample (iface_rx|2|2),(iface_tx|2|2),(uid_rx|2|2),(uid_tx|2|2)
-51101 netstats_wifi_sample (iface_rx|2|2),(iface_tx|2|2),(uid_rx|2|2),(uid_tx|2|2)
+51100 netstats_mobile_sample (iface_rx_bytes|2|2),(iface_tx_bytes|2|2),(iface_rx_pkts|2|1),(iface_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1)
+51101 netstats_wifi_sample (iface_rx_bytes|2|2),(iface_tx_bytes|2|2),(iface_rx_pkts|2|1),(iface_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1)
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index c517965..0cffb15 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -123,6 +123,8 @@
public static final int InterfaceTxCounterResult = 217;
public static final int InterfaceRxThrottleResult = 218;
public static final int InterfaceTxThrottleResult = 219;
+ public static final int QuotaCounterResult = 220;
+ public static final int TetheringStatsResult = 221;
public static final int InterfaceChange = 600;
public static final int BandwidthControl = 601;
@@ -1443,6 +1445,73 @@
return stats;
}
+ @Override
+ public NetworkStats getNetworkStatsTethering(String[] ifacePairs) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+
+ if (ifacePairs.length % 2 != 0) {
+ throw new IllegalArgumentException(
+ "unexpected ifacePairs; length=" + ifacePairs.length);
+ }
+
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
+ for (int i = 0; i < ifacePairs.length; i += 2) {
+ final String ifaceIn = ifacePairs[i];
+ final String ifaceOut = ifacePairs[i + 1];
+ if (ifaceIn != null && ifaceOut != null) {
+ stats.combineValues(getNetworkStatsTethering(ifaceIn, ifaceOut));
+ }
+ }
+ return stats;
+ }
+
+ private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) {
+ final StringBuilder command = new StringBuilder();
+ command.append("bandwidth gettetherstats ").append(ifaceIn).append(" ").append(ifaceOut);
+
+ final String rsp;
+ try {
+ rsp = mConnector.doCommand(command.toString()).get(0);
+ } catch (NativeDaemonConnectorException e) {
+ throw new IllegalStateException("Error communicating to native daemon", e);
+ }
+
+ final String[] tok = rsp.split(" ");
+ /* Expecting: "code ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets" */
+ if (tok.length != 7) {
+ throw new IllegalStateException("Native daemon returned unexpected result: " + rsp);
+ }
+
+ final int code;
+ try {
+ code = Integer.parseInt(tok[0]);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(
+ "Failed to parse native daemon return code for " + ifaceIn + " " + ifaceOut);
+ }
+ if (code != NetdResponseCode.TetheringStatsResult) {
+ throw new IllegalStateException(
+ "Unexpected return code from native daemon for " + ifaceIn + " " + ifaceOut);
+ }
+
+ try {
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.iface = ifaceIn;
+ entry.uid = UID_ALL;
+ entry.set = SET_DEFAULT;
+ entry.tag = TAG_NONE;
+ entry.rxBytes = Long.parseLong(tok[3]);
+ entry.rxPackets = Long.parseLong(tok[4]);
+ entry.txBytes = Long.parseLong(tok[5]);
+ entry.txPackets = Long.parseLong(tok[6]);
+ return entry;
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(
+ "problem parsing tethering stats for " + ifaceIn + " " + ifaceOut + ": " + e);
+ }
+ }
+
public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index ae8b89d..5286824 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -19,7 +19,6 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.bluetooth.BluetoothPan;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -28,15 +27,14 @@
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
-import android.net.InterfaceConfiguration;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
+import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkUtils;
import android.os.Binder;
-import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService;
@@ -51,6 +49,7 @@
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.google.android.collect.Lists;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -59,8 +58,8 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.Set;
+
/**
* @hide
*
@@ -68,7 +67,6 @@
*
* TODO - look for parent classes and code sharing
*/
-
public class Tethering extends INetworkManagementEventObserver.Stub {
private Context mContext;
@@ -629,6 +627,19 @@
return retVal;
}
+ public String[] getTetheredIfacePairs() {
+ final ArrayList<String> list = Lists.newArrayList();
+ synchronized (mIfaces) {
+ for (TetherInterfaceSM sm : mIfaces.values()) {
+ if (sm.isTethered()) {
+ list.add(sm.mMyUpstreamIfaceName);
+ list.add(sm.mIfaceName);
+ }
+ }
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
public String[] getTetherableIfaces() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mIfaces) {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index a2b097e..947cf9c 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.SET_DEFAULT;
@@ -34,7 +35,7 @@
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.net.TrafficStats.UID_REMOVED;
-import static android.provider.Settings.Secure.NETSTATS_FORCE_COMPLETE_POLL;
+import static android.net.TrafficStats.UID_TETHERING;
import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
@@ -68,6 +69,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.TrafficStats;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -89,6 +91,7 @@
import com.android.internal.os.AtomicFile;
import com.android.internal.util.Objects;
import com.android.server.EventLogTags;
+import com.android.server.connectivity.Tethering;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
@@ -132,14 +135,10 @@
private static final int MSG_PERFORM_POLL = 0x1;
/** Flags to control detail level of poll event. */
- private static final int FLAG_POLL_NETWORK = 0x1;
- private static final int FLAG_POLL_UID = 0x2;
private static final int FLAG_PERSIST_NETWORK = 0x10;
private static final int FLAG_PERSIST_UID = 0x20;
- private static final int FLAG_FORCE_PERSIST = 0x100;
-
- private static final int FLAG_POLL_ALL = FLAG_POLL_NETWORK | FLAG_POLL_UID;
private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
+ private static final int FLAG_PERSIST_FORCE = 0x100;
private final Context mContext;
private final INetworkManagementService mNetworkManager;
@@ -177,7 +176,6 @@
public long getUidMaxHistory();
public long getTagMaxHistory();
public long getTimeCacheMaxAge();
- public boolean getForceCompletePoll();
}
private final Object mStatsLock = new Object();
@@ -195,6 +193,7 @@
private NetworkStats mLastPollNetworkSnapshot;
private NetworkStats mLastPollUidSnapshot;
private NetworkStats mLastPollOperationsSnapshot;
+ private NetworkStats mLastPollTetherSnapshot;
private NetworkStats mLastPersistNetworkSnapshot;
private NetworkStats mLastPersistUidSnapshot;
@@ -258,6 +257,10 @@
final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE);
mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
+ // watch for tethering changes
+ final IntentFilter tetherFilter = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
+ mContext.registerReceiver(mTetherReceiver, tetherFilter, CONNECTIVITY_INTERNAL, mHandler);
+
// listen for periodic polling events
final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
@@ -519,7 +522,7 @@
@Override
public void forceUpdate() {
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- performPoll(FLAG_POLL_ALL | FLAG_PERSIST_ALL);
+ performPoll(FLAG_PERSIST_ALL);
}
/**
@@ -543,12 +546,24 @@
}
};
+ /**
+ * Receiver that watches for {@link Tethering} to claim interface pairs.
+ */
+ private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // on background handler thread, and verified CONNECTIVITY_INTERNAL
+ // permission above.
+ performPoll(FLAG_PERSIST_NETWORK);
+ }
+ };
+
private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// on background handler thread, and verified UPDATE_DEVICE_STATS
// permission above.
- performPoll(FLAG_POLL_ALL | FLAG_PERSIST_ALL);
+ performPoll(FLAG_PERSIST_ALL);
// verify that we're watching global alert
registerGlobalAlert();
@@ -595,7 +610,7 @@
if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
// kick off background poll to collect network stats; UID stats
// are handled during normal polling interval.
- final int flags = FLAG_POLL_NETWORK | FLAG_PERSIST_NETWORK;
+ final int flags = FLAG_PERSIST_NETWORK;
mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget();
// re-arm global alert for next update
@@ -617,10 +632,9 @@
// isn't perfect, since the kernel may already be counting traffic from
// the updated network.
- // poll both network and UID stats, but only persist network stats,
- // since this codepath should stay fast. UID stats will be persisted
- // during next alarm poll event.
- performPollLocked(FLAG_POLL_ALL | FLAG_PERSIST_NETWORK);
+ // poll, but only persist network stats to keep codepath fast. UID stats
+ // will be persisted during next alarm poll event.
+ performPollLocked(FLAG_PERSIST_NETWORK);
final NetworkState[] states;
try {
@@ -684,19 +698,9 @@
if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
final long startRealtime = SystemClock.elapsedRealtime();
- boolean pollNetwork = (flags & FLAG_POLL_NETWORK) != 0;
- boolean pollUid = (flags & FLAG_POLL_UID) != 0;
-
- // when complete poll requested, any partial poll enables everything
- final boolean forceCompletePoll = mSettings.getForceCompletePoll();
- if (forceCompletePoll && (pollNetwork || pollUid)) {
- pollNetwork = true;
- pollUid = true;
- }
-
final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
- final boolean forcePersist = (flags & FLAG_FORCE_PERSIST) != 0;
+ final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0;
// try refreshing time source when stale
if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
@@ -709,32 +713,36 @@
final long threshold = mSettings.getPersistThreshold();
try {
- if (pollNetwork) {
- final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
- performNetworkPollLocked(networkSnapshot, currentTime);
+ // record network stats
+ final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
+ performNetworkPollLocked(networkSnapshot, currentTime);
- // persist when enough network data has occurred
- final NetworkStats persistNetworkDelta = computeStatsDelta(
- mLastPersistNetworkSnapshot, networkSnapshot, true);
- final boolean pastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
- if (forcePersist || (persistNetwork && pastThreshold)) {
- writeNetworkStatsLocked();
- mLastPersistNetworkSnapshot = networkSnapshot;
- }
+ // persist when enough network data has occurred
+ final NetworkStats persistNetworkDelta = computeStatsDelta(
+ mLastPersistNetworkSnapshot, networkSnapshot, true);
+ final boolean networkPastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
+ if (persistForce || (persistNetwork && networkPastThreshold)) {
+ writeNetworkStatsLocked();
+ mLastPersistNetworkSnapshot = networkSnapshot;
}
- if (pollUid) {
- final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
- performUidPollLocked(uidSnapshot, currentTime);
+ // record tethering stats; persisted during normal UID cycle below
+ final String[] ifacePairs = mConnManager.getTetheredIfacePairs();
+ final NetworkStats tetherSnapshot = mNetworkManager.getNetworkStatsTethering(
+ ifacePairs);
+ performTetherPollLocked(tetherSnapshot, currentTime);
- // persist when enough network data has occurred
- final NetworkStats persistUidDelta = computeStatsDelta(
- mLastPersistUidSnapshot, uidSnapshot, true);
- final boolean pastThreshold = persistUidDelta.getTotalBytes() > threshold;
- if (forcePersist || (persistUid && pastThreshold)) {
- writeUidStatsLocked();
- mLastPersistUidSnapshot = uidSnapshot;
- }
+ // record uid stats
+ final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
+ performUidPollLocked(uidSnapshot, currentTime);
+
+ // persist when enough network data has occurred
+ final NetworkStats persistUidDelta = computeStatsDelta(
+ mLastPersistUidSnapshot, uidSnapshot, true);
+ final boolean uidPastThreshold = persistUidDelta.getTotalBytes() > threshold;
+ if (persistForce || (persistUid && uidPastThreshold)) {
+ writeUidStatsLocked();
+ mLastPersistUidSnapshot = uidSnapshot;
}
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem reading network stats", e);
@@ -748,9 +756,7 @@
}
// sample stats after each full poll
- if (pollNetwork && pollUid) {
- performSample();
- }
+ performSample();
// finally, dispatch updated event to any listeners
final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
@@ -780,12 +786,6 @@
history.recordData(timeStart, currentTime, entry);
}
- // trim any history beyond max
- final long maxHistory = mSettings.getNetworkMaxHistory();
- for (NetworkStatsHistory history : mNetworkStats.values()) {
- history.removeBucketsBefore(currentTime - maxHistory);
- }
-
mLastPollNetworkSnapshot = networkSnapshot;
if (LOGD && unknownIface.size() > 0) {
@@ -829,32 +829,54 @@
history.recordData(timeStart, currentTime, entry);
}
- // trim any history beyond max
- final long maxUidHistory = mSettings.getUidMaxHistory();
- final long maxTagHistory = mSettings.getTagMaxHistory();
- for (UidStatsKey key : mUidStats.keySet()) {
- final NetworkStatsHistory history = mUidStats.get(key);
-
- // detailed tags are trimmed sooner than summary in TAG_NONE
- if (key.tag == TAG_NONE) {
- history.removeBucketsBefore(currentTime - maxUidHistory);
- } else {
- history.removeBucketsBefore(currentTime - maxTagHistory);
- }
- }
-
mLastPollUidSnapshot = uidSnapshot;
mLastPollOperationsSnapshot = mOperations;
mOperations = new NetworkStats(0L, 10);
}
/**
+ * Update {@link #mUidStats} historical usage for
+ * {@link TrafficStats#UID_TETHERING} based on tethering statistics.
+ */
+ private void performTetherPollLocked(NetworkStats tetherSnapshot, long currentTime) {
+ ensureUidStatsLoadedLocked();
+
+ final NetworkStats delta = computeStatsDelta(
+ mLastPollTetherSnapshot, tetherSnapshot, false);
+ final long timeStart = currentTime - delta.getElapsedRealtime();
+
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < delta.size(); i++) {
+ entry = delta.getValues(i, entry);
+ final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
+ if (ident == null) {
+ if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
+ || entry.txPackets > 0) {
+ Log.w(TAG, "dropping tether delta from unknown iface: " + entry);
+ }
+ continue;
+ }
+
+ final NetworkStatsHistory history = findOrCreateUidStatsLocked(
+ ident, UID_TETHERING, SET_DEFAULT, TAG_NONE);
+ history.recordData(timeStart, currentTime, entry);
+ }
+
+ // normal UID poll will trim any history beyond max
+ mLastPollTetherSnapshot = tetherSnapshot;
+ }
+
+ /**
* Sample recent statistics summary into {@link EventLog}.
*/
private void performSample() {
- // take sample as total over last 4 hours
- final long end = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
- final long start = end - (4 * HOUR_IN_MILLIS);
+ final long largestBucketSize = Math.max(
+ mSettings.getNetworkBucketDuration(), mSettings.getUidBucketDuration());
+
+ // take sample as atomic buckets
+ final long now = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
+ final long end = now - (now % largestBucketSize) + largestBucketSize;
+ final long start = end - largestBucketSize;
NetworkTemplate template = null;
NetworkStats.Entry ifaceTotal = null;
@@ -864,15 +886,17 @@
template = buildTemplateMobileAll(getActiveSubscriberId(mContext));
ifaceTotal = getSummaryForNetwork(template, start, end).getTotal(ifaceTotal);
uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
- EventLogTags.writeNetstatsMobileSample(
- ifaceTotal.rxBytes, ifaceTotal.txBytes, uidTotal.rxBytes, uidTotal.txBytes);
+ EventLogTags.writeNetstatsMobileSample(ifaceTotal.rxBytes, ifaceTotal.rxPackets,
+ ifaceTotal.txBytes, ifaceTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets,
+ uidTotal.txBytes, uidTotal.rxPackets);
// collect wifi sample
template = buildTemplateWifi();
ifaceTotal = getSummaryForNetwork(template, start, end).getTotal(ifaceTotal);
uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
- EventLogTags.writeNetstatsWifiSample(
- ifaceTotal.rxBytes, ifaceTotal.txBytes, uidTotal.rxBytes, uidTotal.txBytes);
+ EventLogTags.writeNetstatsWifiSample(ifaceTotal.rxBytes, ifaceTotal.rxPackets,
+ ifaceTotal.txBytes, ifaceTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets,
+ uidTotal.txBytes, uidTotal.rxPackets);
}
/**
@@ -1072,6 +1096,15 @@
// TODO: consider duplicating stats and releasing lock while writing
+ // trim any history beyond max
+ if (mTime.hasCache()) {
+ final long currentTime = mTime.currentTimeMillis();
+ final long maxHistory = mSettings.getNetworkMaxHistory();
+ for (NetworkStatsHistory history : mNetworkStats.values()) {
+ history.removeBucketsBefore(currentTime - maxHistory);
+ }
+ }
+
FileOutputStream fos = null;
try {
fos = mNetworkFile.startWrite();
@@ -1107,6 +1140,23 @@
// TODO: consider duplicating stats and releasing lock while writing
+ // trim any history beyond max
+ if (mTime.hasCache()) {
+ final long currentTime = mTime.currentTimeMillis();
+ final long maxUidHistory = mSettings.getUidMaxHistory();
+ final long maxTagHistory = mSettings.getTagMaxHistory();
+ for (UidStatsKey key : mUidStats.keySet()) {
+ final NetworkStatsHistory history = mUidStats.get(key);
+
+ // detailed tags are trimmed sooner than summary in TAG_NONE
+ if (key.tag == TAG_NONE) {
+ history.removeBucketsBefore(currentTime - maxUidHistory);
+ } else {
+ history.removeBucketsBefore(currentTime - maxTagHistory);
+ }
+ }
+ }
+
// build UidStatsKey lists grouped by ident
final HashMap<NetworkIdentitySet, ArrayList<UidStatsKey>> keysByIdent = Maps.newHashMap();
for (UidStatsKey key : mUidStats.keySet()) {
@@ -1171,7 +1221,7 @@
}
if (argSet.contains("poll")) {
- performPollLocked(FLAG_POLL_ALL | FLAG_PERSIST_ALL | FLAG_FORCE_PERSIST);
+ performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
pw.println("Forced poll");
return;
}
@@ -1399,8 +1449,5 @@
public long getTimeCacheMaxAge() {
return DAY_IN_MILLIS;
}
- public boolean getForceCompletePoll() {
- return getSecureBoolean(NETSTATS_FORCE_COMPLETE_POLL, false);
- }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 2b1eea1..99ae027 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -272,7 +272,11 @@
// graceful shutdown system, which should trigger persist of stats, and
// clear any values in memory.
+ expectCurrentTime();
+ expectDefaultSettings();
+ replay();
mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ verifyAndReset();
// talk with zombie service to assert stats have gone; and assert that
// we persisted them to file.
@@ -487,6 +491,7 @@
// now pretend two UIDs are uninstalled, which should migrate stats to
// special "removed" bucket.
+ expectCurrentTime();
expectDefaultSettings();
replay();
final Intent intent = new Intent(ACTION_UID_REMOVED);
@@ -758,7 +763,6 @@
expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes();
expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
- expect(mSettings.getForceCompletePoll()).andReturn(false).anyTimes();
}
private void expectCurrentTime() throws Exception {
diff --git a/telephony/java/com/android/internal/telephony/ApnContext.java b/telephony/java/com/android/internal/telephony/ApnContext.java
index a52f1ca..8aeee87 100644
--- a/telephony/java/com/android/internal/telephony/ApnContext.java
+++ b/telephony/java/com/android/internal/telephony/ApnContext.java
@@ -154,6 +154,12 @@
return mState;
}
+ public boolean isDisconnected() {
+ DataConnectionTracker.State currentState = getState();
+ return ((currentState == DataConnectionTracker.State.IDLE) ||
+ currentState == DataConnectionTracker.State.FAILED);
+ }
+
public synchronized void setReason(String reason) {
if (DBG) {
log("set reason as " + reason + ", for type " + mApnType + ",current state " + mState);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 6d0b696..dd8a60a 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -787,9 +787,7 @@
}
DataConnectionAc dcac = apnContext.getDataConnectionAc();
if (tearDown) {
- boolean isConnected = (apnContext.getState() != State.IDLE
- && apnContext.getState() != State.FAILED);
- if (!isConnected) {
+ if (apnContext.isDisconnected()) {
// The request is tearDown and but ApnContext is not connected.
// If apnContext is not enabled anymore, break the linkage to the DCAC/DC.
apnContext.setState(State.IDLE);
@@ -1019,11 +1017,9 @@
*/
private void onApnChanged() {
// TODO: How to handle when multiple APNs are active?
- boolean isConnected;
ApnContext defaultApnContext = mApnContexts.get(Phone.APN_TYPE_DEFAULT);
- isConnected = (defaultApnContext.getState() != State.IDLE
- && defaultApnContext.getState() != State.FAILED);
+ boolean defaultApnIsDisconnected = defaultApnContext.isDisconnected();
if (mPhone instanceof GSMPhone) {
// The "current" may no longer be valid. MMS depends on this to send properly. TBD
@@ -1034,8 +1030,8 @@
// match the current operator.
if (DBG) log("onApnChanged: createAllApnList and cleanUpAllConnections");
createAllApnList();
- cleanUpAllConnections(isConnected, Phone.REASON_APN_CHANGED);
- if (!isConnected) {
+ cleanUpAllConnections(!defaultApnIsDisconnected, Phone.REASON_APN_CHANGED);
+ if (defaultApnIsDisconnected) {
setupDataOnReadyApns(Phone.REASON_APN_CHANGED);
}
}
@@ -1885,7 +1881,7 @@
// if all data connection are gone, check whether Airplane mode request was
// pending.
- if (!isConnected()) {
+ if (isDisconnected()) {
if (mPhone.getServiceStateTracker().processPendingRadioPowerOffAfterDataOff()) {
// Radio will be turned off. No need to retry data setup
apnContext.setApnSetting(null);
@@ -1957,12 +1953,25 @@
protected boolean isConnected() {
for (ApnContext apnContext : mApnContexts.values()) {
if (apnContext.getState() == State.CONNECTED) {
- return true;
+ // At least one context is connected, return true
+ return true;
}
}
+ // There are not any contexts connected, return false
return false;
}
+ protected boolean isDisconnected() {
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (!apnContext.isDisconnected()) {
+ // At least one context was not disconnected return false
+ return false;
+ }
+ }
+ // All contexts were disconnected so return true
+ return true;
+ }
+
@Override
protected void notifyDataConnection(String reason) {
if (DBG) log("notifyDataConnection: reason=" + reason);