Merge "Update the max height/width attribute of the TwoTargetPreference icon."
diff --git a/api/current.txt b/api/current.txt
index 02c313c..38bb68b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44133,7 +44133,6 @@
field public static final int KEYBOARD_TAP = 3; // 0x3
field public static final int LONG_PRESS = 0; // 0x0
field public static final int VIRTUAL_KEY = 1; // 0x1
- field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
}
public class InflateException extends java.lang.RuntimeException {
@@ -71737,6 +71736,8 @@
public class JSONException extends java.lang.Exception {
ctor public JSONException(java.lang.String);
+ ctor public JSONException(java.lang.String, java.lang.Throwable);
+ ctor public JSONException(java.lang.Throwable);
}
public class JSONObject {
diff --git a/api/system-current.txt b/api/system-current.txt
index b9a8b20..d98319c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -47685,7 +47685,6 @@
field public static final int KEYBOARD_TAP = 3; // 0x3
field public static final int LONG_PRESS = 0; // 0x0
field public static final int VIRTUAL_KEY = 1; // 0x1
- field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
}
public class InflateException extends java.lang.RuntimeException {
@@ -75662,6 +75661,8 @@
public class JSONException extends java.lang.Exception {
ctor public JSONException(java.lang.String);
+ ctor public JSONException(java.lang.String, java.lang.Throwable);
+ ctor public JSONException(java.lang.Throwable);
}
public class JSONObject {
diff --git a/api/test-current.txt b/api/test-current.txt
index 52f6439..94bfe84 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -44534,7 +44534,6 @@
field public static final int KEYBOARD_TAP = 3; // 0x3
field public static final int LONG_PRESS = 0; // 0x0
field public static final int VIRTUAL_KEY = 1; // 0x1
- field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
}
public class InflateException extends java.lang.RuntimeException {
@@ -72175,6 +72174,8 @@
public class JSONException extends java.lang.Exception {
ctor public JSONException(java.lang.String);
+ ctor public JSONException(java.lang.String, java.lang.Throwable);
+ ctor public JSONException(java.lang.Throwable);
}
public class JSONObject {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 074cdef..cb648d5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -100,6 +100,7 @@
import android.util.EventLog;
import android.util.Log;
import android.util.LogPrinter;
+import android.util.LogWriter;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -125,6 +126,7 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -133,8 +135,6 @@
import dalvik.system.VMDebug;
import dalvik.system.VMRuntime;
-import com.google.android.collect.Lists;
-
import libcore.io.DropBox;
import libcore.io.EventLogger;
import libcore.io.IoUtils;
@@ -152,6 +152,7 @@
import java.net.InetAddress;
import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -3052,7 +3053,7 @@
public void handleInstallProvider(ProviderInfo info) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
- installContentProviders(mInitialApplication, Lists.newArrayList(info));
+ installContentProviders(mInitialApplication, Arrays.asList(info));
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
@@ -3935,6 +3936,14 @@
ActivityManager.getService().activityStopped(
activity.token, state, persistentState, description);
} catch (RemoteException ex) {
+ // Dump statistics about bundle to help developers debug
+ final LogWriter writer = new LogWriter(Log.WARN, TAG);
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println("Bundle stats:");
+ Bundle.dumpStats(pw, state);
+ pw.println("PersistableBundle stats:");
+ Bundle.dumpStats(pw, persistentState);
+
if (ex instanceof TransactionTooLargeException
&& activity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
index 423ee52..ffd38e2 100644
--- a/core/java/android/app/admin/ConnectEvent.java
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -23,7 +23,11 @@
import java.net.UnknownHostException;
/**
- * A class that represents a connect library call event.
+ * A class that represents a TCP connect event initiated through the standard network stack.
+ *
+ * <p>It contains information about the originating app as well as the remote TCP endpoint.
+ *
+ * <p>Support both IPv4 and IPv6 connections.
*/
public final class ConnectEvent extends NetworkEvent implements Parcelable {
diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java
index c21725f..f84c5b0 100644
--- a/core/java/android/app/admin/DnsEvent.java
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -26,7 +26,10 @@
import java.util.List;
/**
- * A class that represents a DNS lookup event.
+ * A class that represents a DNS lookup event initiated through the standard network stack.
+ *
+ * <p>It contains information about the originating app as well as the DNS hostname and resolved
+ * IP addresses.
*/
public final class DnsEvent extends NetworkEvent implements Parcelable {
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index 98bdde8..a6f6be2 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -45,6 +45,18 @@
/** @hide */
public static final int REASON_DEVICE_IDLE = 4;
+ /** @hide */
+ public static String getReasonName(int reason) {
+ switch (reason) {
+ case REASON_CANCELED: return "canceled";
+ case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints";
+ case REASON_PREEMPT: return "preempt";
+ case REASON_TIMEOUT: return "timeout";
+ case REASON_DEVICE_IDLE: return "device_idle";
+ default: return "unknown:" + reason;
+ }
+ }
+
private final int jobId;
private final PersistableBundle extras;
private final Bundle transientExtras;
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 8d85880..a5763ef 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -21,6 +21,9 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.util.IndentingPrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
@@ -1555,4 +1558,49 @@
mParcelledData = p;
}
+
+ /** {@hide} */
+ public static void dumpStats(IndentingPrintWriter pw, String key, Object value) {
+ final Parcel tmp = Parcel.obtain();
+ tmp.writeValue(value);
+ final int size = tmp.dataPosition();
+ tmp.recycle();
+
+ // We only really care about logging large values
+ if (size > 1024) {
+ pw.println(key + " [size=" + size + "]");
+ if (value instanceof BaseBundle) {
+ dumpStats(pw, (BaseBundle) value);
+ } else if (value instanceof SparseArray) {
+ dumpStats(pw, (SparseArray) value);
+ }
+ }
+ }
+
+ /** {@hide} */
+ public static void dumpStats(IndentingPrintWriter pw, SparseArray array) {
+ pw.increaseIndent();
+ if (array == null) {
+ pw.println("[null]");
+ return;
+ }
+ for (int i = 0; i < array.size(); i++) {
+ dumpStats(pw, "0x" + Integer.toHexString(array.keyAt(i)), array.valueAt(i));
+ }
+ pw.decreaseIndent();
+ }
+
+ /** {@hide} */
+ public static void dumpStats(IndentingPrintWriter pw, BaseBundle bundle) {
+ pw.increaseIndent();
+ if (bundle == null) {
+ pw.println("[null]");
+ return;
+ }
+ final ArrayMap<String, Object> map = bundle.getMap();
+ for (int i = 0; i < map.size(); i++) {
+ dumpStats(pw, map.keyAt(i), map.valueAt(i));
+ }
+ pw.decreaseIndent();
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cf4fd99..a34668c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -25,6 +25,7 @@
import java.util.List;
import java.util.Map;
+import android.app.job.JobParameters;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.telephony.SignalStrength;
@@ -238,6 +239,7 @@
private static final String AGGREGATED_WAKELOCK_DATA = "awl";
private static final String SYNC_DATA = "sy";
private static final String JOB_DATA = "jb";
+ private static final String JOB_COMPLETION_DATA = "jbc";
private static final String KERNEL_WAKELOCK_DATA = "kwl";
private static final String WAKEUP_REASON_DATA = "wr";
private static final String NETWORK_DATA = "nt";
@@ -498,6 +500,13 @@
public abstract ArrayMap<String, ? extends Timer> getJobStats();
/**
+ * Returns statistics about how jobs have completed.
+ *
+ * @return A Map of String job names to completion type -> count mapping.
+ */
+ public abstract ArrayMap<String, SparseIntArray> getJobCompletionStats();
+
+ /**
* The statistics associated with a particular wake lock.
*/
public static abstract class Wakelock {
@@ -3557,6 +3566,20 @@
}
}
+ final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+ for (int ic=completions.size()-1; ic>=0; ic--) {
+ SparseIntArray types = completions.valueAt(ic);
+ if (types != null) {
+ dumpLine(pw, uid, category, JOB_COMPLETION_DATA,
+ "\"" + completions.keyAt(ic) + "\"",
+ types.get(JobParameters.REASON_CANCELED, 0),
+ types.get(JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, 0),
+ types.get(JobParameters.REASON_PREEMPT, 0),
+ types.get(JobParameters.REASON_TIMEOUT, 0),
+ types.get(JobParameters.REASON_DEVICE_IDLE, 0));
+ }
+ }
+
dumpTimer(pw, uid, category, FLASHLIGHT_DATA, u.getFlashlightTurnedOnTimer(),
rawRealtime, which);
dumpTimer(pw, uid, category, CAMERA_DATA, u.getCameraTurnedOnTimer(),
@@ -4979,6 +5002,25 @@
uidActivity = true;
}
+ final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+ for (int ic=completions.size()-1; ic>=0; ic--) {
+ SparseIntArray types = completions.valueAt(ic);
+ if (types != null) {
+ pw.print(prefix);
+ pw.print(" Job Completions ");
+ pw.print(completions.keyAt(ic));
+ pw.print(":");
+ for (int it=0; it<types.size(); it++) {
+ pw.print(" ");
+ pw.print(JobParameters.getReasonName(types.keyAt(it)));
+ pw.print("(");
+ pw.print(types.valueAt(it));
+ pw.print("x)");
+ }
+ pw.println();
+ }
+ }
+
uidActivity |= printTimer(pw, sb, u.getFlashlightTurnedOnTimer(), rawRealtime, which,
prefix, "Flashlight");
uidActivity |= printTimer(pw, sb, u.getCameraTurnedOnTimer(), rawRealtime, which,
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 1c676b3..3f5b611 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -266,7 +266,13 @@
* {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor
* indicates that an object is not in close proximity.
*/
- public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1;
+ public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1 << 0;
+
+ /**
+ * Flag for {@link WakeLock#release(int)} when called due to timeout.
+ * @hide
+ */
+ public static final int RELEASE_FLAG_TIMEOUT = 1 << 16;
/**
* Brightness value for fully on.
@@ -1242,7 +1248,8 @@
private String mTag;
private final String mPackageName;
private final IBinder mToken;
- private int mCount;
+ private int mInternalCount;
+ private int mExternalCount;
private boolean mRefCounted = true;
private boolean mHeld;
private WorkSource mWorkSource;
@@ -1251,7 +1258,7 @@
private final Runnable mReleaser = new Runnable() {
public void run() {
- release();
+ release(RELEASE_FLAG_TIMEOUT);
}
};
@@ -1328,7 +1335,9 @@
}
private void acquireLocked() {
- if (!mRefCounted || mCount++ == 0) {
+ mInternalCount++;
+ mExternalCount++;
+ if (!mRefCounted || mInternalCount == 1) {
// Do this even if the wake lock is already thought to be held (mHeld == true)
// because non-reference counted wake locks are not always properly released.
// For example, the keyguard's wake lock might be forcibly released by the
@@ -1373,7 +1382,11 @@
*/
public void release(int flags) {
synchronized (mToken) {
- if (!mRefCounted || --mCount == 0) {
+ mInternalCount--;
+ if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
+ mExternalCount--;
+ }
+ if (!mRefCounted || mInternalCount == 0) {
mHandler.removeCallbacks(mReleaser);
if (mHeld) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
@@ -1385,7 +1398,7 @@
mHeld = false;
}
}
- if (mCount < 0) {
+ if (mRefCounted && mExternalCount < 0) {
throw new RuntimeException("WakeLock under-locked " + mTag);
}
}
@@ -1469,7 +1482,7 @@
synchronized (mToken) {
return "WakeLock{"
+ Integer.toHexString(System.identityHashCode(this))
- + " held=" + mHeld + ", refCount=" + mCount + "}";
+ + " held=" + mHeld + ", refCount=" + mInternalCount + "}";
}
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 0d81bd9..7a13ee8 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -488,8 +488,8 @@
* Instructs the zygote to pre-load the classes and native libraries at the given paths
* for the specified abi. Not all zygotes support this function.
*/
- public void preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
- String abi) throws ZygoteStartFailedEx, IOException {
+ public boolean preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
+ String abi) throws ZygoteStartFailedEx, IOException {
synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
state.writer.write("4");
@@ -508,6 +508,8 @@
state.writer.newLine();
state.writer.flush();
+
+ return (state.inputStream.readInt() == 0);
}
}
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index 0166c4a..81db2b7 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -119,6 +119,23 @@
}
/**
+ * @hide
+ * Remove a range of mappings as a batch.
+ *
+ * @param index Index to begin at
+ * @param size Number of mappings to remove
+ *
+ * <p>For indices outside of the range <code>0...size()-1</code>,
+ * the behavior is undefined.</p>
+ */
+ public void removeAtRange(int index, int size) {
+ size = Math.min(size, mSize - index);
+ System.arraycopy(mKeys, index + size, mKeys, index, mSize - (index + size));
+ System.arraycopy(mValues, index + size, mValues, index, mSize - (index + size));
+ mSize -= size;
+ }
+
+ /**
* Removes the mapping at the given index.
*/
public void removeAt(int index) {
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 0a73949..71a3f7e 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -58,6 +58,7 @@
/**
* The user has released a virtual or software keyboard key.
+ * @hide
*/
public static final int VIRTUAL_KEY_RELEASE = 7;
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 181ad31..33e6521 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -833,25 +833,28 @@
if (!wrapContent) {
centerHorizontal(child, params, myWidth);
} else {
- params.mLeft = mPaddingLeft + params.leftMargin;
- params.mRight = params.mLeft + child.getMeasuredWidth();
+ positionAtEdge(child, params, myWidth);
}
return true;
} else {
// This is the default case. For RTL we start from the right and for LTR we start
// from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.
- if (isLayoutRtl()) {
- params.mRight = myWidth - mPaddingRight- params.rightMargin;
- params.mLeft = params.mRight - child.getMeasuredWidth();
- } else {
- params.mLeft = mPaddingLeft + params.leftMargin;
- params.mRight = params.mLeft + child.getMeasuredWidth();
- }
+ positionAtEdge(child, params, myWidth);
}
}
return rules[ALIGN_PARENT_END] != 0;
}
+ private void positionAtEdge(View child, LayoutParams params, int myWidth) {
+ if (isLayoutRtl()) {
+ params.mRight = myWidth - mPaddingRight - params.rightMargin;
+ params.mLeft = params.mRight - child.getMeasuredWidth();
+ } else {
+ params.mLeft = mPaddingLeft + params.leftMargin;
+ params.mRight = params.mLeft + child.getMeasuredWidth();
+ }
+ }
+
private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
boolean wrapContent) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index ece4981..54afc95 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -69,7 +69,10 @@
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
import android.widget.ListView;
+import android.widget.Space;
+
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity.TargetInfo;
@@ -1412,6 +1415,10 @@
} else {
lp.height = v.getMeasuredHeight();
}
+ if (i != (mColumnCount - 1)) {
+ row.addView(new Space(ChooserActivity.this),
+ new LinearLayout.LayoutParams(0, 0, 1));
+ }
}
// Pre-measure so we can scale later.
@@ -1439,13 +1446,34 @@
if (startType == ChooserListAdapter.TARGET_SERVICE) {
holder.row.setBackgroundColor(
getColor(R.color.chooser_service_row_background_color));
+ int nextStartType = mChooserListAdapter.getPositionTargetType(
+ getFirstRowPosition(rowPosition + 1));
+ int serviceSpacing = holder.row.getContext().getResources()
+ .getDimensionPixelSize(R.dimen.chooser_service_spacing);
+ int top = rowPosition == 0 ? serviceSpacing : 0;
+ if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+ setVertPadding(holder, top, serviceSpacing);
+ } else {
+ setVertPadding(holder, top, 0);
+ }
} else {
holder.row.setBackgroundColor(Color.TRANSPARENT);
+ int lastStartType = mChooserListAdapter.getPositionTargetType(
+ getFirstRowPosition(rowPosition - 1));
+ if (lastStartType == ChooserListAdapter.TARGET_SERVICE || rowPosition == 0) {
+ int serviceSpacing = holder.row.getContext().getResources()
+ .getDimensionPixelSize(R.dimen.chooser_service_spacing);
+ setVertPadding(holder, serviceSpacing, 0);
+ } else {
+ setVertPadding(holder, 0, 0);
+ }
}
final int oldHeight = holder.row.getLayoutParams().height;
+ int measuredRowHeight = holder.measuredRowHeight + holder.row.getPaddingTop()
+ + holder.row.getPaddingBottom();
holder.row.getLayoutParams().height = Math.max(1,
- (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
+ (int) (measuredRowHeight * getRowScale(rowPosition)));
if (holder.row.getLayoutParams().height != oldHeight) {
holder.row.requestLayout();
}
@@ -1457,11 +1485,16 @@
holder.itemIndices[i] = start + i;
mChooserListAdapter.bindView(holder.itemIndices[i], v);
} else {
- v.setVisibility(View.GONE);
+ v.setVisibility(View.INVISIBLE);
}
}
}
+ private void setVertPadding(RowViewHolder holder, int top, int bottom) {
+ holder.row.setPadding(holder.row.getPaddingLeft(), top,
+ holder.row.getPaddingRight(), bottom);
+ }
+
int getFirstRowPosition(int row) {
final int callerCount = mChooserListAdapter.getCallerTargetCount();
final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 04f7c76..09ad266 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -66,7 +66,7 @@
void noteSyncStart(String name, int uid);
void noteSyncFinish(String name, int uid);
void noteJobStart(String name, int uid);
- void noteJobFinish(String name, int uid);
+ void noteJobFinish(String name, int uid, int stopReason);
void noteStartWakelock(int uid, int pid, String name, String historyName,
int type, boolean unimportantForLogging);
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 79544df..ebea1a4 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -27,6 +27,7 @@
import android.database.MatrixCursor.RowBuilder;
import android.graphics.Point;
import android.net.Uri;
+import android.os.Binder;
import android.os.CancellationSignal;
import android.os.FileObserver;
import android.os.FileUtils;
@@ -156,11 +157,17 @@
if (visibleFolder != null) {
assert (visibleFolder.isDirectory());
- final ContentResolver resolver = getContext().getContentResolver();
- final Uri uri = MediaStore.Files.getDirectoryUri("external");
- ContentValues values = new ContentValues();
- values.put(MediaStore.Files.FileColumns.DATA, visibleFolder.getAbsolutePath());
- resolver.insert(uri, values);
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ final ContentResolver resolver = getContext().getContentResolver();
+ final Uri uri = MediaStore.Files.getDirectoryUri("external");
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Files.FileColumns.DATA, visibleFolder.getAbsolutePath());
+ resolver.insert(uri, values);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
}
@@ -214,22 +221,28 @@
// They should be all null or not null at the same time. File#renameTo() doesn't work across
// volumes so an exception will be thrown before calling this method.
if (oldVisibleFile != null && newVisibleFile != null) {
- final ContentResolver resolver = getContext().getContentResolver();
- final Uri externalUri = newVisibleFile.isDirectory()
- ? MediaStore.Files.getDirectoryUri("external")
- : MediaStore.Files.getContentUri("external");
+ final long token = Binder.clearCallingIdentity();
- ContentValues values = new ContentValues();
- values.put(MediaStore.Files.FileColumns.DATA, newVisibleFile.getAbsolutePath());
+ try {
+ final ContentResolver resolver = getContext().getContentResolver();
+ final Uri externalUri = newVisibleFile.isDirectory()
+ ? MediaStore.Files.getDirectoryUri("external")
+ : MediaStore.Files.getContentUri("external");
- // Logic borrowed from MtpDatabase.
- // note - we are relying on a special case in MediaProvider.update() to update
- // the paths for all children in the case where this is a directory.
- final String path = oldVisibleFile.getAbsolutePath();
- resolver.update(externalUri,
- values,
- "_data LIKE ? AND lower(_data)=lower(?)",
- new String[] { path, path });
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Files.FileColumns.DATA, newVisibleFile.getAbsolutePath());
+
+ // Logic borrowed from MtpDatabase.
+ // note - we are relying on a special case in MediaProvider.update() to update
+ // the paths for all children in the case where this is a directory.
+ final String path = oldVisibleFile.getAbsolutePath();
+ resolver.update(externalUri,
+ values,
+ "_data LIKE ? AND lower(_data)=lower(?)",
+ new String[]{path, path});
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
}
@@ -253,23 +266,29 @@
throws FileNotFoundException {
// visibleFolder is null if we're removing a document from external thumb drive or SD card.
if (visibleFile != null) {
- final ContentResolver resolver = getContext().getContentResolver();
- final Uri externalUri = MediaStore.Files.getContentUri("external");
+ final long token = Binder.clearCallingIdentity();
- // Remove media store entries for any files inside this directory, using
- // path prefix match. Logic borrowed from MtpDatabase.
- if (isFolder) {
- final String path = visibleFile.getAbsolutePath() + "/";
+ try {
+ final ContentResolver resolver = getContext().getContentResolver();
+ final Uri externalUri = MediaStore.Files.getContentUri("external");
+
+ // Remove media store entries for any files inside this directory, using
+ // path prefix match. Logic borrowed from MtpDatabase.
+ if (isFolder) {
+ final String path = visibleFile.getAbsolutePath() + "/";
+ resolver.delete(externalUri,
+ "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
+ new String[]{path + "%", Integer.toString(path.length()), path});
+ }
+
+ // Remove media store entry for this exact file.
+ final String path = visibleFile.getAbsolutePath();
resolver.delete(externalUri,
- "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
- new String[] { path + "%", Integer.toString(path.length()), path });
+ "_data LIKE ?1 AND lower(_data)=lower(?2)",
+ new String[]{path, path});
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
-
- // Remove media store entry for this exact file.
- final String path = visibleFile.getAbsolutePath();
- resolver.delete(externalUri,
- "_data LIKE ?1 AND lower(_data)=lower(?2)",
- new String[] { path, path });
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index f3632f0..f085e29 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -840,7 +840,10 @@
if (sipper.shouldHide) {
if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED
&& sipper.drainType != BatterySipper.DrainType.SCREEN
- && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED) {
+ && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED
+ && sipper.drainType != BatterySipper.DrainType.BLUETOOTH
+ && sipper.drainType != BatterySipper.DrainType.WIFI
+ && sipper.drainType != BatterySipper.DrainType.IDLE) {
// Don't add it if it is overcounted, unaccounted or screen
proportionalSmearPowerMah += sipper.totalPowerMah;
}
@@ -861,19 +864,19 @@
* time.
*/
public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
- final long rawRealtimeMs = SystemClock.elapsedRealtime();
long totalActivityTimeMs = 0;
final SparseLongArray activityTimeArray = new SparseLongArray();
for (int i = 0, size = sippers.size(); i < size; i++) {
final BatteryStats.Uid uid = sippers.get(i).uidObj;
if (uid != null) {
- final long timeMs = getForegroundActivityTotalTimeMs(uid, rawRealtimeMs);
+ final long timeMs = getProcessForegroundTimeMs(uid,
+ BatteryStats.STATS_SINCE_CHARGED);
activityTimeArray.put(uid.getUid(), timeMs);
totalActivityTimeMs += timeMs;
}
}
- if (totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
+ if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
final double screenPowerMah = screenSipper.totalPowerMah;
for (int i = 0, size = sippers.size(); i < size; i++) {
final BatterySipper sipper = sippers.get(i);
@@ -936,17 +939,42 @@
return false;
}
+ public long convertUsToMs(long timeUs) {
+ return timeUs / 1000;
+ }
+
+ public long convertMsToUs(long timeMs) {
+ return timeMs * 1000;
+ }
+
@VisibleForTesting
- public long getForegroundActivityTotalTimeMs(BatteryStats.Uid uid, long rawRealtimeMs) {
+ public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
if (timer != null) {
- return timer.getTotalTimeLocked(rawRealtimeMs, BatteryStats.STATS_SINCE_CHARGED);
+ return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
}
return 0;
}
@VisibleForTesting
+ public long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) {
+ final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
+ final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP};
+
+ long timeUs = 0;
+ for (int type : foregroundTypes) {
+ final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which);
+ timeUs += localTime;
+ }
+
+ // Return the min value of STATE_TOP time and foreground activity time, since both of these
+ // time have some errors.
+ return convertUsToMs(
+ Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)));
+ }
+
+ @VisibleForTesting
public void setPackageManager(PackageManager packageManager) {
mPackageManager = packageManager;
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index cb00c5e..fa23e40 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -42,7 +42,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
@@ -75,7 +75,7 @@
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
-import com.android.server.NetworkManagementSocketTagger;
+
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -119,7 +119,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 159 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 160 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -194,6 +194,17 @@
public String getSubsystemLowPowerStats();
}
+ public static abstract class UserInfoProvider {
+ private int[] userIds;
+ protected abstract @Nullable int[] getUserIds();
+ private final void refreshUserIds() {
+ userIds = getUserIds();
+ }
+ private final boolean exists(int userId) {
+ return userIds != null ? ArrayUtils.contains(userIds, userId) : true;
+ }
+ }
+
private final PlatformIdleStateCallback mPlatformIdleStateCallback;
final class MyHandler extends Handler {
@@ -262,6 +273,7 @@
public final MyHandler mHandler;
private ExternalStatsSync mExternalSync = null;
+ private UserInfoProvider mUserInfoProvider = null;
private BatteryCallback mCallback;
@@ -649,6 +661,7 @@
mDailyFile = null;
mHandler = null;
mPlatformIdleStateCallback = null;
+ mUserInfoProvider = null;
clearHistoryLocked();
}
@@ -3664,11 +3677,11 @@
addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_START, name, uid);
}
- public void noteJobFinishLocked(String name, int uid) {
+ public void noteJobFinishLocked(String name, int uid, int stopReason) {
uid = mapUid(uid);
final long elapsedRealtime = mClocks.elapsedRealtime();
final long uptime = mClocks.uptimeMillis();
- getUidStatsLocked(uid).noteStopJobLocked(name, elapsedRealtime);
+ getUidStatsLocked(uid).noteStopJobLocked(name, elapsedRealtime, stopReason);
if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
return;
}
@@ -5702,6 +5715,11 @@
final OverflowArrayMap<DualTimer> mJobStats;
/**
+ * Count of the jobs that have completed and the reasons why they completed.
+ */
+ final ArrayMap<String, SparseIntArray> mJobCompletions = new ArrayMap<>();
+
+ /**
* The statistics we have collected for this uid's sensor activations.
*/
final SparseArray<Sensor> mSensorStats = new SparseArray<>();
@@ -5823,6 +5841,11 @@
}
@Override
+ public ArrayMap<String, SparseIntArray> getJobCompletionStats() {
+ return mJobCompletions;
+ }
+
+ @Override
public SparseArray<? extends BatteryStats.Uid.Sensor> getSensorStats() {
return mSensorStats;
}
@@ -6706,6 +6729,7 @@
}
}
mJobStats.cleanup();
+ mJobCompletions.clear();
for (int ise=mSensorStats.size()-1; ise>=0; ise--) {
Sensor s = mSensorStats.valueAt(ise);
if (s.reset()) {
@@ -6868,6 +6892,21 @@
return !active;
}
+ void writeJobCompletionsToParcelLocked(Parcel out) {
+ int NJC = mJobCompletions.size();
+ out.writeInt(NJC);
+ for (int ijc=0; ijc<NJC; ijc++) {
+ out.writeString(mJobCompletions.keyAt(ijc));
+ SparseIntArray types = mJobCompletions.valueAt(ijc);
+ int NT = types.size();
+ out.writeInt(NT);
+ for (int it=0; it<NT; it++) {
+ out.writeInt(types.keyAt(it));
+ out.writeInt(types.valueAt(it));
+ }
+ }
+ }
+
void writeToParcelLocked(Parcel out, long uptimeUs, long elapsedRealtimeUs) {
mOnBatteryBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
mOnBatteryScreenOffBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
@@ -6899,6 +6938,8 @@
Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
}
+ writeJobCompletionsToParcelLocked(out);
+
int NSE = mSensorStats.size();
out.writeInt(NSE);
for (int ise=0; ise<NSE; ise++) {
@@ -7114,6 +7155,24 @@
}
}
+ void readJobCompletionsFromParcelLocked(Parcel in) {
+ int numJobCompletions = in.readInt();
+ mJobCompletions.clear();
+ for (int j = 0; j < numJobCompletions; j++) {
+ String jobName = in.readString();
+ int numTypes = in.readInt();
+ if (numTypes > 0) {
+ SparseIntArray types = new SparseIntArray();
+ for (int k = 0; k < numTypes; k++) {
+ int type = in.readInt();
+ int count = in.readInt();
+ types.put(type, count);
+ }
+ mJobCompletions.put(jobName, types);
+ }
+ }
+ }
+
void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
mOnBatteryBackgroundTimeBase.readFromParcel(in);
mOnBatteryScreenOffBackgroundTimeBase.readFromParcel(in);
@@ -7148,6 +7207,8 @@
}
}
+ readJobCompletionsFromParcelLocked(in);
+
int numSensors = in.readInt();
mSensorStats.clear();
for (int k = 0; k < numSensors; k++) {
@@ -8460,11 +8521,20 @@
}
}
- public void noteStopJobLocked(String name, long elapsedRealtimeMs) {
+ public void noteStopJobLocked(String name, long elapsedRealtimeMs, int stopReason) {
DualTimer t = mJobStats.stopObject(name);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
}
+ if (mBsi.mOnBatteryTimeBase.isRunning()) {
+ SparseIntArray types = mJobCompletions.get(name);
+ if (types == null) {
+ types = new SparseIntArray();
+ mJobCompletions.put(name, types);
+ }
+ int last = types.get(stopReason, 0);
+ types.put(stopReason, last + 1);
+ }
}
public StopwatchTimer getWakelockTimerLocked(Wakelock wl, int type) {
@@ -8588,16 +8658,14 @@
return mCpuFreqs;
}
- public BatteryStatsImpl(File systemDir, Handler handler) {
- this(new SystemClocks(), systemDir, handler, null);
+ public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb,
+ UserInfoProvider userInfoProvider) {
+ this(new SystemClocks(), systemDir, handler, cb, userInfoProvider);
}
- public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb) {
- this(new SystemClocks(), systemDir, handler, cb);
- }
-
- public BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
- PlatformIdleStateCallback cb) {
+ private BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
+ PlatformIdleStateCallback cb,
+ UserInfoProvider userInfoProvider) {
init(clocks);
if (systemDir != null) {
@@ -8684,6 +8752,7 @@
clearHistoryLocked();
updateDailyDeadlineLocked();
mPlatformIdleStateCallback = cb;
+ mUserInfoProvider = userInfoProvider;
}
public BatteryStatsImpl(Parcel p) {
@@ -10172,6 +10241,7 @@
// we read, we get a delta. If we are to distribute the cpu time, then do so. Otherwise
// we just ignore the data.
final long startTimeMs = mClocks.uptimeMillis();
+ mUserInfoProvider.refreshUserIds();
mKernelUidCpuTimeReader.readDelta(!mOnBatteryInternal ? null :
new KernelUidCpuTimeReader.Callback() {
@Override
@@ -10185,6 +10255,11 @@
+ " no mapping to owning uid: " + uid);
return;
}
+ if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+ Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
+ mKernelUidCpuTimeReader.removeUid(uid);
+ return;
+ }
final Uid u = getUidStatsLocked(uid);
// Accumulate the total system and user time.
@@ -10357,6 +10432,11 @@
+ " no mapping to owning uid: " + uid);
return;
}
+ if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+ Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
+ mKernelUidCpuFreqTimeReader.removeUid(uid);
+ return;
+ }
final Uid u = getUidStatsLocked(uid);
if (u.mCpuFreqTimeMs == null) {
u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
@@ -11018,6 +11098,23 @@
return u;
}
+ public void onCleanupUserLocked(int userId) {
+ final int firstUidForUser = UserHandle.getUid(userId, 0);
+ final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
+ mKernelUidCpuFreqTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+ mKernelUidCpuTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+ }
+
+ public void onUserRemovedLocked(int userId) {
+ final int firstUidForUser = UserHandle.getUid(userId, 0);
+ final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
+ mUidStats.put(firstUidForUser, null);
+ mUidStats.put(lastUidForUser, null);
+ final int firstIndex = mUidStats.indexOfKey(firstUidForUser);
+ final int lastIndex = mUidStats.indexOfKey(lastUidForUser);
+ mUidStats.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+ }
+
/**
* Remove the statistics object for a particular uid.
*/
@@ -11640,6 +11737,8 @@
u.readJobSummaryFromParcelLocked(name, in);
}
+ u.readJobCompletionsFromParcelLocked(in);
+
int NP = in.readInt();
if (NP > 1000) {
throw new ParcelFormatException("File corrupt: too many sensors " + NP);
@@ -12079,6 +12178,8 @@
jobStats.valueAt(ij).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
+ u.writeJobCompletionsToParcelLocked(out);
+
int NSE = u.mSensorStats.size();
out.writeInt(NSE);
for (int ise=0; ise<NSE; ise++) {
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index ce5e73a..f282fac 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -76,6 +76,17 @@
mLastUidCpuFreqTimeMs.delete(uid);
}
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ return;
+ }
+ mLastUidCpuFreqTimeMs.put(startUid, null);
+ mLastUidCpuFreqTimeMs.put(endUid, null);
+ final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
+ final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
+ mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+ }
+
@VisibleForTesting
public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
String line = reader.readLine();
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
index 181e1ac..37d9d1d 100644
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -130,17 +130,42 @@
* @param uid The UID to remove.
*/
public void removeUid(int uid) {
- int index = mLastUserTimeUs.indexOfKey(uid);
+ final int index = mLastSystemTimeUs.indexOfKey(uid);
if (index >= 0) {
- mLastUserTimeUs.removeAt(index);
mLastSystemTimeUs.removeAt(index);
+ mLastUserTimeUs.removeAt(index);
}
+ removeUidsFromKernelModule(uid, uid);
+ }
+ /**
+ * Removes UIDs in a given range from the kernel module and internal accounting data.
+ * @param startUid the first uid to remove
+ * @param endUid the last uid to remove
+ */
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ return;
+ }
+ mLastSystemTimeUs.put(startUid, 0);
+ mLastUserTimeUs.put(startUid, 0);
+ mLastSystemTimeUs.put(endUid, 0);
+ mLastUserTimeUs.put(endUid, 0);
+ final int startIndex = mLastSystemTimeUs.indexOfKey(startUid);
+ final int endIndex = mLastSystemTimeUs.indexOfKey(endUid);
+ mLastSystemTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
+ mLastUserTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
+ removeUidsFromKernelModule(startUid, endUid);
+ }
+
+ private void removeUidsFromKernelModule(int startUid, int endUid) {
+ Slog.d(TAG, "Removing uids " + startUid + "-" + endUid);
try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
- writer.write(Integer.toString(uid) + "-" + Integer.toString(uid));
+ writer.write(startUid + "-" + endUid);
writer.flush();
} catch (IOException e) {
- Slog.e(TAG, "failed to remove uid from uid_cputime module", e);
+ Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid
+ + " from uid_cputime module", e);
}
}
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index f4be128..66475e4 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -31,6 +31,7 @@
import com.android.internal.logging.AndroidConfig;
import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.VMRuntime;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.TimeZone;
@@ -228,8 +229,8 @@
* @param argv Argument vector for main()
* @param classLoader the classLoader to load {@className} with
*/
- private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
- throws Zygote.MethodAndArgsCaller {
+ private static Runnable findStaticMain(String className, String[] argv,
+ ClassLoader classLoader) {
Class<?> cl;
try {
@@ -263,7 +264,7 @@
* clears up all the stack frames that were required in setting
* up the process.
*/
- throw new Zygote.MethodAndArgsCaller(m, argv);
+ return new MethodAndArgsCaller(m, argv);
}
public static final void main(String[] argv) {
@@ -286,8 +287,8 @@
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
- protected static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
- throws Zygote.MethodAndArgsCaller {
+ protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
+ ClassLoader classLoader) {
// If the application calls System.exit(), terminate the process
// immediately without running any shutdown hooks. It is not possible to
// shutdown an Android application gracefully. Among other things, the
@@ -300,20 +301,13 @@
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
- final Arguments args;
- try {
- args = new Arguments(argv);
- } catch (IllegalArgumentException ex) {
- Slog.e(TAG, ex.getMessage());
- // let the process exit
- return;
- }
+ final Arguments args = new Arguments(argv);
// The end of of the RuntimeInit event (see #zygoteInit).
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// Remaining arguments are passed to the start class's static main
- invokeStaticMain(args.startClass, args.startArgs, classLoader);
+ return findStaticMain(args.startClass, args.startArgs, classLoader);
}
/**
@@ -422,4 +416,37 @@
System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
}
}
+
+ /**
+ * Helper class which holds a method and arguments and can call them. This is used as part of
+ * a trampoline to get rid of the initial process setup stack frames.
+ */
+ static class MethodAndArgsCaller implements Runnable {
+ /** method to call */
+ private final Method mMethod;
+
+ /** argument array */
+ private final String[] mArgs;
+
+ public MethodAndArgsCaller(Method method, String[] args) {
+ mMethod = method;
+ mArgs = args;
+ }
+
+ public void run() {
+ try {
+ mMethod.invoke(null, new Object[] { mArgs });
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ } catch (InvocationTargetException ex) {
+ Throwable cause = ex.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ throw new RuntimeException(ex);
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index e28079f..7f46a0c 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -26,6 +26,7 @@
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -68,8 +69,7 @@
}
@Override
- protected boolean handlePreloadPackage(String packagePath, String libsPath,
- String cacheKey) {
+ protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
Log.i(TAG, "Beginning package preload");
// Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
// our children will reuse the same classloader instead of creating their own.
@@ -87,19 +87,28 @@
// Once we have the classloader, look up the WebViewFactoryProvider implementation and
// call preloadInZygote() on it to give it the opportunity to preload the native library
// and perform any other initialisation work that should be shared among the children.
+ boolean preloadSucceeded = false;
try {
Class<WebViewFactoryProvider> providerClass =
WebViewFactory.getWebViewProviderClass(loader);
Object result = providerClass.getMethod("preloadInZygote").invoke(null);
- if (!((Boolean)result).booleanValue()) {
+ preloadSucceeded = ((Boolean) result).booleanValue();
+ if (!preloadSucceeded) {
Log.e(TAG, "preloadInZygote returned false");
}
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
IllegalAccessException | InvocationTargetException e) {
Log.e(TAG, "Exception while preloading package", e);
}
+
+ try {
+ DataOutputStream socketOut = getSocketOutputStream();
+ socketOut.writeInt(preloadSucceeded ? 1 : 0);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+
Log.i(TAG, "Package preload done");
- return false;
}
}
@@ -113,16 +122,23 @@
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
+ final Runnable caller;
try {
sServer.registerServerSocket("webview_zygote");
- sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
- sServer.closeServerSocket();
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
} catch (RuntimeException e) {
Log.e(TAG, "Fatal exception:", e);
+ throw e;
+ } finally {
+ sServer.closeServerSocket();
}
- System.exit(0);
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
+ }
}
}
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 608bc9f..89328b2 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -25,7 +25,6 @@
import android.system.StructCapUserHeader;
import android.util.BootTimingsTraceLog;
import android.util.Slog;
-import com.android.internal.os.Zygote.MethodAndArgsCaller;
import dalvik.system.VMRuntime;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
@@ -61,37 +60,35 @@
* @param args The command-line arguments.
*/
public static void main(String[] args) {
- try {
- // Parse our mandatory arguments.
- int fdNum = Integer.parseInt(args[0], 10);
- int targetSdkVersion = Integer.parseInt(args[1], 10);
+ // Parse our mandatory arguments.
+ int fdNum = Integer.parseInt(args[0], 10);
+ int targetSdkVersion = Integer.parseInt(args[1], 10);
- // Tell the Zygote what our actual PID is (since it only knows about the
- // wrapper that it directly forked).
- if (fdNum != 0) {
- try {
- FileDescriptor fd = new FileDescriptor();
- fd.setInt$(fdNum);
- DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
- os.writeInt(Process.myPid());
- os.close();
- IoUtils.closeQuietly(fd);
- } catch (IOException ex) {
- Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
- }
+ // Tell the Zygote what our actual PID is (since it only knows about the
+ // wrapper that it directly forked).
+ if (fdNum != 0) {
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(fdNum);
+ DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
+ os.writeInt(Process.myPid());
+ os.close();
+ IoUtils.closeQuietly(fd);
+ } catch (IOException ex) {
+ Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
}
-
- // Mimic system Zygote preloading.
- ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
- Trace.TRACE_TAG_DALVIK));
-
- // Launch the application.
- String[] runtimeArgs = new String[args.length - 2];
- System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
- WrapperInit.wrapperInit(targetSdkVersion, runtimeArgs);
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
}
+
+ // Mimic system Zygote preloading.
+ ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
+ Trace.TRACE_TAG_DALVIK));
+
+ // Launch the application.
+ String[] runtimeArgs = new String[args.length - 2];
+ System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
+ Runnable r = wrapperInit(targetSdkVersion, runtimeArgs);
+
+ r.run();
}
/**
@@ -142,8 +139,7 @@
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
- private static void wrapperInit(int targetSdkVersion, String[] argv)
- throws Zygote.MethodAndArgsCaller {
+ private static Runnable wrapperInit(int targetSdkVersion, String[] argv) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper");
}
@@ -165,7 +161,7 @@
// Perform the same initialization that would happen after the Zygote forks.
Zygote.nativePreApplicationInit();
- RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+ return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
/**
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 0106b81..a9350db 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -221,39 +221,4 @@
command.append(" '").append(arg.replace("'", "'\\''")).append("'");
}
}
-
- /**
- * Helper exception class which holds a method and arguments and
- * can call them. This is used as part of a trampoline to get rid of
- * the initial process setup stack frames.
- */
- public static class MethodAndArgsCaller extends Exception
- implements Runnable {
- /** method to call */
- private final Method mMethod;
-
- /** argument array */
- private final String[] mArgs;
-
- public MethodAndArgsCaller(Method method, String[] args) {
- mMethod = method;
- mArgs = args;
- }
-
- public void run() {
- try {
- mMethod.invoke(null, new Object[] { mArgs });
- } catch (IllegalAccessException ex) {
- throw new RuntimeException(ex);
- } catch (InvocationTargetException ex) {
- Throwable cause = ex.getCause();
- if (cause instanceof RuntimeException) {
- throw (RuntimeException) cause;
- } else if (cause instanceof Error) {
- throw (Error) cause;
- }
- throw new RuntimeException(ex);
- }
- }
- }
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 2013ac0..33382ed 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -26,7 +26,6 @@
import android.net.LocalSocket;
import android.os.FactoryTest;
import android.os.Process;
-import android.os.SELinux;
import android.os.SystemProperties;
import android.os.Trace;
import android.system.ErrnoException;
@@ -36,15 +35,13 @@
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.io.EOFException;
import java.io.FileDescriptor;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
import libcore.io.IoUtils;
/**
@@ -80,6 +77,7 @@
private final BufferedReader mSocketReader;
private final Credentials peer;
private final String abiList;
+ private boolean isEof;
/**
* Constructs instance from connected socket.
@@ -106,6 +104,8 @@
Log.e(TAG, "Cannot read peer credentials", ex);
throw ex;
}
+
+ isEof = false;
}
/**
@@ -118,21 +118,14 @@
}
/**
- * Reads one start command from the command socket. If successful,
- * a child is forked and a {@link Zygote.MethodAndArgsCaller}
- * exception is thrown in that child while in the parent process,
- * the method returns normally. On failure, the child is not
- * spawned and messages are printed to the log and stderr. Returns
- * a boolean status value indicating whether an end-of-file on the command
- * socket has been encountered.
+ * Reads one start command from the command socket. If successful, a child is forked and a
+ * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
+ * process. {@code null} is always returned in the parent process (the zygote).
*
- * @return false if command socket should continue to be read from, or
- * true if an end-of-file has been encountered.
- * @throws Zygote.MethodAndArgsCaller trampoline to invoke main()
- * method in child process
+ * If the client closes the socket, an {@code EOF} condition is set, which callers can test
+ * for by calling {@code ZygoteConnection.isClosedByPeer}.
*/
- boolean runOnce(ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller {
-
+ Runnable processOneCommand(ZygoteServer zygoteServer) {
String args[];
Arguments parsedArgs = null;
FileDescriptor[] descriptors;
@@ -141,130 +134,120 @@
args = readArgumentList();
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
- Log.w(TAG, "IOException on command socket " + ex.getMessage());
- closeSocket();
- return true;
+ throw new IllegalStateException("IOException on command socket", ex);
}
+ // readArgumentList returns null only when it has reached EOF with no available
+ // data to read. This will only happen when the remote socket has disconnected.
if (args == null) {
- // EOF reached.
- closeSocket();
- return true;
- }
-
- /** the stderr of the most recent request, if avail */
- PrintStream newStderr = null;
-
- if (descriptors != null && descriptors.length >= 3) {
- newStderr = new PrintStream(
- new FileOutputStream(descriptors[2]));
+ isEof = true;
+ return null;
}
int pid = -1;
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
- try {
- parsedArgs = new Arguments(args);
+ parsedArgs = new Arguments(args);
- if (parsedArgs.abiListQuery) {
- return handleAbiListQuery();
- }
+ if (parsedArgs.abiListQuery) {
+ handleAbiListQuery();
+ return null;
+ }
- if (parsedArgs.preloadDefault) {
- return handlePreload();
- }
+ if (parsedArgs.preloadDefault) {
+ handlePreload();
+ return null;
+ }
- if (parsedArgs.preloadPackage != null) {
- return handlePreloadPackage(parsedArgs.preloadPackage,
- parsedArgs.preloadPackageLibs, parsedArgs.preloadPackageCacheKey);
- }
+ if (parsedArgs.preloadPackage != null) {
+ handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
+ parsedArgs.preloadPackageCacheKey);
+ return null;
+ }
- if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
- throw new ZygoteSecurityException("Client may not specify capabilities: " +
- "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
- ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
- }
+ if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: " +
+ "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
+ ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+ }
- applyUidSecurityPolicy(parsedArgs, peer);
- applyInvokeWithSecurityPolicy(parsedArgs, peer);
+ applyUidSecurityPolicy(parsedArgs, peer);
+ applyInvokeWithSecurityPolicy(parsedArgs, peer);
- applyDebuggerSystemProperty(parsedArgs);
- applyInvokeWithSystemProperty(parsedArgs);
+ applyDebuggerSystemProperty(parsedArgs);
+ applyInvokeWithSystemProperty(parsedArgs);
- int[][] rlimits = null;
+ int[][] rlimits = null;
- if (parsedArgs.rlimits != null) {
- rlimits = parsedArgs.rlimits.toArray(intArray2d);
- }
+ if (parsedArgs.rlimits != null) {
+ rlimits = parsedArgs.rlimits.toArray(intArray2d);
+ }
- int[] fdsToIgnore = null;
+ int[] fdsToIgnore = null;
- if (parsedArgs.invokeWith != null) {
+ if (parsedArgs.invokeWith != null) {
+ try {
FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0];
Os.fcntlInt(childPipeFd, F_SETFD, 0);
- fdsToIgnore = new int[] { childPipeFd.getInt$(), serverPipeFd.getInt$() };
+ fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
+ } catch (ErrnoException errnoEx) {
+ throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);
}
-
- /**
- * In order to avoid leaking descriptors to the Zygote child,
- * the native code must close the two Zygote socket descriptors
- * in the child process before it switches from Zygote-root to
- * the UID and privileges of the application being launched.
- *
- * In order to avoid "bad file descriptor" errors when the
- * two LocalSocket objects are closed, the Posix file
- * descriptors are released via a dup2() call which closes
- * the socket and substitutes an open descriptor to /dev/null.
- */
-
- int [] fdsToClose = { -1, -1 };
-
- FileDescriptor fd = mSocket.getFileDescriptor();
-
- if (fd != null) {
- fdsToClose[0] = fd.getInt$();
- }
-
- fd = zygoteServer.getServerSocketFileDescriptor();
-
- if (fd != null) {
- fdsToClose[1] = fd.getInt$();
- }
-
- fd = null;
-
- pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
- parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
- parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
- parsedArgs.appDataDir);
- } catch (ErrnoException ex) {
- logAndPrintError(newStderr, "Exception creating pipe", ex);
- } catch (IllegalArgumentException ex) {
- logAndPrintError(newStderr, "Invalid zygote arguments", ex);
- } catch (ZygoteSecurityException ex) {
- logAndPrintError(newStderr,
- "Zygote security policy prevents request: ", ex);
}
+ /**
+ * In order to avoid leaking descriptors to the Zygote child,
+ * the native code must close the two Zygote socket descriptors
+ * in the child process before it switches from Zygote-root to
+ * the UID and privileges of the application being launched.
+ *
+ * In order to avoid "bad file descriptor" errors when the
+ * two LocalSocket objects are closed, the Posix file
+ * descriptors are released via a dup2() call which closes
+ * the socket and substitutes an open descriptor to /dev/null.
+ */
+
+ int [] fdsToClose = { -1, -1 };
+
+ FileDescriptor fd = mSocket.getFileDescriptor();
+
+ if (fd != null) {
+ fdsToClose[0] = fd.getInt$();
+ }
+
+ fd = zygoteServer.getServerSocketFileDescriptor();
+
+ if (fd != null) {
+ fdsToClose[1] = fd.getInt$();
+ }
+
+ fd = null;
+
+ pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
+ parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
+ parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
+ parsedArgs.appDataDir);
+
try {
if (pid == 0) {
// in child
+ zygoteServer.setForkChild();
+
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
- handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
- // should never get here, the child is expected to either
- // throw Zygote.MethodAndArgsCaller or exec().
- return true;
+ return handleChildProc(parsedArgs, descriptors, childPipeFd);
} else {
- // in parent...pid of < 0 means failure
+ // In the parent. A pid < 0 indicates a failure and will be handled in
+ // handleParentProc.
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
- return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
+ handleParentProc(pid, descriptors, serverPipeFd);
+ return null;
}
} finally {
IoUtils.closeQuietly(childPipeFd);
@@ -272,15 +255,13 @@
}
}
- private boolean handleAbiListQuery() {
+ private void handleAbiListQuery() {
try {
final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII);
mSocketOutStream.writeInt(abiListBytes.length);
mSocketOutStream.write(abiListBytes);
- return false;
} catch (IOException ioe) {
- Log.e(TAG, "Error writing to command socket", ioe);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ioe);
}
}
@@ -290,7 +271,7 @@
* if no preload was initiated. The latter implies that the zygote is not configured to load
* resources lazy or that the zygote has already handled a previous request to handlePreload.
*/
- private boolean handlePreload() {
+ private void handlePreload() {
try {
if (isPreloadComplete()) {
mSocketOutStream.writeInt(1);
@@ -298,11 +279,8 @@
preload();
mSocketOutStream.writeInt(0);
}
-
- return false;
} catch (IOException ioe) {
- Log.e(TAG, "Error writing to command socket", ioe);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ioe);
}
}
@@ -314,7 +292,11 @@
return ZygoteInit.isPreloadComplete();
}
- protected boolean handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
+ protected DataOutputStream getSocketOutputStream() {
+ return mSocketOutStream;
+ }
+
+ protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
throw new RuntimeException("Zyogte does not support package preloading");
}
@@ -330,6 +312,10 @@
}
}
+ boolean isClosedByPeer() {
+ return isEof;
+ }
+
/**
* Handles argument parsing for args related to the zygote spawner.
*
@@ -777,15 +763,9 @@
* @param parsedArgs non-null; zygote args
* @param descriptors null-ok; new file descriptors for stdio if available.
* @param pipeFd null-ok; pipe for communication back to Zygote.
- * @param newStderr null-ok; stream to use for stderr until stdio
- * is reopened.
- *
- * @throws Zygote.MethodAndArgsCaller on success to
- * trampoline to code that invokes static main.
*/
- private void handleChildProc(Arguments parsedArgs,
- FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
- throws Zygote.MethodAndArgsCaller {
+ private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+ FileDescriptor pipeFd) {
/**
* By the time we get here, the native code has closed the two actual Zygote
* socket connections, and substituted /dev/null in their place. The LocalSocket
@@ -802,7 +782,6 @@
for (FileDescriptor fd: descriptors) {
IoUtils.closeQuietly(fd);
}
- newStderr = System.err;
} catch (ErrnoException ex) {
Log.e(TAG, "Error reopening stdio", ex);
}
@@ -819,9 +798,12 @@
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(),
pipeFd, parsedArgs.remainingArgs);
+
+ // Should not get here.
+ throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
} else {
- ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion,
- parsedArgs.remainingArgs, null /* classLoader */);
+ return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
+ null /* classLoader */);
}
}
@@ -833,13 +815,8 @@
* @param descriptors null-ok; file descriptors for child's new stdio if
* specified.
* @param pipeFd null-ok; pipe for communication with child.
- * @param parsedArgs non-null; zygote args
- * @return true for "exit command loop" and false for "continue command
- * loop"
*/
- private boolean handleParentProc(int pid,
- FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
-
+ private void handleParentProc(int pid, FileDescriptor[] descriptors, FileDescriptor pipeFd) {
if (pid > 0) {
setChildPgid(pid);
}
@@ -888,11 +865,8 @@
mSocketOutStream.writeInt(pid);
mSocketOutStream.writeBoolean(usingWrapper);
} catch (IOException ex) {
- Log.e(TAG, "Error writing to command socket", ex);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ex);
}
-
- return false;
}
private void setChildPgid(int pid) {
@@ -908,20 +882,4 @@
+ "normal if peer is not in our session");
}
}
-
- /**
- * Logs an error message and prints it to the specified stream, if
- * provided
- *
- * @param newStderr null-ok; a standard error stream
- * @param message non-null; error message
- * @param ex null-ok an exception
- */
- private static void logAndPrintError (PrintStream newStderr,
- String message, Throwable ex) {
- Log.e(TAG, message, ex);
- if (newStderr != null) {
- newStderr.println(message + (ex == null ? "" : ex));
- }
- }
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 385976c..b9022bc 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -24,7 +24,6 @@
import android.icu.impl.CacheValue;
import android.icu.text.DecimalFormatSymbols;
import android.icu.util.ULocale;
-import android.net.LocalServerSocket;
import android.opengl.EGL14;
import android.os.Build;
import android.os.IInstalld;
@@ -446,10 +445,7 @@
/**
* Finish remaining work for the newly forked system server process.
*/
- private static void handleSystemServerProcess(
- ZygoteConnection.Arguments parsedArgs)
- throws Zygote.MethodAndArgsCaller {
-
+ private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
// set umask to 0077 so new files and directories will default to owner-only permissions.
Os.umask(S_IRWXG | S_IRWXO);
@@ -495,6 +491,8 @@
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(), null, args);
+
+ throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
@@ -506,7 +504,7 @@
/*
* Pass the remaining arguments to SystemServer.
*/
- ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+ return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}
/* should never reach here */
@@ -588,10 +586,13 @@
}
/**
- * Prepare the arguments and fork for the system server process.
+ * Prepare the arguments and forks for the system server process.
+ *
+ * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
+ * child process, and {@code null} in the parent.
*/
- private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer)
- throws Zygote.MethodAndArgsCaller, RuntimeException {
+ private static Runnable forkSystemServer(String abiList, String socketName,
+ ZygoteServer zygoteServer) {
long capabilities = posixCapabilitiesAsBits(
OsConstants.CAP_IPC_LOCK,
OsConstants.CAP_KILL,
@@ -648,10 +649,10 @@
}
zygoteServer.closeServerSocket();
- handleSystemServerProcess(parsedArgs);
+ return handleSystemServerProcess(parsedArgs);
}
- return true;
+ return null;
}
/**
@@ -682,6 +683,7 @@
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
+ final Runnable caller;
try {
// Report Zygote start time to tron unless it is a runtime restart
if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -751,19 +753,32 @@
ZygoteHooks.stopZygoteNoThreadCreation();
if (startSystemServer) {
- startSystemServer(abiList, socketName, zygoteServer);
+ Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
+
+ // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
+ // child (system_server) process.
+ if (r != null) {
+ r.run();
+ return;
+ }
}
Log.i(TAG, "Accepting command socket connections");
- zygoteServer.runSelectLoop(abiList);
- zygoteServer.closeServerSocket();
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
- zygoteServer.closeServerSocket();
throw ex;
+ } finally {
+ zygoteServer.closeServerSocket();
+ }
+
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
}
}
@@ -807,8 +822,7 @@
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
- public static final void zygoteInit(int targetSdkVersion, String[] argv,
- ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {
+ public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
@@ -818,7 +832,7 @@
RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit();
- RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+ return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
private static final native void nativeZygoteInit();
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index 126d9e7..8baa15a 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -25,6 +25,7 @@
import android.system.StructPollfd;
import android.util.Log;
+import android.util.Slog;
import java.io.IOException;
import java.io.FileDescriptor;
import java.util.ArrayList;
@@ -45,9 +46,18 @@
private LocalServerSocket mServerSocket;
+ /**
+ * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
+ */
+ private boolean mIsForkChild;
+
ZygoteServer() {
}
+ void setForkChild() {
+ mIsForkChild = true;
+ }
+
/**
* Registers a server socket for zygote command connections
*
@@ -129,11 +139,8 @@
* Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's
* worth at a time.
- *
- * @throws Zygote.MethodAndArgsCaller in a child process when a main()
- * should be executed.
*/
- void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
+ Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
@@ -156,15 +163,62 @@
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
+
if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
- boolean done = peers.get(i).runOnce(this);
- if (done) {
- peers.remove(i);
- fds.remove(i);
+ try {
+ ZygoteConnection connection = peers.get(i);
+ final Runnable command = connection.processOneCommand(this);
+
+ if (mIsForkChild) {
+ // We're in the child. We should always have a command to run at this
+ // stage if processOneCommand hasn't called "exec".
+ if (command == null) {
+ throw new IllegalStateException("command == null");
+ }
+
+ return command;
+ } else {
+ // We're in the server - we should never have any commands to run.
+ if (command != null) {
+ throw new IllegalStateException("command != null");
+ }
+
+ // We don't know whether the remote side of the socket was closed or
+ // not until we attempt to read from it from processOneCommand. This shows up as
+ // a regular POLLIN event in our regular processing loop.
+ if (connection.isClosedByPeer()) {
+ connection.closeSocket();
+ peers.remove(i);
+ fds.remove(i);
+ }
+ }
+ } catch (Exception e) {
+ if (!mIsForkChild) {
+ // We're in the server so any exception here is one that has taken place
+ // pre-fork while processing commands or reading / writing from the
+ // control socket. Make a loud noise about any such exceptions so that
+ // we know exactly what failed and why.
+
+ Slog.e(TAG, "Exception executing zygote command: ", e);
+
+ // Make sure the socket is closed so that the other end knows immediately
+ // that something has gone wrong and doesn't time out waiting for a
+ // response.
+ ZygoteConnection conn = peers.remove(i);
+ conn.closeSocket();
+
+ fds.remove(i);
+ } else {
+ // We're in the child so any exception caught here has happened post
+ // fork and before we execute ActivityThread.main (or any other main()
+ // method). Log the details of the exception and bring down the process.
+ Log.e(TAG, "Caught post-fork exception in child process.", e);
+ throw e;
+ }
}
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7555cab..a3cb350 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -279,6 +279,7 @@
<protected-broadcast android:name="com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG" />
<protected-broadcast android:name="com.android.nfc.handover.action.ALLOW_CONNECT" />
<protected-broadcast android:name="com.android.nfc.handover.action.DENY_CONNECT" />
+ <protected-broadcast android:name="com.android.nfc.handover.action.TIMEOUT_CONNECT" />
<protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_ON_DETECTED" />
<protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED" />
<protected-broadcast android:name="com.android.nfc_extras.action.AID_SELECTED" />
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index d83ccb2..dc47dcf 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -17,7 +17,6 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layoutDirection="ltr"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -34,6 +33,7 @@
android:layoutDirection="ltr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
android:paddingTop="@dimen/timepicker_radial_picker_top_margin"
android:orientation="horizontal">
diff --git a/core/res/res/layout/chooser_row.xml b/core/res/res/layout/chooser_row.xml
index 9baa32c..6c1271d 100644
--- a/core/res/res/layout/chooser_row.xml
+++ b/core/res/res/layout/chooser_row.xml
@@ -18,11 +18,9 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
- android:layout_width="match_parent" android:layout_height="wrap_content"
- android:minHeight="80dp"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
android:gravity="start|top"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
android:paddingStart="@dimen/chooser_grid_padding"
android:paddingEnd="@dimen/chooser_grid_padding"
android:weightSum="4">
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 305c8b0..71c153f 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -18,10 +18,9 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:layout_width="0dp"
+ android:layout_width="76dp"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:minWidth="80dp"
+ android:minHeight="100dp"
android:gravity="center"
android:paddingTop="8dp"
android:paddingBottom="8dp"
@@ -49,7 +48,7 @@
<TextView android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:textAppearance="?attr/textAppearanceSmall"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 55030d5..802f027 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -411,24 +411,45 @@
-->
<integer translatable="false" name="config_wifi_wakeup_available">0</integer>
- <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
- <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
- <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
- <!-- This list is also modified by code within the framework, including:
+ <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering.
+
+ Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+ [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE.
+
+ This list is also modified by code within the framework, including:
- TYPE_ETHERNET (9) is prepended to this list, and
- - the output of TelephonyManager.getTetherApnRequired()
- determines whether both TYPE_MOBILE (0) and TYPE_HIPRI (5)
- or TYPE_MOBILE_DUN (4) are appended (if not already present).
+
+ - the return value of TelephonyManager.getTetherApnRequired()
+ determines how the array is further modified:
+
+ * DUN_REQUIRED
+ TYPE_MOBILE is removed (if present)
+ TYPE_MOBILE_HIPRI is removed (if present)
+ TYPE_MOBILE_DUN is appended (if not already present)
+
+ * DUN_NOT_REQUIRED
+ TYPE_MOBILE_DUN is removed (if present)
+ TYPE_MOBILE is appended (if not already present)
+ TYPE_MOBILE_HIPRI is appended (if not already present)
+
+ * DUN_UNSPECIFIED
+ if any of TYPE_MOBILE{,_DUN,_HIPRI} are present:
+ change nothing
+ else:
+ TYPE_MOBILE is appended
+ TYPE_MOBILE_HIPRI is appended
For other changes applied to this list, now and in the future, see
com.android.server.connectivity.tethering.TetheringConfiguration.
+
+ Note also: the order of this is important. The first upstream type
+ for which a satisfying network exists is used.
-->
<integer-array translatable="false" name="config_tether_upstream_types">
- <item>0</item>
<item>1</item>
- <item>5</item>
<item>7</item>
+ <item>0</item>
</integer-array>
<!-- If the DUN connection for this CDMA device supports more than just DUN -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index fa33d56..9f9c883 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -533,6 +533,8 @@
<dimen name="content_rect_bottom_clip_allowance">20dp</dimen>
<dimen name="chooser_grid_padding">0dp</dimen>
+ <!-- Spacing around the background change frome service to non-service -->
+ <dimen name="chooser_service_spacing">8dp</dimen>
<item type="dimen" name="aerr_padding_list_top">15dp</item>
<item type="dimen" name="aerr_padding_list_bottom">8dp</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d9ce7fb..6d6064f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3053,4 +3053,5 @@
<java-symbol type="string" name="popup_window_default_title" />
<java-symbol type="bool" name="config_showAreaUpdateInfoSettings" />
<java-symbol type="layout" name="shutdown_dialog" />
+ <java-symbol type="dimen" name="chooser_service_spacing" />
</resources>
diff --git a/core/tests/coretests/src/android/database/run_newdb_perf_test.sh b/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
index b2f9827..10a62e6 100755
--- a/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
+++ b/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
@@ -14,7 +14,7 @@
# limitations under the License.
make -j44 FrameworksCoreTests
-adb install -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+adb install -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
adb logcat -c
echo "Running benchmark 5 times"
diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
new file mode 100644
index 0000000..5a5e893
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+/**
+ * Internal tests for {@link SparseLongArray}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SparseLongArrayTest {
+
+ private static final int TEST_SIZE = 1000;
+
+ private SparseLongArray mSparseLongArray;
+ private int[] mKeys;
+ private long[] mValues;
+ private Random mRandom;
+
+ private static boolean isSame(@NonNull SparseLongArray array1,
+ @NonNull SparseLongArray array2) {
+ if (array1.size() != array2.size()) {
+ return false;
+ }
+ for (int i = 0; i < array1.size(); i++) {
+ if (array1.keyAt(i) != array2.keyAt(i) || array1.valueAt(i) != array2.valueAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void assertRemoved(int startIndex, int endIndex) {
+ for (int i = 0; i < TEST_SIZE; i++) {
+ if (i >= startIndex && i <= endIndex) {
+ assertEquals("Entry not removed", Long.MIN_VALUE,
+ mSparseLongArray.get(mKeys[i], Long.MIN_VALUE));
+ } else {
+ assertEquals("Untouched entry corrupted", mValues[i],
+ mSparseLongArray.get(mKeys[i]));
+ }
+ }
+ }
+
+ /**
+ * Generates a sorted array of distinct and random keys
+ *
+ * @param size the number of keys to return in the array. Should be < (2^31)/1000.
+ * @return the array of keys
+ */
+ private int[] generateRandomKeys(int size) {
+ final int[] keys = new int[size];
+ keys[0] = -1 * mRandom.nextInt(size * 500);
+ for (int i = 1; i < size; i++) {
+ keys[i] = keys[i - 1] + 1 + mRandom.nextInt(1000);
+ assertTrue(keys[i] > keys[i - 1]);
+ }
+ return keys;
+ }
+
+ @Before
+ public void setUp() {
+ mSparseLongArray = new SparseLongArray();
+ mRandom = new Random(12345);
+ mKeys = generateRandomKeys(TEST_SIZE);
+ mValues = new long[TEST_SIZE];
+ for (int i = 0; i < TEST_SIZE; i++) {
+ mValues[i] = i + 1;
+ mSparseLongArray.put(mKeys[i], mValues[i]);
+ }
+ }
+
+ @Test
+ public void testRemoveAtRange_removeHead() {
+ mSparseLongArray.removeAtRange(0, 100);
+ assertEquals(TEST_SIZE - 100, mSparseLongArray.size());
+ assertRemoved(0, 99);
+ }
+
+ @Test
+ public void testRemoveAtRange_removeTail() {
+ mSparseLongArray.removeAtRange(TEST_SIZE - 200, 200);
+ assertEquals(TEST_SIZE - 200, mSparseLongArray.size());
+ assertRemoved(TEST_SIZE - 200, TEST_SIZE - 1);
+ }
+
+ @Test
+ public void testRemoveAtRange_removeOverflow() {
+ mSparseLongArray.removeAtRange(TEST_SIZE - 100, 200);
+ assertEquals(TEST_SIZE - 100, mSparseLongArray.size());
+ assertRemoved(TEST_SIZE - 100, TEST_SIZE - 1);
+ }
+
+ @Test
+ public void testRemoveAtRange_removeEverything() {
+ mSparseLongArray.removeAtRange(0, TEST_SIZE);
+ assertEquals(0, mSparseLongArray.size());
+ assertRemoved(0, TEST_SIZE - 1);
+ }
+
+ @Test
+ public void testRemoveAtRange_removeMiddle() {
+ mSparseLongArray.removeAtRange(200, 200);
+ assertEquals(TEST_SIZE - 200, mSparseLongArray.size());
+ assertRemoved(200, 399);
+ }
+
+ @Test
+ public void testRemoveAtRange_removeSingle() {
+ mSparseLongArray.removeAtRange(300, 1);
+ assertEquals(TEST_SIZE - 1, mSparseLongArray.size());
+ assertRemoved(300, 300);
+ }
+
+ @Test
+ public void testRemoveAtRange_compareRemoveAt() {
+ final SparseLongArray sparseLongArray2 = mSparseLongArray.clone();
+ assertTrue(isSame(mSparseLongArray, sparseLongArray2));
+
+ final int startIndex = 101;
+ final int endIndex = 200;
+ mSparseLongArray.removeAtRange(startIndex, endIndex - startIndex + 1);
+ for (int i = endIndex; i >= startIndex; i--) {
+ sparseLongArray2.removeAt(i);
+ }
+ assertEquals(TEST_SIZE - (endIndex - startIndex + 1), mSparseLongArray.size());
+ assertRemoved(startIndex, endIndex);
+ assertTrue(isSame(sparseLongArray2, mSparseLongArray));
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
index c539f78..7a6b8d5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -280,7 +280,7 @@
// Stop timer
curr = 1000 * (clocks.realtime = clocks.uptime = 161);
- bi.noteJobFinishLocked(jobName, UID);
+ bi.noteJobFinishLocked(jobName, UID, 0);
// Move to background
curr = 1000 * (clocks.realtime = clocks.uptime = 202);
@@ -296,7 +296,7 @@
// Stop timer
curr = 1000 * (clocks.realtime = clocks.uptime = 409);
- bi.noteJobFinishLocked(jobName, UID);
+ bi.noteJobFinishLocked(jobName, UID, 0);
// Test
curr = 1000 * (clocks.realtime = clocks.uptime = 657);
@@ -319,7 +319,7 @@
final String jobName2 = "second_job";
bi.noteJobStartLocked(jobName2, UID);
assertEquals(2, bi.getUidStats().get(UID).getJobStats().size());
- bi.noteJobFinishLocked(jobName2, UID);
+ bi.noteJobFinishLocked(jobName2, UID, 0);
}
@SmallTest
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index f01c33f..e81f678 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -18,9 +18,12 @@
package com.android.internal.os;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -54,11 +57,16 @@
@SmallTest
public class BatteryStatsHelperTest extends TestCase {
private static final long TIME_FOREGROUND_ACTIVITY_ZERO = 0;
- private static final long TIME_FOREGROUND_ACTIVITY = 100 * DateUtils.MINUTE_IN_MILLIS;
+ private static final long TIME_FOREGROUND_ACTIVITY = 100 * DateUtils.MINUTE_IN_MILLIS * 1000;
+ private static final long TIME_STATE_FOREGROUND_MS = 10 * DateUtils.MINUTE_IN_MILLIS;
+ private static final long TIME_STATE_FOREGROUND_US = TIME_STATE_FOREGROUND_MS * 1000;
private static final int UID = 123456;
private static final double BATTERY_SCREEN_USAGE = 300;
private static final double BATTERY_SYSTEM_USAGE = 600;
+ private static final double BATTERY_WIFI_USAGE = 200;
+ private static final double BATTERY_IDLE_USAGE = 600;
+ private static final double BATTERY_BLUETOOTH_USAGE = 300;
private static final double BATTERY_OVERACCOUNTED_USAGE = 500;
private static final double BATTERY_UNACCOUNTED_USAGE = 700;
private static final double BATTERY_APP_USAGE = 100;
@@ -68,6 +76,12 @@
@Mock
private BatteryStats.Uid mUid;
@Mock
+ private BatterySipper mWifiBatterySipper;
+ @Mock
+ private BatterySipper mBluetoothBatterySipper;
+ @Mock
+ private BatterySipper mIdleBatterySipper;
+ @Mock
private BatterySipper mNormalBatterySipper;
@Mock
private BatterySipper mScreenBatterySipper;
@@ -109,6 +123,15 @@
mUnaccountedBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED;
mUnaccountedBatterySipper.totalPowerMah = BATTERY_UNACCOUNTED_USAGE;
+ mWifiBatterySipper.drainType = BatterySipper.DrainType.WIFI;
+ mWifiBatterySipper.totalPowerMah = BATTERY_WIFI_USAGE;
+
+ mBluetoothBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
+ mBluetoothBatterySipper.totalPowerMah = BATTERY_BLUETOOTH_USAGE;
+
+ mIdleBatterySipper.drainType = BatterySipper.DrainType.IDLE;
+ mIdleBatterySipper.totalPowerMah = BATTERY_IDLE_USAGE;
+
mContext = InstrumentationRegistry.getContext();
mBatteryStatsHelper = spy(new BatteryStatsHelper(mContext));
mBatteryStatsHelper.setPackageManager(mPackageManager);
@@ -165,6 +188,9 @@
sippers.add(mSystemBatterySipper);
sippers.add(mOvercountedBatterySipper);
sippers.add(mUnaccountedBatterySipper);
+ sippers.add(mWifiBatterySipper);
+ sippers.add(mBluetoothBatterySipper);
+ sippers.add(mIdleBatterySipper);
doReturn(true).when(mBatteryStatsHelper).isTypeSystem(mSystemBatterySipper);
doNothing().when(mBatteryStatsHelper).smearScreenBatterySipper(any(), any());
@@ -219,6 +245,19 @@
assertThat(mBatteryStatsHelper.isTypeService(mNormalBatterySipper)).isTrue();
}
+ @Test
+ public void testGetProcessForegroundTimeMs_largerActivityTime_returnMinTime() {
+ doReturn(TIME_STATE_FOREGROUND_US + 500).when(mBatteryStatsHelper)
+ .getForegroundActivityTotalTimeUs(eq(mUid), anyLong());
+ doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP),
+ anyLong(), anyInt());
+
+ final long time = mBatteryStatsHelper.getProcessForegroundTimeMs(mUid,
+ BatteryStats.STATS_SINCE_CHARGED);
+
+ assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
+ }
+
private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah,
int uidCode, boolean isUidNull) {
final BatterySipper sipper = mock(BatterySipper.class);
@@ -227,8 +266,8 @@
doReturn(uidCode).when(sipper).getUid();
if (!isUidNull) {
final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS);
- doReturn(activityTime).when(mBatteryStatsHelper).getForegroundActivityTotalTimeMs(
- eq(uid), anyLong());
+ doReturn(activityTime).when(mBatteryStatsHelper).getProcessForegroundTimeMs(eq(uid),
+ anyInt());
doReturn(uidCode).when(uid).getUid();
sipper.uidObj = uid;
}
@@ -236,5 +275,4 @@
return sipper;
}
-
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java
new file mode 100644
index 0000000..450473d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.IStopUserCallback;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.ArraySet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsUserLifecycleTests {
+
+ private static final long POLL_INTERVAL_MS = 500;
+ private static final long USER_REMOVE_TIMEOUT_MS = 5_000;
+ private static final long STOP_USER_TIMEOUT_MS = 10_000;
+ private static final long BATTERYSTATS_POLLING_TIMEOUT_MS = 5_000;
+
+ private static final String CPU_DATA_TAG = "cpu";
+ private static final String CPU_FREQ_DATA_TAG = "ctf";
+
+ private int mTestUserId = UserHandle.USER_NULL;
+ private Context mContext;
+ private UserManager mUm;
+ private IActivityManager mIam;
+
+ @BeforeClass
+ public static void setUpOnce() {
+ assumeTrue(UserManager.getMaxSupportedUsers() > 1);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mUm = UserManager.get(mContext);
+ mIam = ActivityManager.getService();
+ final UserInfo user = mUm.createUser("Test_user_" + System.currentTimeMillis() / 1000, 0);
+ assertNotNull("Unable to create test user", user);
+ mTestUserId = user.id;
+ batteryOnScreenOff();
+ }
+
+ @Test
+ public void testNoCpuDataForRemovedUser() throws Exception {
+ mIam.startUserInBackground(mTestUserId);
+ waitUntilTrue("No uids for started user " + mTestUserId,
+ () -> getNumberOfUidsInBatteryStats() > 0, BATTERYSTATS_POLLING_TIMEOUT_MS);
+
+ CountDownLatch stopUserLatch = new CountDownLatch(1);
+ mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() {
+ @Override
+ public void userStopped(int userId) throws RemoteException {
+ stopUserLatch.countDown();
+ }
+
+ @Override
+ public void userStopAborted(int userId) throws RemoteException {
+ }
+ });
+ assertTrue("User " + mTestUserId + " could not be stopped",
+ stopUserLatch.await(STOP_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ mUm.removeUser(mTestUserId);
+ waitUntilTrue("Unable to remove user " + mTestUserId, () -> {
+ for (UserInfo user : mUm.getUsers()) {
+ if (user.id == mTestUserId) {
+ return false;
+ }
+ }
+ return true;
+ }, USER_REMOVE_TIMEOUT_MS);
+ waitUntilTrue("Uids still found for removed user " + mTestUserId,
+ () -> getNumberOfUidsInBatteryStats() == 0, BATTERYSTATS_POLLING_TIMEOUT_MS);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ batteryOffScreenOn();
+ if (mTestUserId != UserHandle.USER_NULL) {
+ mUm.removeUser(mTestUserId);
+ }
+ }
+
+ private int getNumberOfUidsInBatteryStats() throws Exception {
+ ArraySet<Integer> uids = new ArraySet<>();
+ final String dumpsys = executeShellCommand("dumpsys batterystats --checkin");
+ for (String line : dumpsys.split("\n")) {
+ final String[] parts = line.trim().split(",");
+ if (parts.length < 5 ||
+ (!parts[3].equals(CPU_DATA_TAG) && !parts[3].equals(CPU_FREQ_DATA_TAG))) {
+ continue;
+ }
+ try {
+ final int uid = Integer.parseInt(parts[1]);
+ if (UserHandle.getUserId(uid) == mTestUserId) {
+ uids.add(uid);
+ }
+ } catch (NumberFormatException nexc) {
+ // ignore
+ }
+ }
+ return uids.size();
+ }
+
+ protected void batteryOnScreenOff() throws Exception {
+ executeShellCommand("dumpsys battery unplug");
+ executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+ }
+
+ protected void batteryOffScreenOn() throws Exception {
+ executeShellCommand("dumpsys battery reset");
+ executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+ }
+
+ private String executeShellCommand(String cmd) throws Exception {
+ return UiDevice.getInstance(
+ InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+ }
+
+ private void waitUntilTrue(String message, Condition condition, long timeout) throws Exception {
+ final long deadLine = System.currentTimeMillis() + timeout;
+ while (System.currentTimeMillis() <= deadLine && !condition.isTrue()) {
+ Thread.sleep(POLL_INTERVAL_MS);
+ }
+ assertTrue(message, condition.isTrue());
+ }
+
+ private interface Condition {
+ boolean isTrue() throws Exception;
+ }
+}
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index ae5ae1a..ef2950b 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -31,6 +31,20 @@
include $(BUILD_JAVA_LIBRARY)
+# Build the repackaged-legacy-test library
+# ========================================
+# This contains repackaged versions of the classes from legacy-test.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := repackaged-legacy-test
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
# Generate the stub source files for legacy.test.stubs
# ====================================================
include $(CLEAR_VARS)
diff --git a/legacy-test/jarjar-rules.txt b/legacy-test/jarjar-rules.txt
new file mode 100644
index 0000000..9077e6f
--- /dev/null
+++ b/legacy-test/jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule junit.** repackaged.junit.@1
+rule android.test.** repackaged.android.test.@1
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 303d05f..113a477 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -201,6 +201,8 @@
"PathParser.cpp",
"PathTessellator.cpp",
"PixelBuffer.cpp",
+ "ProfileData.cpp",
+ "ProfileDataContainer.cpp",
"ProfileRenderer.cpp",
"Program.cpp",
"ProgramCache.cpp",
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 028d9f7..9d11828 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -34,14 +34,6 @@
namespace android {
namespace uirenderer {
-static const char* JANK_TYPE_NAMES[] = {
- "Missed Vsync",
- "High input latency",
- "Slow UI thread",
- "Slow bitmap uploads",
- "Slow issue draw commands",
-};
-
struct Comparison {
FrameInfoIndex start;
FrameInfoIndex end;
@@ -68,70 +60,13 @@
*/
static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas;
-// The bucketing algorithm controls so to speak
-// If a frame is <= to this it goes in bucket 0
-static const uint32_t kBucketMinThreshold = 5;
-// If a frame is > this, start counting in increments of 2ms
-static const uint32_t kBucket2msIntervals = 32;
-// If a frame is > this, start counting in increments of 4ms
-static const uint32_t kBucket4msIntervals = 48;
-
// For testing purposes to try and eliminate test infra overhead we will
// consider any unknown delay of frame start as part of the test infrastructure
// and filter it out of the frame profile data
static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
-// The interval of the slow frame histogram
-static const uint32_t kSlowFrameBucketIntervalMs = 50;
-// The start point of the slow frame bucket in ms
-static const uint32_t kSlowFrameBucketStartMs = 150;
-
-// This will be called every frame, performance sensitive
-// Uses bit twiddling to avoid branching while achieving the packing desired
-static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) {
- uint32_t index = static_cast<uint32_t>(ns2ms(frameTime));
- // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
- // of negating 1 (twos compliment, yaay) else mask will be 0
- uint32_t mask = -(index > kBucketMinThreshold);
- // If index > threshold, this will essentially perform:
- // amountAboveThreshold = index - threshold;
- // index = threshold + (amountAboveThreshold / 2)
- // However if index is <= this will do nothing. It will underflow, do
- // a right shift by 0 (no-op), then overflow back to the original value
- index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals))
- + kBucket4msIntervals;
- index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals))
- + kBucket2msIntervals;
- // If index was < minThreshold at the start of all this it's going to
- // be a pretty garbage value right now. However, mask is 0 so we'll end
- // up with the desired result of 0.
- index = (index - kBucketMinThreshold) & mask;
- return index;
-}
-
-// Only called when dumping stats, less performance sensitive
-int32_t JankTracker::frameTimeForFrameCountIndex(uint32_t index) {
- index = index + kBucketMinThreshold;
- if (index > kBucket2msIntervals) {
- index += (index - kBucket2msIntervals);
- }
- if (index > kBucket4msIntervals) {
- // This works because it was already doubled by the above if
- // 1 is added to shift slightly more towards the middle of the bucket
- index += (index - kBucket4msIntervals) + 1;
- }
- return index;
-}
-
-int32_t JankTracker::frameTimeForSlowFrameCountIndex(uint32_t index) {
- return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
-}
-
-JankTracker::JankTracker(const DisplayInfo& displayInfo) {
- // By default this will use malloc memory. It may be moved later to ashmem
- // if there is shared space for it and a request comes in to do that.
- mData = new ProfileData;
- reset();
+JankTracker::JankTracker(ProfileDataContainer* globalData, const DisplayInfo& displayInfo) {
+ mGlobalData = globalData;
nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / displayInfo.fps);
#if USE_HWC2
nsecs_t sfOffset = frameIntervalNanos - (displayInfo.presentationDeadline - 1_ms);
@@ -151,82 +86,6 @@
setFrameInterval(frameIntervalNanos);
}
-JankTracker::~JankTracker() {
- freeData();
-}
-
-void JankTracker::freeData() {
- if (mIsMapped) {
- munmap(mData, sizeof(ProfileData));
- } else {
- delete mData;
- }
- mIsMapped = false;
- mData = nullptr;
-}
-
-void JankTracker::rotateStorage() {
- // If we are mapped we want to stop using the ashmem backend and switch to malloc
- // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
- // If we aren't sitting on top of ashmem then just do a reset() as it's functionally
- // equivalent do a free, malloc, reset.
- if (mIsMapped) {
- freeData();
- mData = new ProfileData;
- }
- reset();
-}
-
-void JankTracker::switchStorageToAshmem(int ashmemfd) {
- int regionSize = ashmem_get_size_region(ashmemfd);
- if (regionSize < 0) {
- int err = errno;
- ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err));
- return;
- }
- if (regionSize < static_cast<int>(sizeof(ProfileData))) {
- ALOGW("Ashmem region is too small! Received %d, required %u",
- regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
- return;
- }
- ProfileData* newData = reinterpret_cast<ProfileData*>(
- mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE,
- MAP_SHARED, ashmemfd, 0));
- if (newData == MAP_FAILED) {
- int err = errno;
- ALOGW("Failed to move profile data to ashmem fd %d, error = %d",
- ashmemfd, err);
- return;
- }
-
- // The new buffer may have historical data that we want to build on top of
- // But let's make sure we don't overflow Just In Case
- uint32_t divider = 0;
- if (newData->totalFrameCount > (1 << 24)) {
- divider = 4;
- }
- for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) {
- newData->jankTypeCounts[i] >>= divider;
- newData->jankTypeCounts[i] += mData->jankTypeCounts[i];
- }
- for (size_t i = 0; i < mData->frameCounts.size(); i++) {
- newData->frameCounts[i] >>= divider;
- newData->frameCounts[i] += mData->frameCounts[i];
- }
- newData->jankFrameCount >>= divider;
- newData->jankFrameCount += mData->jankFrameCount;
- newData->totalFrameCount >>= divider;
- newData->totalFrameCount += mData->totalFrameCount;
- if (newData->statStartTime > mData->statStartTime
- || newData->statStartTime == 0) {
- newData->statStartTime = mData->statStartTime;
- }
-
- freeData();
- mData = newData;
- mIsMapped = true;
-}
-
void JankTracker::setFrameInterval(nsecs_t frameInterval) {
mFrameInterval = frameInterval;
mThresholds[kMissedVsync] = 1;
@@ -250,8 +109,7 @@
}
-void JankTracker::addFrame(const FrameInfo& frame) {
- mData->totalFrameCount++;
+void JankTracker::finishFrame(const FrameInfo& frame) {
// Fast-path for jank-free frames
int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
if (mDequeueTimeForgiveness
@@ -271,11 +129,11 @@
}
}
LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
- uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
- LOG_ALWAYS_FATAL_IF(framebucket < 0, "framebucket < 0 (%u)", framebucket);
+ mData->reportFrame(totalDuration);
+ (*mGlobalData)->reportFrame(totalDuration);
+
// Keep the fast path as fast as possible.
if (CC_LIKELY(totalDuration < mFrameInterval)) {
- mData->frameCounts[framebucket]++;
return;
}
@@ -284,22 +142,14 @@
return;
}
- if (framebucket <= mData->frameCounts.size()) {
- mData->frameCounts[framebucket]++;
- } else {
- framebucket = (ns2ms(totalDuration) - kSlowFrameBucketStartMs)
- / kSlowFrameBucketIntervalMs;
- framebucket = std::min(framebucket,
- static_cast<uint32_t>(mData->slowFrameCounts.size() - 1));
- mData->slowFrameCounts[framebucket]++;
- }
-
- mData->jankFrameCount++;
+ mData->reportJank();
+ (*mGlobalData)->reportJank();
for (int i = 0; i < NUM_BUCKETS; i++) {
int64_t delta = frame.duration(COMPARISONS[i].start, COMPARISONS[i].end);
if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {
- mData->jankTypeCounts[i]++;
+ mData->reportJankType((JankType) i);
+ (*mGlobalData)->reportJankType((JankType) i);
}
}
}
@@ -320,58 +170,39 @@
if (sFrameStart != FrameInfoIndex::IntendedVsync) {
dprintf(fd, "\nNote: Data has been filtered!");
}
- dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime);
- dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
- dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
- (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f);
- dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50));
- dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
- dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
- dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
- for (int i = 0; i < NUM_BUCKETS; i++) {
- dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]);
- }
- dprintf(fd, "\nHISTOGRAM:");
- for (size_t i = 0; i < data->frameCounts.size(); i++) {
- dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i),
- data->frameCounts[i]);
- }
- for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
- dprintf(fd, " %ums=%u", frameTimeForSlowFrameCountIndex(i),
- data->slowFrameCounts[i]);
- }
+ data->dump(fd);
dprintf(fd, "\n");
}
+void JankTracker::dumpFrames(int fd) {
+ FILE* file = fdopen(fd, "a");
+ fprintf(file, "\n\n---PROFILEDATA---\n");
+ for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
+ fprintf(file, "%s", FrameInfoNames[i].c_str());
+ fprintf(file, ",");
+ }
+ for (size_t i = 0; i < mFrames.size(); i++) {
+ FrameInfo& frame = mFrames[i];
+ if (frame[FrameInfoIndex::SyncStart] == 0) {
+ continue;
+ }
+ fprintf(file, "\n");
+ for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) {
+ fprintf(file, "%" PRId64 ",", frame[i]);
+ }
+ }
+ fprintf(file, "\n---PROFILEDATA---\n\n");
+ fflush(file);
+}
+
void JankTracker::reset() {
- mData->jankTypeCounts.fill(0);
- mData->frameCounts.fill(0);
- mData->slowFrameCounts.fill(0);
- mData->totalFrameCount = 0;
- mData->jankFrameCount = 0;
- mData->statStartTime = systemTime(CLOCK_MONOTONIC);
+ mFrames.clear();
+ mData->reset();
+ (*mGlobalData)->reset();
sFrameStart = Properties::filterOutTestOverhead
? FrameInfoIndex::HandleInputStart
: FrameInfoIndex::IntendedVsync;
}
-uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
- int pos = percentile * data->totalFrameCount / 100;
- int remaining = data->totalFrameCount - pos;
- for (int i = data->slowFrameCounts.size() - 1; i >= 0; i--) {
- remaining -= data->slowFrameCounts[i];
- if (remaining <= 0) {
- return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
- }
- }
- for (int i = data->frameCounts.size() - 1; i >= 0; i--) {
- remaining -= data->frameCounts[i];
- if (remaining <= 0) {
- return frameTimeForFrameCountIndex(i);
- }
- }
- return 0;
-}
-
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 6ff5d89..e56c079 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -17,6 +17,8 @@
#define JANKTRACKER_H_
#include "FrameInfo.h"
+#include "ProfileData.h"
+#include "ProfileDataContainer.h"
#include "renderthread/TimeLord.h"
#include "utils/RingBuffer.h"
@@ -29,31 +31,6 @@
namespace android {
namespace uirenderer {
-enum JankType {
- kMissedVsync = 0,
- kHighInputLatency,
- kSlowUI,
- kSlowSync,
- kSlowRT,
-
- // must be last
- NUM_BUCKETS,
-};
-
-// Try to keep as small as possible, should match ASHMEM_SIZE in
-// GraphicsStatsService.java
-struct ProfileData {
- std::array<uint32_t, NUM_BUCKETS> jankTypeCounts;
- // See comments on kBucket* constants for what this holds
- std::array<uint32_t, 57> frameCounts;
- // Holds a histogram of frame times in 50ms increments from 150ms to 5s
- std::array<uint16_t, 97> slowFrameCounts;
-
- uint32_t totalFrameCount;
- uint32_t jankFrameCount;
- nsecs_t statStartTime;
-};
-
enum class JankTrackerType {
// The default, means there's no description set
Generic,
@@ -72,31 +49,27 @@
// TODO: Replace DrawProfiler with this
class JankTracker {
public:
- explicit JankTracker(const DisplayInfo& displayInfo);
- ~JankTracker();
+ explicit JankTracker(ProfileDataContainer* globalData, const DisplayInfo& displayInfo);
void setDescription(JankTrackerType type, const std::string&& name) {
mDescription.type = type;
mDescription.name = name;
}
- void addFrame(const FrameInfo& frame);
+ FrameInfo* startFrame() { return &mFrames.next(); }
+ void finishFrame(const FrameInfo& frame);
- void dump(int fd) { dumpData(fd, &mDescription, mData); }
+ void dumpStats(int fd) { dumpData(fd, &mDescription, mData.get()); }
+ void dumpFrames(int fd);
void reset();
- void rotateStorage();
- void switchStorageToAshmem(int ashmemfd);
-
- uint32_t findPercentile(int p) { return findPercentile(mData, p); }
- static int32_t frameTimeForFrameCountIndex(uint32_t index);
- static int32_t frameTimeForSlowFrameCountIndex(uint32_t index);
+ // Exposed for FrameInfoVisualizer
+ // TODO: Figure out a better way to handle this
+ RingBuffer<FrameInfo, 120>& frames() { return mFrames; }
private:
- void freeData();
void setFrameInterval(nsecs_t frameIntervalNanos);
- static uint32_t findPercentile(const ProfileData* data, int p);
static void dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data);
std::array<int64_t, NUM_BUCKETS> mThresholds;
@@ -109,9 +82,12 @@
// This is only used if we are in pipelined mode and are using HWC2,
// otherwise it's 0.
nsecs_t mDequeueTimeForgiveness = 0;
- ProfileData* mData;
- bool mIsMapped = false;
+ ProfileDataContainer mData;
+ ProfileDataContainer* mGlobalData;
ProfileDataDescription mDescription;
+
+ // Ring buffer large enough for 2 seconds worth of frames
+ RingBuffer<FrameInfo, 120> mFrames;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
new file mode 100644
index 0000000..a295c5d
--- /dev/null
+++ b/libs/hwui/ProfileData.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProfileData.h"
+
+#include <cinttypes>
+
+namespace android {
+namespace uirenderer {
+
+static const char* JANK_TYPE_NAMES[] = {
+ "Missed Vsync",
+ "High input latency",
+ "Slow UI thread",
+ "Slow bitmap uploads",
+ "Slow issue draw commands",
+};
+
+// The bucketing algorithm controls so to speak
+// If a frame is <= to this it goes in bucket 0
+static const uint32_t kBucketMinThreshold = 5;
+// If a frame is > this, start counting in increments of 2ms
+static const uint32_t kBucket2msIntervals = 32;
+// If a frame is > this, start counting in increments of 4ms
+static const uint32_t kBucket4msIntervals = 48;
+
+// The interval of the slow frame histogram
+static const uint32_t kSlowFrameBucketIntervalMs = 50;
+// The start point of the slow frame bucket in ms
+static const uint32_t kSlowFrameBucketStartMs = 150;
+
+// This will be called every frame, performance sensitive
+// Uses bit twiddling to avoid branching while achieving the packing desired
+static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) {
+ uint32_t index = static_cast<uint32_t>(ns2ms(frameTime));
+ // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
+ // of negating 1 (twos compliment, yaay) else mask will be 0
+ uint32_t mask = -(index > kBucketMinThreshold);
+ // If index > threshold, this will essentially perform:
+ // amountAboveThreshold = index - threshold;
+ // index = threshold + (amountAboveThreshold / 2)
+ // However if index is <= this will do nothing. It will underflow, do
+ // a right shift by 0 (no-op), then overflow back to the original value
+ index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals))
+ + kBucket4msIntervals;
+ index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals))
+ + kBucket2msIntervals;
+ // If index was < minThreshold at the start of all this it's going to
+ // be a pretty garbage value right now. However, mask is 0 so we'll end
+ // up with the desired result of 0.
+ index = (index - kBucketMinThreshold) & mask;
+ return index;
+}
+
+// Only called when dumping stats, less performance sensitive
+uint32_t ProfileData::frameTimeForFrameCountIndex(uint32_t index) {
+ index = index + kBucketMinThreshold;
+ if (index > kBucket2msIntervals) {
+ index += (index - kBucket2msIntervals);
+ }
+ if (index > kBucket4msIntervals) {
+ // This works because it was already doubled by the above if
+ // 1 is added to shift slightly more towards the middle of the bucket
+ index += (index - kBucket4msIntervals) + 1;
+ }
+ return index;
+}
+
+uint32_t ProfileData::frameTimeForSlowFrameCountIndex(uint32_t index) {
+ return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
+}
+
+void ProfileData::mergeWith(const ProfileData& other) {
+ // Make sure we don't overflow Just In Case
+ uint32_t divider = 0;
+ if (mTotalFrameCount > (1 << 24)) {
+ divider = 4;
+ }
+ for (size_t i = 0; i < other.mJankTypeCounts.size(); i++) {
+ mJankTypeCounts[i] >>= divider;
+ mJankTypeCounts[i] += other.mJankTypeCounts[i];
+ }
+ for (size_t i = 0; i < other.mFrameCounts.size(); i++) {
+ mFrameCounts[i] >>= divider;
+ mFrameCounts[i] += other.mFrameCounts[i];
+ }
+ mJankFrameCount >>= divider;
+ mJankFrameCount += other.mJankFrameCount;
+ mTotalFrameCount >>= divider;
+ mTotalFrameCount += other.mTotalFrameCount;
+ if (mStatStartTime > other.mStatStartTime
+ || mStatStartTime == 0) {
+ mStatStartTime = other.mStatStartTime;
+ }
+}
+
+void ProfileData::dump(int fd) const {
+ dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime);
+ dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount);
+ dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount,
+ (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f);
+ dprintf(fd, "\n50th percentile: %ums", findPercentile(50));
+ dprintf(fd, "\n90th percentile: %ums", findPercentile(90));
+ dprintf(fd, "\n95th percentile: %ums", findPercentile(95));
+ dprintf(fd, "\n99th percentile: %ums", findPercentile(99));
+ for (int i = 0; i < NUM_BUCKETS; i++) {
+ dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], mJankTypeCounts[i]);
+ }
+ dprintf(fd, "\nHISTOGRAM:");
+ histogramForEach([fd](HistogramEntry entry) {
+ dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
+ });
+}
+
+uint32_t ProfileData::findPercentile(int percentile) const {
+ int pos = percentile * mTotalFrameCount / 100;
+ int remaining = mTotalFrameCount - pos;
+ for (int i = mSlowFrameCounts.size() - 1; i >= 0; i--) {
+ remaining -= mSlowFrameCounts[i];
+ if (remaining <= 0) {
+ return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
+ }
+ }
+ for (int i = mFrameCounts.size() - 1; i >= 0; i--) {
+ remaining -= mFrameCounts[i];
+ if (remaining <= 0) {
+ return frameTimeForFrameCountIndex(i);
+ }
+ }
+ return 0;
+}
+
+void ProfileData::reset() {
+ mJankTypeCounts.fill(0);
+ mFrameCounts.fill(0);
+ mSlowFrameCounts.fill(0);
+ mTotalFrameCount = 0;
+ mJankFrameCount = 0;
+ mStatStartTime = systemTime(CLOCK_MONOTONIC);
+}
+
+void ProfileData::reportFrame(int64_t duration) {
+ mTotalFrameCount++;
+ uint32_t framebucket = frameCountIndexForFrameTime(duration);
+ if (framebucket <= mFrameCounts.size()) {
+ mFrameCounts[framebucket]++;
+ } else {
+ framebucket = (ns2ms(duration) - kSlowFrameBucketStartMs) / kSlowFrameBucketIntervalMs;
+ framebucket = std::min(framebucket, static_cast<uint32_t>(mSlowFrameCounts.size() - 1));
+ mSlowFrameCounts[framebucket]++;
+ }
+}
+
+void ProfileData::histogramForEach(const std::function<void(HistogramEntry)>& callback) const {
+ for (size_t i = 0; i < mFrameCounts.size(); i++) {
+ callback(HistogramEntry{frameTimeForFrameCountIndex(i), mFrameCounts[i]});
+ }
+ for (size_t i = 0; i < mSlowFrameCounts.size(); i++) {
+ callback(HistogramEntry{frameTimeForSlowFrameCountIndex(i), mSlowFrameCounts[i]});
+ }
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
new file mode 100644
index 0000000..d53ee29
--- /dev/null
+++ b/libs/hwui/ProfileData.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "utils/Macros.h"
+
+#include <utils/Timers.h>
+
+#include <array>
+#include <functional>
+#include <tuple>
+
+namespace android {
+namespace uirenderer {
+
+enum JankType {
+ kMissedVsync = 0,
+ kHighInputLatency,
+ kSlowUI,
+ kSlowSync,
+ kSlowRT,
+
+ // must be last
+ NUM_BUCKETS,
+};
+
+// For testing
+class MockProfileData;
+
+// Try to keep as small as possible, should match ASHMEM_SIZE in
+// GraphicsStatsService.java
+class ProfileData {
+ PREVENT_COPY_AND_ASSIGN(ProfileData);
+
+public:
+ ProfileData() { reset(); }
+
+ void reset();
+ void mergeWith(const ProfileData& other);
+ void dump(int fd) const;
+ uint32_t findPercentile(int percentile) const;
+
+ void reportFrame(int64_t duration);
+ void reportJank() { mJankFrameCount++; }
+ void reportJankType(JankType type) { mJankTypeCounts[static_cast<int>(type)]++; }
+
+ uint32_t totalFrameCount() const { return mTotalFrameCount; }
+ uint32_t jankFrameCount() const { return mJankFrameCount; }
+ nsecs_t statsStartTime() const { return mStatStartTime; }
+ uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+
+ struct HistogramEntry {
+ uint32_t renderTimeMs;
+ uint32_t frameCount;
+ };
+ void histogramForEach(const std::function<void(HistogramEntry)>& callback) const;
+
+ constexpr static int HistogramSize() {
+ return std::tuple_size<decltype(ProfileData::mFrameCounts)>::value
+ + std::tuple_size<decltype(ProfileData::mSlowFrameCounts)>::value;
+ }
+
+ // Visible for testing
+ static uint32_t frameTimeForFrameCountIndex(uint32_t index);
+ static uint32_t frameTimeForSlowFrameCountIndex(uint32_t index);
+
+private:
+ // Open our guts up to unit tests
+ friend class MockProfileData;
+
+ std::array <uint32_t, NUM_BUCKETS> mJankTypeCounts;
+ // See comments on kBucket* constants for what this holds
+ std::array<uint32_t, 57> mFrameCounts;
+ // Holds a histogram of frame times in 50ms increments from 150ms to 5s
+ std::array<uint16_t, 97> mSlowFrameCounts;
+
+ uint32_t mTotalFrameCount;
+ uint32_t mJankFrameCount;
+ nsecs_t mStatStartTime;
+};
+
+// For testing
+class MockProfileData : public ProfileData {
+public:
+ std::array<uint32_t, NUM_BUCKETS>& editJankTypeCounts() { return mJankTypeCounts; }
+ std::array<uint32_t, 57>& editFrameCounts() { return mFrameCounts; }
+ std::array<uint16_t, 97>& editSlowFrameCounts() { return mSlowFrameCounts; }
+ uint32_t& editTotalFrameCount() { return mTotalFrameCount; }
+ uint32_t& editJankFrameCount() { return mJankFrameCount; }
+ nsecs_t& editStatStartTime() { return mStatStartTime; }
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
diff --git a/libs/hwui/ProfileDataContainer.cpp b/libs/hwui/ProfileDataContainer.cpp
new file mode 100644
index 0000000..cbf3eb3
--- /dev/null
+++ b/libs/hwui/ProfileDataContainer.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProfileDataContainer.h"
+
+#include <log/log.h>
+#include <cutils/ashmem.h>
+
+#include <sys/mman.h>
+
+namespace android {
+namespace uirenderer {
+
+void ProfileDataContainer::freeData() {
+ if (mIsMapped) {
+ munmap(mData, sizeof(ProfileData));
+ } else {
+ delete mData;
+ }
+ mIsMapped = false;
+ mData = nullptr;
+}
+
+void ProfileDataContainer::rotateStorage() {
+ // If we are mapped we want to stop using the ashmem backend and switch to malloc
+ // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
+ // If we aren't sitting on top of ashmem then just do a reset() as it's functionally
+ // equivalent do a free, malloc, reset.
+ if (mIsMapped) {
+ freeData();
+ mData = new ProfileData;
+ }
+ mData->reset();
+}
+
+void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
+ int regionSize = ashmem_get_size_region(ashmemfd);
+ if (regionSize < 0) {
+ int err = errno;
+ ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err));
+ return;
+ }
+ if (regionSize < static_cast<int>(sizeof(ProfileData))) {
+ ALOGW("Ashmem region is too small! Received %d, required %u",
+ regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
+ return;
+ }
+ ProfileData* newData = reinterpret_cast<ProfileData*>(
+ mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE,
+ MAP_SHARED, ashmemfd, 0));
+ if (newData == MAP_FAILED) {
+ int err = errno;
+ ALOGW("Failed to move profile data to ashmem fd %d, error = %d",
+ ashmemfd, err);
+ return;
+ }
+
+ newData->mergeWith(*mData);
+ freeData();
+ mData = newData;
+ mIsMapped = true;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/ProfileDataContainer.h b/libs/hwui/ProfileDataContainer.h
new file mode 100644
index 0000000..d2de241
--- /dev/null
+++ b/libs/hwui/ProfileDataContainer.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "ProfileData.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class ProfileDataContainer {
+ PREVENT_COPY_AND_ASSIGN(ProfileDataContainer);
+public:
+ explicit ProfileDataContainer() {}
+
+ ~ProfileDataContainer() { freeData(); }
+
+ void rotateStorage();
+ void switchStorageToAshmem(int ashmemfd);
+
+ ProfileData* get() { return mData; }
+ ProfileData* operator->() { return mData; }
+
+private:
+ void freeData();
+
+ // By default this will use malloc memory. It may be moved later to ashmem
+ // if there is shared space for it and a request comes in to do that.
+ ProfileData* mData = new ProfileData;
+ bool mIsMapped = false;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 7799248..5d7f594 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -142,8 +142,8 @@
: mRenderThread(thread)
, mOpaque(!translucent)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
- , mJankTracker(thread.mainDisplayInfo())
- , mProfiler(mFrames)
+ , mJankTracker(&thread.globalProfileData(), thread.mainDisplayInfo())
+ , mProfiler(mJankTracker.frames())
, mContentDrawBounds(0, 0, 0, 0)
, mRenderPipeline(std::move(renderPipeline)) {
rootRenderNode->makeRoot();
@@ -321,7 +321,7 @@
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
if (!wasSkipped(mCurrentFrameInfo)) {
- mCurrentFrameInfo = &mFrames.next();
+ mCurrentFrameInfo = mJankTracker.startFrame();
}
mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
@@ -485,8 +485,7 @@
}
#endif
- mJankTracker.addFrame(*mCurrentFrameInfo);
- mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
+ mJankTracker.finishFrame(*mCurrentFrameInfo);
if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
}
@@ -625,30 +624,12 @@
}
void CanvasContext::dumpFrames(int fd) {
- mJankTracker.dump(fd);
- FILE* file = fdopen(fd, "a");
- fprintf(file, "\n\n---PROFILEDATA---\n");
- for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
- fprintf(file, "%s", FrameInfoNames[i].c_str());
- fprintf(file, ",");
- }
- for (size_t i = 0; i < mFrames.size(); i++) {
- FrameInfo& frame = mFrames[i];
- if (frame[FrameInfoIndex::SyncStart] == 0) {
- continue;
- }
- fprintf(file, "\n");
- for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) {
- fprintf(file, "%" PRId64 ",", frame[i]);
- }
- }
- fprintf(file, "\n---PROFILEDATA---\n\n");
- fflush(file);
+ mJankTracker.dumpStats(fd);
+ mJankTracker.dumpFrames(fd);
}
void CanvasContext::resetFrameStats() {
- mFrames.clear();
- mRenderThread.jankTracker().reset();
+ mJankTracker.reset();
}
void CanvasContext::setName(const std::string&& name) {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b1f4050..aa6d2f3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -29,7 +29,6 @@
#include "RenderNode.h"
#include "thread/Task.h"
#include "thread/TaskProcessor.h"
-#include "utils/RingBuffer.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
@@ -253,8 +252,6 @@
std::vector< sp<RenderNode> > mRenderNodes;
FrameInfo* mCurrentFrameInfo = nullptr;
- // Ring buffer large enough for 2 seconds worth of frames
- RingBuffer<FrameInfo, 120> mFrames;
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 80c2955..370cf52 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -427,12 +427,12 @@
if (args->dumpFlags & DumpFlags::FrameStats) {
args->context->dumpFrames(args->fd);
}
+ if (args->dumpFlags & DumpFlags::JankStats) {
+ args->thread->globalProfileData()->dump(args->fd);
+ }
if (args->dumpFlags & DumpFlags::Reset) {
args->context->resetFrameStats();
}
- if (args->dumpFlags & DumpFlags::JankStats) {
- args->thread->jankTracker().dump(args->fd);
- }
return nullptr;
}
@@ -458,7 +458,7 @@
CREATE_BRIDGE2(frameTimePercentile, RenderThread* thread, int percentile) {
return reinterpret_cast<void*>(static_cast<uintptr_t>(
- args->thread->jankTracker().findPercentile(args->percentile)));
+ args->thread->globalProfileData()->findPercentile(args->percentile)));
}
uint32_t RenderProxy::frameTimePercentile(int p) {
@@ -483,7 +483,7 @@
}
CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {
- args->thread->jankTracker().switchStorageToAshmem(args->fd);
+ args->thread->globalProfileData().switchStorageToAshmem(args->fd);
close(args->fd);
return nullptr;
}
@@ -497,7 +497,7 @@
}
CREATE_BRIDGE1(rotateProcessStatsBuffer, RenderThread* thread) {
- args->thread->jankTracker().rotateStorage();
+ args->thread->globalProfileData().rotateStorage();
return nullptr;
}
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 13af2c4..72a428f 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -200,13 +200,12 @@
initializeDisplayEventReceiver();
mEglManager = new EglManager(*this);
mRenderState = new RenderState(*this);
- mJankTracker = new JankTracker(mDisplayInfo);
mVkManager = new VulkanManager(*this);
mCacheManager = new CacheManager(mDisplayInfo);
}
void RenderThread::dumpGraphicsMemory(int fd) {
- jankTracker().dump(fd);
+ globalProfileData()->dump(fd);
String8 cachesOutput;
String8 pipeline;
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index d984257..bef47b3 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -97,7 +97,7 @@
TimeLord& timeLord() { return mTimeLord; }
RenderState& renderState() const { return *mRenderState; }
EglManager& eglManager() const { return *mEglManager; }
- JankTracker& jankTracker() { return *mJankTracker; }
+ ProfileDataContainer& globalProfileData() { return mGlobalProfileData; }
Readback& readback();
const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; }
@@ -160,7 +160,7 @@
RenderState* mRenderState;
EglManager* mEglManager;
- JankTracker* mJankTracker = nullptr;
+ ProfileDataContainer mGlobalProfileData;
Readback* mReadback = nullptr;
sk_sp<GrContext> mGrContext;
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 87eaa6a..3a77195 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -38,9 +38,7 @@
constexpr int32_t sHeaderSize = 4;
static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
-constexpr int sHistogramSize =
- std::tuple_size<decltype(ProfileData::frameCounts)>::value +
- std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value;
+constexpr int sHistogramSize = ProfileData::HistogramSize();
static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto,
const std::string& package, int versionCode, int64_t startTime, int64_t endTime,
@@ -172,18 +170,18 @@
proto->set_package_name(package);
proto->set_version_code(versionCode);
auto summary = proto->mutable_summary();
- summary->set_total_frames(summary->total_frames() + data->totalFrameCount);
- summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount);
+ summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
+ summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
summary->set_missed_vsync_count(
- summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]);
+ summary->missed_vsync_count() + data->jankTypeCount(kMissedVsync));
summary->set_high_input_latency_count(
- summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]);
+ summary->high_input_latency_count() + data->jankTypeCount(kHighInputLatency));
summary->set_slow_ui_thread_count(
- summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]);
+ summary->slow_ui_thread_count() + data->jankTypeCount(kSlowUI));
summary->set_slow_bitmap_upload_count(
- summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]);
+ summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync));
summary->set_slow_draw_count(
- summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]);
+ summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
bool creatingHistogram = false;
if (proto->histogram_size() == 0) {
@@ -193,33 +191,20 @@
LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d",
proto->histogram_size(), sHistogramSize);
}
- for (size_t i = 0; i < data->frameCounts.size(); i++) {
+ int index = 0;
+ data->histogramForEach([&](ProfileData::HistogramEntry entry) {
service::GraphicsStatsHistogramBucketProto* bucket;
- int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i);
if (creatingHistogram) {
bucket = proto->add_histogram();
- bucket->set_render_millis(renderTime);
+ bucket->set_render_millis(entry.renderTimeMs);
} else {
- bucket = proto->mutable_histogram(i);
- LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
- "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
+ bucket = proto->mutable_histogram(index);
+ LOG_ALWAYS_FATAL_IF(bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs),
+ "Frame time mistmatch %d vs. %u", bucket->render_millis(), entry.renderTimeMs);
}
- bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]);
- }
- for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
- service::GraphicsStatsHistogramBucketProto* bucket;
- int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i);
- if (creatingHistogram) {
- bucket = proto->add_histogram();
- bucket->set_render_millis(renderTime);
- } else {
- constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value;
- bucket = proto->mutable_histogram(offset + i);
- LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
- "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
- }
- bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]);
- }
+ bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
+ index++;
+ });
}
static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) {
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
index f6f7337..fda3a79 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -61,17 +61,17 @@
TEST(GraphicsStats, saveLoad) {
std::string path = findRootPath() + "/test_saveLoad";
std::string packageName = "com.test.saveLoad";
- ProfileData mockData;
- mockData.jankFrameCount = 20;
- mockData.totalFrameCount = 100;
- mockData.statStartTime = 10000;
+ MockProfileData mockData;
+ mockData.editJankFrameCount() = 20;
+ mockData.editTotalFrameCount() = 100;
+ mockData.editStatStartTime() = 10000;
// Fill with patterned data we can recognize but which won't map to a
// memset or basic for iteration count
- for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
- mockData.frameCounts[i] = ((i % 10) + 1) * 2;
+ for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
+ mockData.editFrameCounts()[i] = ((i % 10) + 1) * 2;
}
- for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
- mockData.slowFrameCounts[i] = (i % 5) + 1;
+ for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
+ mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
}
GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
service::GraphicsStatsProto loadedProto;
@@ -87,17 +87,17 @@
ASSERT_TRUE(loadedProto.has_summary());
EXPECT_EQ(20, loadedProto.summary().janky_frames());
EXPECT_EQ(100, loadedProto.summary().total_frames());
- EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(),
+ EXPECT_EQ(mockData.editFrameCounts().size() + mockData.editSlowFrameCounts().size(),
(size_t) loadedProto.histogram_size());
for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) {
int expectedCount, expectedBucket;
- if (i < mockData.frameCounts.size()) {
+ if (i < mockData.editFrameCounts().size()) {
expectedCount = ((i % 10) + 1) * 2;
- expectedBucket = JankTracker::frameTimeForFrameCountIndex(i);
+ expectedBucket = ProfileData::frameTimeForFrameCountIndex(i);
} else {
- int temp = i - mockData.frameCounts.size();
+ int temp = i - mockData.editFrameCounts().size();
expectedCount = (temp % 5) + 1;
- expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp);
+ expectedBucket = ProfileData::frameTimeForSlowFrameCountIndex(temp);
}
EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
@@ -107,26 +107,26 @@
TEST(GraphicsStats, merge) {
std::string path = findRootPath() + "/test_merge";
std::string packageName = "com.test.merge";
- ProfileData mockData;
- mockData.jankFrameCount = 20;
- mockData.totalFrameCount = 100;
- mockData.statStartTime = 10000;
+ MockProfileData mockData;
+ mockData.editJankFrameCount() = 20;
+ mockData.editTotalFrameCount() = 100;
+ mockData.editStatStartTime() = 10000;
// Fill with patterned data we can recognize but which won't map to a
// memset or basic for iteration count
- for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
- mockData.frameCounts[i] = ((i % 10) + 1) * 2;
+ for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
+ mockData.editFrameCounts()[i] = ((i % 10) + 1) * 2;
}
- for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
- mockData.slowFrameCounts[i] = (i % 5) + 1;
+ for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
+ mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
}
GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
- mockData.jankFrameCount = 50;
- mockData.totalFrameCount = 500;
- for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
- mockData.frameCounts[i] = (i % 5) + 1;
+ mockData.editJankFrameCount() = 50;
+ mockData.editTotalFrameCount() = 500;
+ for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
+ mockData.editFrameCounts()[i] = (i % 5) + 1;
}
- for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
- mockData.slowFrameCounts[i] = ((i % 10) + 1) * 2;
+ for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
+ mockData.editSlowFrameCounts()[i] = ((i % 10) + 1) * 2;
}
GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData);
@@ -143,19 +143,19 @@
ASSERT_TRUE(loadedProto.has_summary());
EXPECT_EQ(20 + 50, loadedProto.summary().janky_frames());
EXPECT_EQ(100 + 500, loadedProto.summary().total_frames());
- EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(),
+ EXPECT_EQ(mockData.editFrameCounts().size() + mockData.editSlowFrameCounts().size(),
(size_t) loadedProto.histogram_size());
for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) {
int expectedCount, expectedBucket;
- if (i < mockData.frameCounts.size()) {
+ if (i < mockData.editFrameCounts().size()) {
expectedCount = ((i % 10) + 1) * 2;
expectedCount += (i % 5) + 1;
- expectedBucket = JankTracker::frameTimeForFrameCountIndex(i);
+ expectedBucket = ProfileData::frameTimeForFrameCountIndex(i);
} else {
- int temp = i - mockData.frameCounts.size();
+ int temp = i - mockData.editFrameCounts().size();
expectedCount = (temp % 5) + 1;
expectedCount += ((temp % 10) + 1) * 2;
- expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp);
+ expectedBucket = ProfileData::frameTimeForSlowFrameCountIndex(temp);
}
EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a430288..064cc84 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -465,6 +465,8 @@
<string name="wifi_allow_scan_with_traffic">Always allow Wi\u2011Fi Roam Scans</string>
<!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] -->
<string name="mobile_data_always_on">Mobile data always active</string>
+ <!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
+ <string name="tethering_hardware_offload">Tethering hardware acceleration</string>
<!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
<string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
<!-- Setting Checkbox title for enabling Bluetooth inband ringing -->
@@ -534,6 +536,7 @@
<!-- Setting Checkbox title whether to enable view attribute inspection -->
<string name="debug_view_attributes">Enable view attribute inspection</string>
<string name="mobile_data_always_on_summary">Always keep mobile data active, even when Wi\u2011Fi is active (for fast network switching).</string>
+ <string name="tethering_hardware_offload_summary">Use tethering hardware acceleration if available</string>
<!-- Title of warning dialog about the implications of enabling USB debugging -->
<string name="adb_warning_title">Allow USB debugging?</string>
<!-- Warning text to user about the implications of enabling USB debugging -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index e4e0f7f..c4cbc2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -36,7 +36,8 @@
}
public boolean applyNewConfig(Resources res) {
- int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
+ int configChanges = mLastConfiguration.updateFrom(
+ Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
if (densityChanged || (configChanges & (mFlags)) != 0) {
mLastDensity = res.getDisplayMetrics().densityDpi;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 80b943c..d07da93 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -102,6 +102,7 @@
// Fine-grained state broadcasts
addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
+ addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());
// Dock event broadcasts
addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
@@ -376,6 +377,17 @@
}
}
}
+
+ private class BatteryLevelChangedHandler implements Handler {
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice != null) {
+ cachedDevice.refresh();
+ }
+ }
+ }
+
boolean readPairedDevices() {
Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
if (bondedDevices == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e279a09..4bb4b40 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -368,6 +368,15 @@
return mDevice;
}
+ /**
+ * Convenience method that can be mocked - it lets tests avoid having to call getDevice() which
+ * causes problems in tests since BluetoothDevice is final and cannot be mocked.
+ * @return the address of this device
+ */
+ public String getAddress() {
+ return mDevice.getAddress();
+ }
+
public String getName() {
return mName;
}
@@ -410,6 +419,14 @@
}
}
+ /**
+ * Get battery level from remote device
+ * @return battery level in percentage [0-100], or {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+ */
+ public int getBatteryLevel() {
+ return mDevice.getBatteryLevel();
+ }
+
void refresh() {
dispatchAttributesChanged();
}
@@ -828,7 +845,7 @@
/**
* @return resource for string that discribes the connection state of this device.
*/
- public int getConnectionSummary() {
+ public String getConnectionSummary() {
boolean profileConnected = false; // at least one profile is connected
boolean a2dpNotConnected = false; // A2DP is preferred but not connected
boolean hfpNotConnected = false; // HFP is preferred but not connected
@@ -839,7 +856,7 @@
switch (connectionStatus) {
case BluetoothProfile.STATE_CONNECTING:
case BluetoothProfile.STATE_DISCONNECTING:
- return Utils.getConnectionStateSummary(connectionStatus);
+ return mContext.getString(Utils.getConnectionStateSummary(connectionStatus));
case BluetoothProfile.STATE_CONNECTED:
profileConnected = true;
@@ -859,18 +876,54 @@
}
}
+ String batteryLevelPercentageString = null;
+ // Android framework should only set mBatteryLevel to valid range [0-100] or
+ // BluetoothDevice.BATTERY_LEVEL_UNKNOWN, any other value should be a framework bug.
+ // Thus assume here that if value is not BluetoothDevice.BATTERY_LEVEL_UNKNOWN, it must
+ // be valid
+ final int batteryLevel = getBatteryLevel();
+ if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ // TODO: name com.android.settingslib.bluetooth.Utils something different
+ batteryLevelPercentageString =
+ com.android.settingslib.Utils.formatPercentage(batteryLevel);
+ }
+
if (profileConnected) {
if (a2dpNotConnected && hfpNotConnected) {
- return R.string.bluetooth_connected_no_headset_no_a2dp;
+ if (batteryLevelPercentageString != null) {
+ return mContext.getString(
+ R.string.bluetooth_connected_no_headset_no_a2dp_battery_level,
+ batteryLevelPercentageString);
+ } else {
+ return mContext.getString(R.string.bluetooth_connected_no_headset_no_a2dp);
+ }
+
} else if (a2dpNotConnected) {
- return R.string.bluetooth_connected_no_a2dp;
+ if (batteryLevelPercentageString != null) {
+ return mContext.getString(R.string.bluetooth_connected_no_a2dp_battery_level,
+ batteryLevelPercentageString);
+ } else {
+ return mContext.getString(R.string.bluetooth_connected_no_a2dp);
+ }
+
} else if (hfpNotConnected) {
- return R.string.bluetooth_connected_no_headset;
+ if (batteryLevelPercentageString != null) {
+ return mContext.getString(R.string.bluetooth_connected_no_headset_battery_level,
+ batteryLevelPercentageString);
+ } else {
+ return mContext.getString(R.string.bluetooth_connected_no_headset);
+ }
} else {
- return R.string.bluetooth_connected;
+ if (batteryLevelPercentageString != null) {
+ return mContext.getString(R.string.bluetooth_connected_battery_level,
+ batteryLevelPercentageString);
+ } else {
+ return mContext.getString(R.string.bluetooth_connected);
+ }
}
}
- return getBondState() == BluetoothDevice.BOND_BONDING ? R.string.bluetooth_pairing : 0;
+ return getBondState() == BluetoothDevice.BOND_BONDING ?
+ mContext.getString(R.string.bluetooth_pairing) : null;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 5529866..d45fe1a 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -34,7 +34,7 @@
/**
* HeadsetProfile handles Bluetooth HFP and Headset profiles.
*/
-public final class HeadsetProfile implements LocalBluetoothProfile {
+public class HeadsetProfile implements LocalBluetoothProfile {
private static final String TAG = "HeadsetProfile";
private static boolean V = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index a9e8db5..d1621da 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -31,7 +31,7 @@
/**
* HidProfile handles Bluetooth HID profile.
*/
-public final class HidProfile implements LocalBluetoothProfile {
+public class HidProfile implements LocalBluetoothProfile {
private static final String TAG = "HidProfile";
private static boolean V = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 4a54451..ec41415 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -1053,7 +1053,8 @@
}
/** Attempt to update the AccessPoint and return true if an update occurred. */
- public boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
+ public boolean update(
+ @Nullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
boolean updated = false;
final int oldLevel = getLevel();
if (info != null && isInfoForThisAccessPoint(config, info)) {
@@ -1088,9 +1089,9 @@
return updated;
}
- void update(WifiConfiguration config) {
+ void update(@Nullable WifiConfiguration config) {
mConfig = config;
- networkId = config.networkId;
+ networkId = config != null ? config.networkId : WifiConfiguration.INVALID_NETWORK_ID;
if (mAccessPointListener != null) {
mAccessPointListener.onAccessPointChanged(this);
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 5c64cff..c08dd6e 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -656,4 +656,28 @@
assertThat(ap.update(newConfig, wifiInfo, networkInfo)).isFalse();
verify(mockListener).onAccessPointChanged(ap);
}
+
+ @Test
+ public void testUpdateWithNullWifiConfiguration_doesNotThrowNPE() {
+ int networkId = 123;
+ int rssi = -55;
+ WifiConfiguration config = new WifiConfiguration();
+ config.networkId = networkId;
+ WifiInfo wifiInfo = new WifiInfo();
+ wifiInfo.setNetworkId(networkId);
+ wifiInfo.setRssi(rssi);
+
+ NetworkInfo networkInfo =
+ new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
+ networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTING, "", "");
+
+ AccessPoint ap = new TestAccessPointBuilder(mContext)
+ .setNetworkInfo(networkInfo)
+ .setNetworkId(networkId)
+ .setRssi(rssi)
+ .setWifiInfo(wifiInfo)
+ .build();
+
+ ap.update(null, wifiInfo, networkInfo);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
new file mode 100644
index 0000000..e2ebbeb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 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.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.R;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, resourceDir =
+ "../../res")
+public class CachedBluetoothDeviceTest {
+ @Mock
+ private LocalBluetoothAdapter mAdapter;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private HeadsetProfile mHfpProfile;
+ @Mock
+ private A2dpProfile mA2dpProfile;
+ @Mock
+ private HidProfile mHidProfile;
+ @Mock
+ private BluetoothDevice mDevice;
+ private CachedBluetoothDevice mCachedDevice;
+ private Context mContext;
+ private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ when(mAdapter.getBluetoothState()).thenReturn(BluetoothAdapter.STATE_ON);
+ when(mHfpProfile.isProfileReady()).thenReturn(true);
+ when(mA2dpProfile.isProfileReady()).thenReturn(true);
+ when(mHidProfile.isProfileReady()).thenReturn(true);
+ mCachedDevice = spy(
+ new CachedBluetoothDevice(mContext, mAdapter, mProfileManager, mDevice));
+ doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
+ }
+
+ /**
+ * Test to verify the current test context object works so that we are not checking null
+ * against null
+ */
+ @Test
+ public void testContextMock() {
+ assertThat(mContext.getString(R.string.bluetooth_connected)).isEqualTo("Connected");
+ }
+
+ @Test
+ public void testGetConnectionSummary_testSingleProfileConnectDisconnect() {
+ // Test without battery level
+ // Set HID profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+ R.string.bluetooth_connected));
+
+ // Set HID profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+ // Test with battery level
+ mBatteryLevel = 10;
+ // Set HID profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+ R.string.bluetooth_connected_battery_level,
+ com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+ // Set HID profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+ // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
+ mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+
+ // Set HID profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+ R.string.bluetooth_connected));
+
+ // Set HID profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+ }
+
+ @Test
+ public void testGetConnectionSummary_testMultipleProfileConnectDisconnect() {
+ mBatteryLevel = 10;
+
+ // Set HFP, A2DP and HID profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+ R.string.bluetooth_connected_battery_level,
+ com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+ // Disconnect HFP only and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+ R.string.bluetooth_connected_no_headset_battery_level,
+ com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+ // Disconnect A2DP only and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+ R.string.bluetooth_connected_no_a2dp_battery_level,
+ com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+ // Disconnect both HFP and A2DP and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+ R.string.bluetooth_connected_no_headset_no_a2dp_battery_level,
+ com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+ // Disconnect all profiles and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 7fed3e8..377fab5 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -545,6 +545,16 @@
*/
@VisibleForTesting
void finishExpanding(boolean forceAbort, float velocity) {
+ finishExpanding(forceAbort, velocity, true /* allowAnimation */);
+ }
+
+ /**
+ * Finish the current expand motion
+ * @param forceAbort whether the expansion should be forcefully aborted and returned to the old
+ * state
+ * @param velocity the velocity this was expanded/ collapsed with
+ */
+ private void finishExpanding(boolean forceAbort, float velocity, boolean allowAnimation) {
if (!mExpanding) return;
if (DEBUG) Log.d(TAG, "scale in finishing on view: " + mResizedView);
@@ -568,7 +578,7 @@
mCallback.expansionStateChanged(false);
int naturalHeight = mScaler.getNaturalHeight();
float targetHeight = nowExpanded ? naturalHeight : mSmallSize;
- if (targetHeight != currentHeight && mEnabled) {
+ if (targetHeight != currentHeight && mEnabled && allowAnimation) {
mScaleAnimation.setFloatValues(targetHeight);
mScaleAnimation.setupStartValues();
final View scaledView = mResizedView;
@@ -622,10 +632,22 @@
}
/**
+ * Use this to abort any pending expansions in progress and force that there will be no
+ * animations.
+ */
+ public void cancelImmediately() {
+ cancel(false /* allowAnimation */);
+ }
+
+ /**
* Use this to abort any pending expansions in progress.
*/
public void cancel() {
- finishExpanding(true /* forceAbort */, 0f /* velocity */);
+ cancel(true /* allowAnimation */);
+ }
+
+ private void cancel(boolean allowAnimation) {
+ finishExpanding(true /* forceAbort */, 0f /* velocity */, allowAnimation);
clearView();
// reset the gesture detector
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 5aaa6c7..180b3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -31,6 +31,7 @@
void dozeTimeTick();
boolean isPowerSaveActive();
boolean isPulsingBlocked();
+ boolean isProvisioned();
void startPendingIntentDismissingKeyguard(PendingIntent intent);
void abortPulsing();
@@ -40,6 +41,7 @@
void onDoubleTap(float x, float y);
+
interface Callback {
default void onNotificationHeadsUp() {}
default void onPowerSaveChanged(boolean active) {}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 9981972..179f5b8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -172,14 +172,6 @@
}
}
- private void onCarMode() {
- mMachine.requestState(DozeMachine.State.FINISH);
- }
-
- private void onPowerSave() {
- mMachine.requestState(DozeMachine.State.FINISH);
- }
-
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
@@ -215,11 +207,10 @@
}
private void checkTriggersAtInit() {
- if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
- onCarMode();
- }
- if (mDozeHost.isPowerSaveActive()) {
- onPowerSave();
+ if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
+ || mDozeHost.isPowerSaveActive()
+ || !mDozeHost.isProvisioned()) {
+ mMachine.requestState(DozeMachine.State.FINISH);
}
}
@@ -355,7 +346,7 @@
requestPulse(DozeLog.PULSE_REASON_INTENT, false /* performedProxCheck */);
}
if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
- onCarMode();
+ mMachine.requestState(DozeMachine.State.FINISH);
}
if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
mDozeSensors.onUserSwitched();
@@ -391,7 +382,7 @@
@Override
public void onPowerSaveChanged(boolean active) {
if (active) {
- onPowerSave();
+ mMachine.requestState(DozeMachine.State.FINISH);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index cdd0ebc..6659650 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -144,22 +144,26 @@
}
public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
- final float fromAlpha = Color.alpha(fromColor);
- final float toAlpha = Color.alpha(toColor);
- final float fromChannel = Color.red(fromColor);
- final float toChannel = Color.red(toColor);
+ if (ValueAnimator.areAnimatorsEnabled()) {
+ final float fromAlpha = Color.alpha(fromColor);
+ final float toAlpha = Color.alpha(toColor);
+ final float fromChannel = Color.red(fromColor);
+ final float toChannel = Color.red(toColor);
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- anim.setDuration(QS_ANIM_LENGTH);
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.setDuration(QS_ANIM_LENGTH);
+ anim.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction);
+ int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction);
- anim.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction);
- int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction);
+ setTint(iv, Color.argb(alpha, channel, channel, channel));
+ });
- setTint(iv, Color.argb(alpha, channel, channel, channel));
- });
- anim.start();
+ anim.start();
+ } else {
+ setTint(iv, toColor);
+ }
}
public static void setTint(ImageView iv, int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 36677a4..14afbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -30,6 +30,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -271,7 +272,14 @@
int state = mController.getMaxConnectionState(device);
if (state == BluetoothProfile.STATE_CONNECTED) {
item.icon = R.drawable.ic_qs_bluetooth_connected;
- item.line2 = mContext.getString(R.string.quick_settings_connected);
+ int batteryLevel = device.getBatteryLevel();
+ if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ item.line2 = mContext.getString(
+ R.string.quick_settings_connected_battery_level,
+ Utils.formatPercentage(batteryLevel));
+ } else {
+ item.line2 = mContext.getString(R.string.quick_settings_connected);
+ }
item.canDisconnect = true;
items.add(connectedDevices, item);
connectedDevices++;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 21dfe8c..968b77f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -400,8 +400,8 @@
view.draw(c);
}
node.end(c);
- return ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight)
- .createGraphicBufferHandle();
+ Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight);
+ return hwBitmap.createGraphicBufferHandle();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 674a61c..df1ffda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -168,8 +168,7 @@
distance = mTranslationOnDown + distance;
distance = Math.max(0, distance);
}
- setTranslation(distance, false /* isReset */, false /* animateReset */,
- false /* force */);
+ setTranslation(distance, false /* isReset */, false /* animateReset */);
}
break;
@@ -374,12 +373,11 @@
targetView.finishAnimation(velocity, mAnimationEndRunnable);
}
- private void setTranslation(float translation, boolean isReset, boolean animateReset,
- boolean force) {
+ private void setTranslation(float translation, boolean isReset, boolean animateReset) {
translation = rightSwipePossible() ? translation : Math.max(0, translation);
translation = leftSwipePossible() ? translation : Math.min(0, translation);
float absTranslation = Math.abs(translation);
- if (translation != mTranslation || isReset || force) {
+ if (translation != mTranslation || isReset) {
KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
float alpha = absTranslation / getMinTranslationAmount();
@@ -394,15 +392,15 @@
boolean slowAnimation = isReset && isBelowFalsingThreshold();
if (!isReset) {
updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
- false, false, force, false);
+ false, false, false, false);
} else {
updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
- animateIcons, slowAnimation, force, forceNoCircleAnimation);
+ animateIcons, slowAnimation, true /* isReset */, forceNoCircleAnimation);
}
updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
- animateIcons, slowAnimation, force, forceNoCircleAnimation);
+ animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
updateIcon(mCenterIcon, 0.0f, fadeOutAlpha * mCenterIcon.getRestingAlpha(),
- animateIcons, slowAnimation, force, forceNoCircleAnimation);
+ animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
mTranslation = translation;
}
@@ -510,12 +508,8 @@
}
public void reset(boolean animate) {
- reset(animate, false /* force */);
- }
-
- public void reset(boolean animate, boolean force) {
cancelAnimation();
- setTranslation(0.0f, true, animate, force);
+ setTranslation(0.0f, true /* isReset */, animate);
mMotionCancelled = true;
if (mSwipingInProgress) {
mCallback.onSwipingAborted();
@@ -523,10 +517,6 @@
}
}
- public void resetImmediately() {
- reset(false /* animate */, true /* force */);
- }
-
public boolean isSwipingInProgress() {
return mSwipingInProgress;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index a4dfcd8..e9aa6d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -254,6 +254,7 @@
mFlashlightController = Dependency.get(FlashlightController.class);
mAccessibilityController = Dependency.get(AccessibilityController.class);
mAssistManager = Dependency.get(AssistManager.class);
+ mLockIcon.setAccessibilityController(mAccessibilityController);
updateLeftAffordance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 9662078..b8ac6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2561,7 +2561,7 @@
public void setTouchDisabled(boolean disabled) {
super.setTouchDisabled(disabled);
if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
- mAffordanceHelper.resetImmediately();
+ mAffordanceHelper.reset(false /* animate */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 12e9c009..b654209 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3770,7 +3770,7 @@
// SystemUIService notifies SystemBars of configuration changes, which then calls down here
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
updateResources();
updateDisplaySize(); // populates mDisplayMetrics
@@ -5382,6 +5382,12 @@
}
@Override
+ public boolean isProvisioned() {
+ return mDeviceProvisionedController.isDeviceProvisioned()
+ && mDeviceProvisionedController.isCurrentUserSetup();
+ }
+
+ @Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
StatusBar.this.startPendingIntentDismissingKeyguard(intent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 9d197d7..74523e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -3362,15 +3362,29 @@
if (!mIsExpanded) {
setOwnScrollY(0);
mStatusBar.resetUserExpandedStates();
+ clearTemporaryViews();
+ clearUserLockedViews();
+ }
+ }
- // lets make sure nothing is in the overlay / transient anymore
- clearTemporaryViews(this);
- for (int i = 0; i < getChildCount(); i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- clearTemporaryViews(row.getChildrenContainer());
- }
+ private void clearUserLockedViews() {
+ for (int i = 0; i < getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ row.setUserLocked(false);
+ }
+ }
+ }
+
+ private void clearTemporaryViews() {
+ // lets make sure nothing is in the overlay / transient anymore
+ clearTemporaryViews(this);
+ for (int i = 0; i < getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ clearTemporaryViews(row.getChildrenContainer());
}
}
}
@@ -3405,6 +3419,7 @@
if (changed) {
if (!mIsExpanded) {
mGroupManager.collapseAllGroups();
+ mExpandHelper.cancelImmediately();
}
updateNotificationAnimationStates();
updateChronometers();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index 2345110..8ff9fa9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -72,6 +72,11 @@
}
@Override
+ public boolean isProvisioned() {
+ return false;
+ }
+
+ @Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
throw new RuntimeException("not implemented");
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 0937ce2..c0de004 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -19,12 +19,13 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
import com.android.internal.app.NightDisplayController;
import com.android.systemui.Prefs;
import com.android.systemui.Prefs.Key;
-
-import android.support.test.filters.SmallTest;
-import android.testing.AndroidTestingRunner;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.QSTileHost;
@@ -35,6 +36,7 @@
import org.mockito.Mockito;
@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
@SmallTest
public class AutoTileManagerTest extends SysuiTestCase {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3566c29..ec1709a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -414,6 +414,7 @@
import com.android.server.wm.PinnedStackWindowController;
import com.android.server.wm.WindowManagerService;
+import java.text.SimpleDateFormat;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -2648,6 +2649,11 @@
mService.start();
}
+ @Override
+ public void onCleanupUser(int userId) {
+ mService.mBatteryStatsService.onCleanupUser(userId);
+ }
+
public ActivityManagerService getService() {
return mService;
}
@@ -5502,11 +5508,8 @@
// NOTE: We should consider creating the file in native code atomically once we've
// gotten rid of the old scheme of dumping and lot of the code that deals with paths
// can be removed.
- try {
- tracesFile = File.createTempFile("anr_", "", tracesDir);
- FileUtils.setPermissions(tracesFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
- } catch (IOException ioe) {
- Slog.w(TAG, "Unable to create ANR traces file: ", ioe);
+ tracesFile = createAnrDumpFile(tracesDir);
+ if (tracesFile == null) {
return null;
}
@@ -5518,6 +5521,31 @@
return tracesFile;
}
+ @GuardedBy("ActivityManagerService.class")
+ private static SimpleDateFormat sAnrFileDateFormat;
+
+ private static synchronized File createAnrDumpFile(File tracesDir) {
+ if (sAnrFileDateFormat == null) {
+ sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
+ }
+
+ final String formattedDate = sAnrFileDateFormat.format(new Date());
+ final File anrFile = new File(tracesDir, "anr_" + formattedDate);
+
+ try {
+ if (anrFile.createNewFile()) {
+ FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
+ return anrFile;
+ } else {
+ Slog.w(TAG, "Unable to create ANR dump file: createNewFile failed");
+ }
+ } catch (IOException ioe) {
+ Slog.w(TAG, "Exception creating ANR dump file:", ioe);
+ }
+
+ return null;
+ }
+
/**
* Prune all trace files that are more than a day old.
*
@@ -17189,12 +17217,12 @@
}
} catch (IOException e) {
if (!isCheckinRequest) {
- pw.println("Got IoException!");
+ pw.println("Got IoException! " + e);
pw.flush();
}
} catch (RemoteException e) {
if (!isCheckinRequest) {
- pw.println("Got RemoteException!");
+ pw.println("Got RemoteException! " + e);
pw.flush();
}
}
@@ -23740,6 +23768,7 @@
synchronized (ActivityManagerService.this) {
ActivityManagerService.this.onUserStoppedLocked(userId);
}
+ mBatteryStatsService.onUserRemoved(userId);
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 77e438a..5b461a1 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2848,8 +2848,9 @@
} else {
// If a new task is being launched, then mark the existing top activity as
// supporting picture-in-picture while pausing
- if (focusedTopActivity != null &&
- focusedTopActivity.getStack().getStackId() != PINNED_STACK_ID) {
+ if (focusedTopActivity != null
+ && focusedTopActivity.getStackId() != PINNED_STACK_ID
+ && r.getStackId() != ASSISTANT_STACK_ID) {
focusedTopActivity.supportsPictureInPictureWhilePausing = true;
}
transit = TRANSIT_TASK_OPEN;
@@ -4461,7 +4462,8 @@
}
// If a new task is moved to the front, then mark the existing top activity as supporting
// picture-in-picture while paused
- if (topActivity != null && topActivity.getStack().getStackId() != PINNED_STACK_ID) {
+ if (topActivity != null && topActivity.getStackId() != PINNED_STACK_ID
+ && tr.getStackId() != ASSISTANT_STACK_ID) {
topActivity.supportsPictureInPictureWhilePausing = true;
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index cf322d7..4836bc3 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -34,6 +34,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManagerInternal;
import android.os.WorkSource;
import android.os.health.HealthStatsParceler;
import android.os.health.HealthStatsWriter;
@@ -79,6 +80,7 @@
private static IBatteryStats sService;
final BatteryStatsImpl mStats;
+ private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
private final Context mContext;
private final BatteryExternalStatsWorker mWorker;
@@ -140,7 +142,17 @@
BatteryStatsService(Context context, File systemDir, Handler handler) {
// BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
mContext = context;
- mStats = new BatteryStatsImpl(systemDir, handler, this);
+ mUserManagerUserInfoProvider = new BatteryStatsImpl.UserInfoProvider() {
+ private UserManagerInternal umi;
+ @Override
+ public int[] getUserIds() {
+ if (umi == null) {
+ umi = LocalServices.getService(UserManagerInternal.class);
+ }
+ return (umi != null) ? umi.getUserIds() : null;
+ }
+ };
+ mStats = new BatteryStatsImpl(systemDir, handler, this, mUserManagerUserInfoProvider);
mWorker = new BatteryExternalStatsWorker(context, mStats);
mStats.setExternalStatsSyncLocked(mWorker);
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
@@ -242,6 +254,18 @@
}
}
+ void onCleanupUser(int userId) {
+ synchronized (mStats) {
+ mStats.onCleanupUserLocked(userId);
+ }
+ }
+
+ void onUserRemoved(int userId) {
+ synchronized (mStats) {
+ mStats.onUserRemovedLocked(userId);
+ }
+ }
+
void addIsolatedUid(int isolatedUid, int appUid) {
synchronized (mStats) {
mStats.addIsolatedUidLocked(isolatedUid, appUid);
@@ -369,10 +393,10 @@
}
}
- public void noteJobFinish(String name, int uid) {
+ public void noteJobFinish(String name, int uid, int stopReason) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteJobFinishLocked(name, uid);
+ mStats.noteJobFinishLocked(name, uid, stopReason);
}
}
@@ -1260,7 +1284,7 @@
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
- null, mStats.mHandler, null);
+ null, mStats.mHandler, null, mUserManagerUserInfoProvider);
checkinStats.readSummaryFromParcel(in);
in.recycle();
checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 78487b7..20ec206 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -58,7 +58,12 @@
}
public void start() {
- if (isOffloadDisabled() || started()) return;
+ if (started()) return;
+
+ if (isOffloadDisabled()) {
+ mLog.i("tethering offload disabled");
+ return;
+ }
if (!mConfigInitialized) {
mConfigInitialized = mHwInterface.initOffloadConfig();
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
index ba92295..025ff0b 100644
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ b/services/core/java/com/android/server/job/JobPackageTracker.java
@@ -17,11 +17,13 @@
package com.android.server.job;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.TimeUtils;
import com.android.internal.util.RingBufferIndices;
import com.android.server.job.controllers.JobStatus;
@@ -36,6 +38,9 @@
private static final int EVENT_BUFFER_SIZE = 100;
+ public static final int EVENT_CMD_MASK = 0xff;
+ public static final int EVENT_STOP_REASON_SHIFT = 8;
+ public static final int EVENT_STOP_REASON_MASK = 0xff << EVENT_STOP_REASON_SHIFT;
public static final int EVENT_NULL = 0;
public static final int EVENT_START_JOB = 1;
public static final int EVENT_STOP_JOB = 2;
@@ -49,9 +54,9 @@
private final String[] mEventTags = new String[EVENT_BUFFER_SIZE];
private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE];
- public void addEvent(int cmd, int uid, String tag, int jobId) {
+ public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason) {
int index = mEventIndices.add();
- mEventCmds[index] = cmd;
+ mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
mEventTimes[index] = SystemClock.elapsedRealtime();
mEventUids[index] = uid;
mEventTags[index] = tag;
@@ -77,6 +82,7 @@
int pendingNesting;
int pendingCount;
boolean hadPending;
+ final SparseIntArray stopReasons = new SparseIntArray();
public long getActiveTime(long now) {
long time = pastActiveTime;
@@ -179,12 +185,14 @@
pe.activeNesting++;
}
- void decActive(int uid, String pkg, long now) {
+ void decActive(int uid, String pkg, long now, int stopReason) {
PackageEntry pe = getOrCreateEntry(uid, pkg);
if (pe.activeNesting == 1) {
pe.pastActiveTime += now - pe.activeStartTime;
}
pe.activeNesting--;
+ int count = pe.stopReasons.get(stopReason, 0);
+ pe.stopReasons.put(stopReason, count+1);
}
void incActiveTop(int uid, String pkg, long now) {
@@ -196,12 +204,14 @@
pe.activeTopNesting++;
}
- void decActiveTop(int uid, String pkg, long now) {
+ void decActiveTop(int uid, String pkg, long now, int stopReason) {
PackageEntry pe = getOrCreateEntry(uid, pkg);
if (pe.activeTopNesting == 1) {
pe.pastActiveTopTime += now - pe.activeTopStartTime;
}
pe.activeTopNesting--;
+ int count = pe.stopReasons.get(stopReason, 0);
+ pe.stopReasons.put(stopReason, count+1);
}
void finish(DataSet next, long now) {
@@ -261,6 +271,11 @@
outPe.pastPendingTime += now - pe.pendingStartTime;
outPe.hadPending = true;
}
+ for (int k = pe.stopReasons.size()-1; k >= 0; k--) {
+ int type = pe.stopReasons.keyAt(k);
+ outPe.stopReasons.put(type, outPe.stopReasons.get(type, 0)
+ + pe.stopReasons.valueAt(k));
+ }
}
}
if (mMaxTotalActive > out.mMaxTotalActive) {
@@ -312,7 +327,8 @@
pw.print(prefix); pw.print(" ");
UserHandle.formatUid(pw, uid);
pw.print(" / "); pw.print(uidMap.keyAt(j));
- pw.print(":");
+ pw.println(":");
+ pw.print(prefix); pw.print(" ");
printDuration(pw, period, pe.getPendingTime(now), pe.pendingCount, "pending");
printDuration(pw, period, pe.getActiveTime(now), pe.activeCount, "active");
printDuration(pw, period, pe.getActiveTopTime(now), pe.activeTopCount,
@@ -327,6 +343,18 @@
pw.print(" (active-top)");
}
pw.println();
+ if (pe.stopReasons.size() > 0) {
+ pw.print(prefix); pw.print(" ");
+ for (int k = 0; k < pe.stopReasons.size(); k++) {
+ if (k > 0) {
+ pw.print(", ");
+ }
+ pw.print(pe.stopReasons.valueAt(k));
+ pw.print("x ");
+ pw.print(JobParameters.getReasonName(pe.stopReasons.keyAt(k)));
+ }
+ pw.println();
+ }
}
}
pw.print(prefix); pw.print(" Max concurrency: ");
@@ -370,19 +398,20 @@
mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
}
addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB : EVENT_START_JOB,
- job.getSourceUid(), job.getBatteryName(), job.getJobId());
+ job.getSourceUid(), job.getBatteryName(), job.getJobId(), 0);
}
- public void noteInactive(JobStatus job) {
+ public void noteInactive(JobStatus job, int stopReason) {
final long now = SystemClock.uptimeMillis();
if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
- mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
+ mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
+ stopReason);
} else {
- mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now);
+ mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now, stopReason);
}
rebatchIfNeeded(now);
addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB : EVENT_STOP_PERIODIC_JOB,
- job.getSourceUid(), job.getBatteryName(), job.getJobId());
+ job.getSourceUid(), job.getBatteryName(), job.getJobId(), stopReason);
}
public void noteConcurrency(int totalActive, int fgActive) {
@@ -448,12 +477,12 @@
if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
continue;
}
- final int cmd = mEventCmds[index];
+ final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
if (cmd == EVENT_NULL) {
continue;
}
final String label;
- switch (mEventCmds[index]) {
+ switch (cmd) {
case EVENT_START_JOB: label = " START"; break;
case EVENT_STOP_JOB: label = " STOP"; break;
case EVENT_START_PERIODIC_JOB: label = "START-P"; break;
@@ -469,7 +498,13 @@
pw.print("/");
pw.print(mEventJobIds[index]);
pw.print(" ");
- pw.println(mEventTags[index]);
+ pw.print(mEventTags[index]);
+ if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
+ pw.print(" ");
+ pw.print(JobParameters.getReasonName((mEventCmds[index] & EVENT_STOP_REASON_MASK)
+ >> EVENT_STOP_REASON_SHIFT));
+ }
+ pw.println();
}
return true;
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 98c65fd..3fea51f 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -2052,7 +2052,15 @@
synchronized (mLock) {
boolean foundSome = false;
for (int i=0; i<mActiveServices.size(); i++) {
- mActiveServices.get(i).timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId);
+ final JobServiceContext jc = mActiveServices.get(i);
+ final JobStatus js = jc.getRunningJobLocked();
+ if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId)) {
+ foundSome = true;
+ pw.print("Timing out: ");
+ js.printUniqueId(pw);
+ pw.print(" ");
+ pw.println(js.getServiceComponent().flattenToShortString());
+ }
}
if (!foundSome) {
pw.println("No matching executing jobs found.");
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 107475f..a34e251 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -681,6 +681,7 @@
return;
}
try {
+ applyStoppedReasonLocked(reason);
mVerb = VERB_STOPPING;
scheduleOpTimeOutLocked();
service.stopJob(mParams);
@@ -704,10 +705,10 @@
}
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
- mJobPackageTracker.noteInactive(completedJob);
+ mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason());
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
- mRunningJob.getSourceUid());
+ mRunningJob.getSourceUid(), mParams.getStopReason());
} catch (RemoteException e) {
// Whatever.
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index c07bd8e..a7cd962 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -67,7 +67,6 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -589,22 +588,20 @@
}
private void maybeEnableFactoryTrustAgents(LockPatternUtils utils, int userId) {
+ if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
+ return;
+ }
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userId);
ComponentName defaultAgent = getDefaultFactoryTrustAgent(mContext);
boolean shouldUseDefaultAgent = defaultAgent != null;
+ ArraySet<ComponentName> discoveredAgents = new ArraySet<>();
if (shouldUseDefaultAgent) {
+ discoveredAgents.add(defaultAgent);
Log.i(TAG, "Enabling " + defaultAgent + " because it is a default agent.");
- utils.setEnabledTrustAgents(Collections.singleton(defaultAgent), userId);
} else { // A default agent is not set; perform regular trust agent discovery
- if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
- return;
- }
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userId);
-
- ArraySet<ComponentName> discoveredAgents = new ArraySet<>();
-
for (ResolveInfo resolveInfo : resolveInfos) {
ComponentName componentName = getComponentName(resolveInfo);
int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags;
@@ -615,13 +612,13 @@
}
discoveredAgents.add(componentName);
}
-
- List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
- if (previouslyEnabledAgents != null) {
- discoveredAgents.addAll(previouslyEnabledAgents);
- }
- utils.setEnabledTrustAgents(discoveredAgents, userId);
}
+
+ List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
+ if (previouslyEnabledAgents != null) {
+ discoveredAgents.addAll(previouslyEnabledAgents);
+ }
+ utils.setEnabledTrustAgents(discoveredAgents, userId);
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9fe7381..05f4626 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1073,7 +1073,7 @@
}
if (w.mHasSurface && !rotateSeamlessly) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w);
- w.mOrientationChanging = true;
+ w.setOrientationChanging(true);
mService.mRoot.mOrientationChangeComplete = false;
w.mLastFreezeDuration = 0;
}
@@ -2679,10 +2679,10 @@
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
forAllWindows(w -> {
- if (!w.mOrientationChanging) {
+ if (!w.getOrientationChanging()) {
return;
}
- w.mOrientationChanging = false;
+ w.setOrientationChanging(false);
w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mService.mDisplayFreezeTime);
Slog.w(TAG_WM, "Force clearing orientation change: " + w);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index edb8853..940ad33 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -18,6 +18,9 @@
import static android.app.ActivityManager.ENABLE_TASK_SNAPSHOTS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
@@ -29,6 +32,7 @@
import android.os.Environment;
import android.os.Handler;
import android.util.ArraySet;
+import android.util.Slog;
import android.view.DisplayListCanvas;
import android.view.RenderNode;
import android.view.ThreadedRenderer;
@@ -57,6 +61,7 @@
* To access this class, acquire the global window manager lock.
*/
class TaskSnapshotController {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
/**
* Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
@@ -154,10 +159,17 @@
break;
}
if (snapshot != null) {
- mCache.putSnapshot(task, snapshot);
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- if (task.getController() != null) {
- task.getController().reportSnapshotChanged(snapshot);
+ final GraphicBuffer buffer = snapshot.getSnapshot();
+ if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
+ buffer.destroy();
+ Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
+ + buffer.getHeight());
+ } else {
+ mCache.putSnapshot(task, snapshot);
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ if (task.getController() != null) {
+ task.getController().reportSnapshotChanged(snapshot);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index b628869..f90b3fb 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -291,7 +291,6 @@
failed = true;
}
if (!writeBuffer()) {
- writeBuffer();
failed = true;
}
if (failed) {
@@ -327,11 +326,7 @@
final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
if (bitmap == null) {
- Slog.e(TAG, "Invalid task snapshot");
- return false;
- } else if (bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
- Slog.e(TAG, "Invalid task snapshot dimensions " + bitmap.getWidth() + "x"
- + bitmap.getHeight());
+ Slog.e(TAG, "Invalid task snapshot hw bitmap");
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 20d9286..f9c21b3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5789,7 +5789,7 @@
// orientation.
if (!okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Changing surface while display frozen: " + w);
- w.mOrientationChanging = true;
+ w.setOrientationChanging(true);
w.mLastFreezeDuration = 0;
mRoot.mOrientationChangeComplete = false;
if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f2cbb6b..17768e6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -446,7 +446,7 @@
* Set when the orientation is changing and this window has not yet
* been updated for the new orientation.
*/
- boolean mOrientationChanging;
+ private boolean mOrientationChanging;
/**
* The orientation during the last visible call to relayout. If our
@@ -1189,7 +1189,8 @@
// then we need to hold off on unfreezing the display until this window has been
// redrawn; to do that, we need to go through the process of getting informed by the
// application when it has finished drawing.
- if (mOrientationChanging || dragResizingChanged || isResizedWhileNotDragResizing()) {
+ if (getOrientationChanging() || dragResizingChanged
+ || isResizedWhileNotDragResizing()) {
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
+ ", mDrawState=DRAW_PENDING in " + this
@@ -1204,17 +1205,33 @@
if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG_WM, "Resizing window " + this);
mService.mResizingWindows.add(this);
}
- } else if (mOrientationChanging) {
+ } else if (getOrientationChanging()) {
if (isDrawnLw()) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Orientation not waiting for draw in "
+ this + ", surfaceController " + winAnimator.mSurfaceController);
- mOrientationChanging = false;
+ setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mService.mDisplayFreezeTime);
}
}
}
+ boolean getOrientationChanging() {
+ // In addition to the local state flag, we must also consider the difference in the last
+ // reported configuration vs. the current state. If the client code has not been informed of
+ // the change, logic dependent on having finished processing the orientation, such as
+ // unfreezing, could be improperly triggered.
+ // TODO(b/62846907): Checking against {@link mLastReportedConfiguration} could be flaky as
+ // this is not necessarily what the client has processed yet. Find a
+ // better indicator consistent with the client.
+ return mOrientationChanging
+ || getConfiguration().orientation != mLastReportedConfiguration.orientation;
+ }
+
+ void setOrientationChanging(boolean changing) {
+ mOrientationChanging = changing;
+ }
+
DisplayContent getDisplayContent() {
return mToken.getDisplayContent();
}
@@ -2688,10 +2705,10 @@
mAppFreezing = false;
- if (mHasSurface && !mOrientationChanging
+ if (mHasSurface && !getOrientationChanging()
&& mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "set mOrientationChanging of " + this);
- mOrientationChanging = true;
+ setOrientationChanging(true);
mService.mRoot.mOrientationChangeComplete = false;
}
mLastFreezeDuration = 0;
@@ -3110,7 +3127,7 @@
mWinAnimator.mSurfaceResized = false;
mReportOrientationChanged = false;
} catch (RemoteException e) {
- mOrientationChanging = false;
+ setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mService.mDisplayFreezeTime);
// We are assuming the hosting process is dead or in a zombie state.
@@ -3471,10 +3488,13 @@
pw.print(" mDestroying="); pw.print(mDestroying);
pw.print(" mRemoved="); pw.println(mRemoved);
}
- if (mOrientationChanging || mAppFreezing || mTurnOnScreen
+ if (getOrientationChanging() || mAppFreezing || mTurnOnScreen
|| mReportOrientationChanged) {
pw.print(prefix); pw.print("mOrientationChanging=");
pw.print(mOrientationChanging);
+ pw.print(" configOrientationChanging=");
+ pw.print(mLastReportedConfiguration.orientation
+ != getConfiguration().orientation);
pw.print(" mAppFreezing="); pw.print(mAppFreezing);
pw.print(" mTurnOnScreen="); pw.print(mTurnOnScreen);
pw.print(" mReportOrientationChanged="); pw.println(mReportOrientationChanged);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 33cb908..df30cfc 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1527,11 +1527,11 @@
// There is no need to wait for an animation change if our window is gone for layout
// already as we'll never be visible.
- if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
+ if (w.getOrientationChanging() && w.isGoneForLayoutLw()) {
if (DEBUG_ORIENTATION) {
Slog.v(TAG, "Orientation change skips hidden " + w);
}
- w.mOrientationChanging = false;
+ w.setOrientationChanging(false);
}
return;
}
@@ -1564,8 +1564,8 @@
// really hidden (gone for layout), there is no point in still waiting for it.
// Note that this does introduce a potential glitch if the window becomes unhidden
// before it has drawn for the new orientation.
- if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
- w.mOrientationChanging = false;
+ if (w.getOrientationChanging() && w.isGoneForLayoutLw()) {
+ w.setOrientationChanging(false);
if (DEBUG_ORIENTATION) Slog.v(TAG,
"Orientation change skips hidden " + w);
}
@@ -1618,7 +1618,7 @@
mAnimator.setPendingLayoutChanges(w.getDisplayId(),
WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
} else {
- w.mOrientationChanging = false;
+ w.setOrientationChanging(false);
}
}
if (hasSurface()) {
@@ -1631,14 +1631,14 @@
displayed = true;
}
- if (w.mOrientationChanging) {
+ if (w.getOrientationChanging()) {
if (!w.isDrawnLw()) {
mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
mAnimator.mLastWindowFreezeSource = w;
if (DEBUG_ORIENTATION) Slog.v(TAG,
"Orientation continue waiting for draw in " + w);
} else {
- w.mOrientationChanging = false;
+ w.setOrientationChanging(false);
if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
}
}
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index e6b567e..dcf5c27 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -83,6 +83,13 @@
*/
public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF";
+ /**
+ * Reason code (returned via {@link #getReason()}), which indicates that the video telephony
+ * call was disconnected because IMS access is blocked.
+ * @hide
+ */
+ public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED";
+
private int mDisconnectCode;
private CharSequence mDisconnectLabel;
private CharSequence mDisconnectDescription;
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index b3f46c2..231f2c8 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -93,19 +93,21 @@
/**
* Integer extra indicating the result code of the download. One of
* {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}.
+ * TODO: Not systemapi.
*/
public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
/**
* Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
* is for. Must not be null.
- * TODO: future systemapi (here and and all extras) except the two for the app intent
+ * TODO: Not systemapi.
*/
public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO";
/**
* Extra containing the {@link DownloadRequest} for which the download result or file
* descriptor request is for. Must not be null.
+ * TODO: future systemapi (here and and all extras) except the three for the app intent
*/
public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
@@ -180,6 +182,7 @@
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
* Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to
* {@link #RESULT_SUCCESSFUL}.
+ * TODO: Not systemapi.
*/
public static final String EXTRA_COMPLETED_FILE_URI =
"android.telephony.mbms.extra.COMPLETED_FILE_URI";
@@ -554,7 +557,7 @@
private File getDownloadRequestTokenPath(DownloadRequest request) {
File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
- request.getFileServiceInfo());
+ request.getFileServiceId());
String downloadTokenFileName = request.getHash()
+ MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
return new File(tempFileLocation, downloadTokenFileName);
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 345729d..01e0bbd 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -21,7 +21,14 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;
+import android.util.Log;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -35,24 +42,52 @@
public class DownloadRequest implements Parcelable {
// Version code used to keep token calculation consistent.
private static final int CURRENT_VERSION = 1;
+ private static final String LOG_TAG = "MbmsDownloadRequest";
+
+ /**
+ * Maximum permissible length for the app's download-completion intent, when serialized via
+ * {@link Intent#toUri(int)}.
+ */
+ public static final int MAX_APP_INTENT_SIZE = 50000;
+
+ /**
+ * Maximum permissible length for the app's destination path, when serialized via
+ * {@link Uri#toString()}.
+ */
+ public static final int MAX_DESTINATION_URI_SIZE = 50000;
/** @hide */
+ private static class OpaqueDataContainer implements Serializable {
+ private final String destinationUri;
+ private final String appIntent;
+ private final int version;
+
+ public OpaqueDataContainer(String destinationUri, String appIntent, int version) {
+ this.destinationUri = destinationUri;
+ this.appIntent = appIntent;
+ this.version = version;
+ }
+ }
+
public static class Builder {
- private int id;
- private FileServiceInfo serviceInfo;
+ private String fileServiceId;
private Uri source;
private Uri dest;
private int subscriptionId;
private String appIntent;
private int version = CURRENT_VERSION;
- public Builder setId(int id) {
- this.id = id;
+ public Builder setServiceInfo(FileServiceInfo serviceInfo) {
+ fileServiceId = serviceInfo.getServiceId();
return this;
}
- public Builder setServiceInfo(FileServiceInfo serviceInfo) {
- this.serviceInfo = serviceInfo;
+ /**
+ * @hide
+ * TODO: systemapi
+ */
+ public Builder setServiceId(String serviceId) {
+ fileServiceId = serviceId;
return this;
}
@@ -62,6 +97,10 @@
}
public Builder setDest(Uri dest) {
+ if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
+ throw new IllegalArgumentException("Destination uri must not exceed length " +
+ MAX_DESTINATION_URI_SIZE);
+ }
this.dest = dest;
return this;
}
@@ -73,33 +112,53 @@
public Builder setAppIntent(Intent intent) {
this.appIntent = intent.toUri(0);
+ if (this.appIntent.length() > MAX_APP_INTENT_SIZE) {
+ throw new IllegalArgumentException("App intent must not exceed length " +
+ MAX_APP_INTENT_SIZE);
+ }
return this;
}
- public Builder setVersion(int version) {
- this.version = version;
+ /**
+ * For use by middleware only
+ * TODO: systemapi
+ * @hide
+ */
+ public Builder setOpaqueData(byte[] data) {
+ try {
+ ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
+ OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
+ version = dataContainer.version;
+ appIntent = dataContainer.appIntent;
+ dest = Uri.parse(dataContainer.destinationUri);
+ } catch (IOException e) {
+ // Really should never happen
+ Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
+ throw new IllegalArgumentException(e);
+ } catch (ClassNotFoundException e) {
+ Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data");
+ throw new IllegalArgumentException(e);
+ }
return this;
}
public DownloadRequest build() {
- return new DownloadRequest(id, serviceInfo, source, dest,
+ return new DownloadRequest(fileServiceId, source, dest,
subscriptionId, appIntent, version);
}
}
- private final int downloadId;
- private final FileServiceInfo fileServiceInfo;
+ private final String fileServiceId;
private final Uri sourceUri;
private final Uri destinationUri;
private final int subscriptionId;
private final String serializedResultIntentForApp;
private final int version;
- private DownloadRequest(int id, FileServiceInfo serviceInfo,
+ private DownloadRequest(String fileServiceId,
Uri source, Uri dest,
int sub, String appIntent, int version) {
- downloadId = id;
- fileServiceInfo = serviceInfo;
+ this.fileServiceId = fileServiceId;
sourceUri = source;
destinationUri = dest;
subscriptionId = sub;
@@ -112,8 +171,7 @@
}
private DownloadRequest(DownloadRequest dr) {
- downloadId = dr.downloadId;
- fileServiceInfo = dr.fileServiceInfo;
+ fileServiceId = dr.fileServiceId;
sourceUri = dr.sourceUri;
destinationUri = dr.destinationUri;
subscriptionId = dr.subscriptionId;
@@ -122,8 +180,7 @@
}
private DownloadRequest(Parcel in) {
- downloadId = in.readInt();
- fileServiceInfo = in.readParcelable(getClass().getClassLoader());
+ fileServiceId = in.readString();
sourceUri = in.readParcelable(getClass().getClassLoader());
destinationUri = in.readParcelable(getClass().getClassLoader());
subscriptionId = in.readInt();
@@ -136,8 +193,7 @@
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(downloadId);
- out.writeParcelable(fileServiceInfo, flags);
+ out.writeString(fileServiceId);
out.writeParcelable(sourceUri, flags);
out.writeParcelable(destinationUri, flags);
out.writeInt(subscriptionId);
@@ -145,12 +201,8 @@
out.writeInt(version);
}
- public int getDownloadId() {
- return downloadId;
- }
-
- public FileServiceInfo getFileServiceInfo() {
- return fileServiceInfo;
+ public String getFileServiceId() {
+ return fileServiceId;
}
public Uri getSourceUri() {
@@ -173,6 +225,27 @@
}
}
+ /**
+ * @hide
+ * TODO: systemapi
+ */
+ public byte[] getOpaqueData() {
+ try {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
+ OpaqueDataContainer container = new OpaqueDataContainer(
+ destinationUri.toString(), serializedResultIntentForApp, version);
+ stream.writeObject(container);
+ stream.flush();
+ return byteArrayOutputStream.toByteArray();
+ } catch (IOException e) {
+ // Really should never happen
+ Log.e(LOG_TAG, "Got IOException trying to serialize opaque data");
+ return null;
+ }
+ }
+
+ /** @hide */
public int getVersion() {
return version;
}
@@ -228,10 +301,9 @@
return false;
}
DownloadRequest request = (DownloadRequest) o;
- return downloadId == request.downloadId &&
- subscriptionId == request.subscriptionId &&
+ return subscriptionId == request.subscriptionId &&
version == request.version &&
- Objects.equals(fileServiceInfo, request.fileServiceInfo) &&
+ Objects.equals(fileServiceId, request.fileServiceId) &&
Objects.equals(sourceUri, request.sourceUri) &&
Objects.equals(destinationUri, request.destinationUri) &&
Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
@@ -239,7 +311,7 @@
@Override
public int hashCode() {
- return Objects.hash(downloadId, fileServiceInfo, sourceUri, destinationUri,
+ return Objects.hash(fileServiceId, sourceUri, destinationUri,
subscriptionId, serializedResultIntentForApp, version);
}
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 5bc53a8..3617165 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -83,7 +83,7 @@
public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4;
/**
- * Indicates that the manager weas unable to generate one or more of the requested file
+ * Indicates that the manager was unable to generate one or more of the requested file
* descriptors.
* This is a non-fatal result code -- some file descriptors may still be generated, but there
* is no guarantee that they will be the same number as requested.
@@ -149,7 +149,7 @@
DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
File expectedTokenFile = new File(
- MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceInfo()),
+ MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
expectedTokenFileName);
if (!expectedTokenFile.exists()) {
Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " +
@@ -201,22 +201,24 @@
Uri destinationUri = request.getDestinationUri();
Uri finalTempFile = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FINAL_URI);
- if (!verifyTempFilePath(context, request.getFileServiceInfo(), finalTempFile)) {
+ if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
return;
}
- String relativePath = calculateDestinationFileRelativePath(request,
- (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO));
+ FileInfo completedFileInfo =
+ (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO);
+ String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo);
Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath);
if (finalFileLocation == null) {
Log.w(LOG_TAG, "Failed to move temp file to final destination");
- // TODO: how do we notify the app of this?
setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
+ return;
}
intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation);
+ intentForApp.putExtra(MbmsDownloadManager.EXTRA_FILE_INFO, completedFileInfo);
context.sendBroadcast(intentForApp);
setResultCode(RESULT_OK);
@@ -235,7 +237,7 @@
}
for (Uri tempFileUri : tempFiles) {
- if (verifyTempFilePath(context, request.getFileServiceInfo(), tempFileUri)) {
+ if (verifyTempFilePath(context, request.getFileServiceId(), tempFileUri)) {
File tempFile = new File(tempFileUri.getSchemeSpecificPart());
tempFile.delete();
}
@@ -276,7 +278,8 @@
private ArrayList<UriPathPair> generateFreshTempFiles(Context context,
FileServiceInfo serviceInfo,
int freshFdCount) {
- File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo);
+ File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
+ serviceInfo.getServiceId());
if (!tempFileDir.exists()) {
tempFileDir.mkdirs();
}
@@ -327,7 +330,7 @@
ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
for (Uri fileUri : pausedFiles) {
- if (!verifyTempFilePath(context, serviceInfo, fileUri)) {
+ if (!verifyTempFilePath(context, serviceInfo.getServiceId(), fileUri)) {
Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
continue;
@@ -351,7 +354,8 @@
private void cleanupTempFiles(Context context, Intent intent) {
FileServiceInfo serviceInfo =
intent.getParcelableExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO);
- File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo);
+ File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
+ serviceInfo.getServiceId());
List<Uri> filesInUse =
intent.getParcelableArrayListExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE);
File[] filesToDelete = tempFileDir.listFiles(new FileFilter() {
@@ -439,7 +443,7 @@
return null;
}
- private static boolean verifyTempFilePath(Context context, FileServiceInfo serviceInfo,
+ private static boolean verifyTempFilePath(Context context, String serviceId,
Uri filePath) {
if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) {
Log.w(LOG_TAG, "Uri " + filePath + " does not have a file scheme");
@@ -454,7 +458,7 @@
}
if (!MbmsUtils.isContainedIn(
- MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo), tempFile)) {
+ MbmsUtils.getEmbmsTempFileDirForService(context, serviceId), tempFile)) {
return false;
}
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index b28950e..1e03fb9 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -89,10 +89,9 @@
/**
* Returns a File linked to the directory used to store temp files for this file service
*/
- public static File getEmbmsTempFileDirForService(Context context, FileServiceInfo serviceInfo) {
+ public static File getEmbmsTempFileDirForService(Context context, String serviceId) {
File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
- String tempFileDirName = String.valueOf(serviceInfo.getServiceId());
- return new File(embmsTempFileDir, tempFileDirName);
+ return new File(embmsTempFileDir, serviceId);
}
}
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index b41dadb..9053b23 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -30,6 +30,20 @@
include $(BUILD_JAVA_LIBRARY)
+# Build the repackaged.android.test.runner library
+# ================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework legacy-test
+
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../legacy-test/jarjar-rules.txt
+
+LOCAL_MODULE:= repackaged.android.test.runner
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
# Generate the stub source files for android.test.runner.stubs
# ============================================================
include $(CLEAR_VARS)