Merge "Add remaining Accessibility Settings to backup list. Note additional settings that should not be restored if they are already set (on account of the new Setup Wizard, which allows critical Accessibility Settings to be set before restore)."
diff --git a/api/current.txt b/api/current.txt
index f6b4d61..6c49041 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1288,6 +1288,9 @@
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
field public static final int thumbnail = 16843429; // 0x10102a5
+ field public static final int tickMark = 16844043; // 0x101050b
+ field public static final int tickMarkTint = 16844044; // 0x101050c
+ field public static final int tickMarkTintMode = 16844045; // 0x101050d
field public static final int tileMode = 16843265; // 0x1010201
field public static final int tileModeX = 16843895; // 0x1010477
field public static final int tileModeY = 16843896; // 0x1010478
@@ -2547,6 +2550,7 @@
field public static final int Widget_Material_ScrollView = 16974462; // 0x103027e
field public static final int Widget_Material_SearchView = 16974463; // 0x103027f
field public static final int Widget_Material_SeekBar = 16974464; // 0x1030280
+ field public static final int Widget_Material_SeekBar_Discrete = 16974553; // 0x10302d9
field public static final int Widget_Material_SegmentedButton = 16974465; // 0x1030281
field public static final int Widget_Material_Spinner = 16974467; // 0x1030283
field public static final int Widget_Material_Spinner_Underlined = 16974468; // 0x1030284
@@ -3638,6 +3642,9 @@
method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
}
+ public static abstract class ActivityManager.BugreportMode implements java.lang.annotation.Annotation {
+ }
+
public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
ctor public ActivityManager.MemoryInfo();
method public int describeContents();
@@ -28451,6 +28458,7 @@
field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi";
field public static final java.lang.String DISALLOW_CREATE_WINDOWS = "no_create_windows";
field public static final java.lang.String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";
+ field public static final java.lang.String DISALLOW_DATA_ROAMING = "no_data_roaming";
field public static final java.lang.String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";
field public static final java.lang.String DISALLOW_FACTORY_RESET = "no_factory_reset";
field public static final java.lang.String DISALLOW_FUN = "no_fun";
@@ -44533,12 +44541,18 @@
method public int getThumbOffset();
method public android.content.res.ColorStateList getThumbTintList();
method public android.graphics.PorterDuff.Mode getThumbTintMode();
+ method public android.graphics.drawable.Drawable getTickMark();
+ method public android.content.res.ColorStateList getTickMarkTintList();
+ method public android.graphics.PorterDuff.Mode getTickMarkTintMode();
method public void setKeyProgressIncrement(int);
method public void setSplitTrack(boolean);
method public void setThumb(android.graphics.drawable.Drawable);
method public void setThumbOffset(int);
method public void setThumbTintList(android.content.res.ColorStateList);
method public void setThumbTintMode(android.graphics.PorterDuff.Mode);
+ method public void setTickMark(android.graphics.drawable.Drawable);
+ method public void setTickMarkTintList(android.content.res.ColorStateList);
+ method public void setTickMarkTintMode(android.graphics.PorterDuff.Mode);
}
public abstract class AbsSpinner extends android.widget.AdapterView {
diff --git a/api/system-current.txt b/api/system-current.txt
index bf4fc50..3ede80a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1386,6 +1386,9 @@
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
field public static final int thumbnail = 16843429; // 0x10102a5
+ field public static final int tickMark = 16844043; // 0x101050b
+ field public static final int tickMarkTint = 16844044; // 0x101050c
+ field public static final int tickMarkTintMode = 16844045; // 0x101050d
field public static final int tileMode = 16843265; // 0x1010201
field public static final int tileModeX = 16843895; // 0x1010477
field public static final int tileModeY = 16843896; // 0x1010478
@@ -2648,6 +2651,7 @@
field public static final int Widget_Material_ScrollView = 16974462; // 0x103027e
field public static final int Widget_Material_SearchView = 16974463; // 0x103027f
field public static final int Widget_Material_SeekBar = 16974464; // 0x1030280
+ field public static final int Widget_Material_SeekBar_Discrete = 16974553; // 0x10302d9
field public static final int Widget_Material_SegmentedButton = 16974465; // 0x1030281
field public static final int Widget_Material_Spinner = 16974467; // 0x1030283
field public static final int Widget_Material_Spinner_Underlined = 16974468; // 0x1030284
@@ -3749,6 +3753,9 @@
method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
}
+ public static abstract class ActivityManager.BugreportMode implements java.lang.annotation.Annotation {
+ }
+
public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
ctor public ActivityManager.MemoryInfo();
method public int describeContents();
@@ -30454,6 +30461,7 @@
field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi";
field public static final java.lang.String DISALLOW_CREATE_WINDOWS = "no_create_windows";
field public static final java.lang.String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";
+ field public static final java.lang.String DISALLOW_DATA_ROAMING = "no_data_roaming";
field public static final java.lang.String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";
field public static final java.lang.String DISALLOW_FACTORY_RESET = "no_factory_reset";
field public static final java.lang.String DISALLOW_FUN = "no_fun";
@@ -47197,12 +47205,18 @@
method public int getThumbOffset();
method public android.content.res.ColorStateList getThumbTintList();
method public android.graphics.PorterDuff.Mode getThumbTintMode();
+ method public android.graphics.drawable.Drawable getTickMark();
+ method public android.content.res.ColorStateList getTickMarkTintList();
+ method public android.graphics.PorterDuff.Mode getTickMarkTintMode();
method public void setKeyProgressIncrement(int);
method public void setSplitTrack(boolean);
method public void setThumb(android.graphics.drawable.Drawable);
method public void setThumbOffset(int);
method public void setThumbTintList(android.content.res.ColorStateList);
method public void setThumbTintMode(android.graphics.PorterDuff.Mode);
+ method public void setTickMark(android.graphics.drawable.Drawable);
+ method public void setTickMarkTintList(android.content.res.ColorStateList);
+ method public void setTickMarkTintMode(android.graphics.PorterDuff.Mode);
}
public abstract class AbsSpinner extends android.widget.AdapterView {
diff --git a/api/test-current.txt b/api/test-current.txt
index 8ff46c2..306cf73 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1288,6 +1288,9 @@
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
field public static final int thumbnail = 16843429; // 0x10102a5
+ field public static final int tickMark = 16844043; // 0x101050b
+ field public static final int tickMarkTint = 16844044; // 0x101050c
+ field public static final int tickMarkTintMode = 16844045; // 0x101050d
field public static final int tileMode = 16843265; // 0x1010201
field public static final int tileModeX = 16843895; // 0x1010477
field public static final int tileModeY = 16843896; // 0x1010478
@@ -2547,6 +2550,7 @@
field public static final int Widget_Material_ScrollView = 16974462; // 0x103027e
field public static final int Widget_Material_SearchView = 16974463; // 0x103027f
field public static final int Widget_Material_SeekBar = 16974464; // 0x1030280
+ field public static final int Widget_Material_SeekBar_Discrete = 16974553; // 0x10302d9
field public static final int Widget_Material_SegmentedButton = 16974465; // 0x1030281
field public static final int Widget_Material_Spinner = 16974467; // 0x1030283
field public static final int Widget_Material_Spinner_Underlined = 16974468; // 0x1030284
@@ -3638,6 +3642,9 @@
method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
}
+ public static abstract class ActivityManager.BugreportMode implements java.lang.annotation.Annotation {
+ }
+
public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
ctor public ActivityManager.MemoryInfo();
method public int describeContents();
@@ -28460,6 +28467,7 @@
field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi";
field public static final java.lang.String DISALLOW_CREATE_WINDOWS = "no_create_windows";
field public static final java.lang.String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";
+ field public static final java.lang.String DISALLOW_DATA_ROAMING = "no_data_roaming";
field public static final java.lang.String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";
field public static final java.lang.String DISALLOW_FACTORY_RESET = "no_factory_reset";
field public static final java.lang.String DISALLOW_FUN = "no_fun";
@@ -44549,12 +44557,18 @@
method public int getThumbOffset();
method public android.content.res.ColorStateList getThumbTintList();
method public android.graphics.PorterDuff.Mode getThumbTintMode();
+ method public android.graphics.drawable.Drawable getTickMark();
+ method public android.content.res.ColorStateList getTickMarkTintList();
+ method public android.graphics.PorterDuff.Mode getTickMarkTintMode();
method public void setKeyProgressIncrement(int);
method public void setSplitTrack(boolean);
method public void setThumb(android.graphics.drawable.Drawable);
method public void setThumbOffset(int);
method public void setThumbTintList(android.content.res.ColorStateList);
method public void setThumbTintMode(android.graphics.PorterDuff.Mode);
+ method public void setTickMark(android.graphics.drawable.Drawable);
+ method public void setTickMarkTintList(android.content.res.ColorStateList);
+ method public void setTickMarkTintMode(android.graphics.PorterDuff.Mode);
}
public abstract class AbsSpinner extends android.widget.AdapterView {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 6302d74..72e8c3b 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1080,16 +1080,16 @@
private void runBugReport() throws Exception {
String opt;
- boolean progress = false;
+ int bugreportType = ActivityManager.BUGREPORT_OPTION_FULL;
while ((opt=nextOption()) != null) {
if (opt.equals("--progress")) {
- progress = true;
+ bugreportType = ActivityManager.BUGREPORT_OPTION_INTERACTIVE;
} else {
System.err.println("Error: Unknown option: " + opt);
return;
}
}
- mAm.requestBugReport(progress);
+ mAm.requestBugReport(bugreportType);
System.out.println("Your lovely bug report is being created; please be patient.");
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8637dde..9540ae1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -36,10 +37,12 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.UriPermission;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -59,12 +62,15 @@
import android.util.DisplayMetrics;
import android.util.Size;
import android.util.Slog;
+
import org.xmlpull.v1.XmlSerializer;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -79,6 +85,36 @@
private final Context mContext;
private final Handler mHandler;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ BUGREPORT_OPTION_FULL,
+ BUGREPORT_OPTION_INTERACTIVE,
+ BUGREPORT_OPTION_REMOTE
+ })
+ /**
+ * Defines acceptable types of bugreports.
+ * @hide
+ */
+ public @interface BugreportMode {}
+ /**
+ * Takes a bugreport without user interference (and hence causing less
+ * interference to the system), but includes all sections.
+ * @hide
+ */
+ public static final int BUGREPORT_OPTION_FULL = 0;
+ /**
+ * Allows user to monitor progress and enter additional data; might not include all
+ * sections.
+ * @hide
+ */
+ public static final int BUGREPORT_OPTION_INTERACTIVE = 1;
+ /**
+ * Takes a bugreport requested remotely by administrator of the Device Owner app,
+ * not the device's user.
+ * @hide
+ */
+ public static final int BUGREPORT_OPTION_REMOTE = 2;
+
/**
* <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
* <meta-data>}</a> name for a 'home' Activity that declares a package that is to be
@@ -2256,6 +2292,45 @@
return clearApplicationUserData(mContext.getPackageName(), null);
}
+
+ /**
+ * Permits an application to get the persistent URI permissions granted to another.
+ *
+ * <p>Typically called by Settings.
+ *
+ * @param packageName application to look for the granted permissions
+ * @return list of granted URI permissions
+ *
+ * @hide
+ */
+ public ParceledListSlice<UriPermission> getGrantedUriPermissions(String packageName) {
+ try {
+ return ActivityManagerNative.getDefault().getGrantedUriPermissions(packageName,
+ UserHandle.myUserId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't get granted URI permissions for :" + packageName, e);
+ return ParceledListSlice.emptyList();
+ }
+ }
+
+ /**
+ * Permits an application to clear the persistent URI permissions granted to another.
+ *
+ * <p>Typically called by Settings.
+ *
+ * @param packageName application to clear its granted permissions
+ *
+ * @hide
+ */
+ public void clearGrantedUriPermissions(String packageName) {
+ try {
+ ActivityManagerNative.getDefault().clearGrantedUriPermissions(packageName,
+ UserHandle.myUserId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't clear granted URI permissions for :" + packageName, e);
+ }
+ }
+
/**
* Information you can retrieve about any processes that are in an error condition.
*/
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4bea112..624131e 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1408,6 +1408,26 @@
return true;
}
+ case GET_GRANTED_URI_PERMISSIONS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final String packageName = data.readString();
+ final int userId = data.readInt();
+ final ParceledListSlice<UriPermission> perms = getGrantedUriPermissions(packageName,
+ userId);
+ reply.writeNoException();
+ perms.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ return true;
+ }
+
+ case CLEAR_GRANTED_URI_PERMISSIONS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final String packageName = data.readString();
+ final int userId = data.readInt();
+ clearGrantedUriPermissions(packageName, userId);
+ reply.writeNoException();
+ return true;
+ }
+
case SHOW_WAITING_FOR_DEBUGGER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
@@ -2286,8 +2306,8 @@
case REQUEST_BUG_REPORT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
- boolean progress = data.readInt() != 0;
- requestBugReport(progress);
+ int bugreportType = data.readInt();
+ requestBugReport(bugreportType);
reply.writeNoException();
return true;
}
@@ -4611,6 +4631,7 @@
data.writeInt(incoming ? 1 : 0);
mRemote.transact(GET_PERSISTED_URI_PERMISSIONS_TRANSACTION, data, reply, 0);
reply.readException();
+ @SuppressWarnings("unchecked")
final ParceledListSlice<UriPermission> perms = ParceledListSlice.CREATOR.createFromParcel(
reply);
data.recycle();
@@ -4618,6 +4639,37 @@
return perms;
}
+ @Override
+ public ParceledListSlice<UriPermission> getGrantedUriPermissions(String packageName, int userId)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ data.writeInt(userId);
+ mRemote.transact(GET_GRANTED_URI_PERMISSIONS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ @SuppressWarnings("unchecked")
+ final ParceledListSlice<UriPermission> perms = ParceledListSlice.CREATOR.createFromParcel(
+ reply);
+ data.recycle();
+ reply.recycle();
+ return perms;
+ }
+
+ @Override
+ public void clearGrantedUriPermissions(String packageName, int userId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ data.writeInt(userId);
+ mRemote.transact(CLEAR_GRANTED_URI_PERMISSIONS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
public void showWaitingForDebugger(IApplicationThread who, boolean waiting)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -5768,11 +5820,12 @@
reply.recycle();
}
- public void requestBugReport(boolean progress) throws RemoteException {
+ public void requestBugReport(@ActivityManager.BugreportMode int bugreportType)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeInt(progress ? 1 : 0);
+ data.writeInt(bugreportType);
mRemote.transact(REQUEST_BUG_REPORT_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8804c8b..1ae91a6 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -276,6 +276,15 @@
public ParceledListSlice<UriPermission> getPersistedUriPermissions(
String packageName, boolean incoming) throws RemoteException;
+ // Gets the URI permissions granted to an arbitrary package.
+ // NOTE: this is different from getPersistedUriPermissions(), which returns the URIs the package
+ // granted to another packages (instead of those granted to it).
+ public ParceledListSlice<UriPermission> getGrantedUriPermissions(String packageName, int userId)
+ throws RemoteException;
+
+ // Clears the URI permissions granted to an arbitrary package.
+ public void clearGrantedUriPermissions(String packageName, int userId) throws RemoteException;
+
public void showWaitingForDebugger(IApplicationThread who, boolean waiting)
throws RemoteException;
@@ -464,7 +473,7 @@
public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
- public void requestBugReport(boolean progress) throws RemoteException;
+ public void requestBugReport(int bugreportType) throws RemoteException;
public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason)
throws RemoteException;
@@ -949,4 +958,6 @@
int GET_URI_PERMISSION_OWNER_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 357;
int RESIZE_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 358;
int SET_VR_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 359;
+ int GET_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 360;
+ int CLEAR_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 361;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9d94b74..cc00308 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -27,8 +27,8 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.StringRes;
import android.annotation.SystemApi;
-import android.annotation.UserIdInt;
import android.annotation.TestApi;
+import android.annotation.UserIdInt;
import android.annotation.XmlRes;
import android.app.PackageDeleteObserver;
import android.app.PackageInstallObserver;
@@ -50,6 +50,7 @@
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
import android.util.AndroidException;
+import android.util.Log;
import com.android.internal.util.ArrayUtils;
@@ -65,6 +66,7 @@
* You can find this class through {@link Context#getPackageManager}.
*/
public abstract class PackageManager {
+ private static final String TAG = "PackageManager";
/**
* This exception is thrown when a given package, application, or component
@@ -3329,6 +3331,14 @@
public abstract List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent,
@ResolveInfoFlags int flags, @UserIdInt int userId);
+ /** {@hide} */
+ @Deprecated
+ public List<ResolveInfo> queryBroadcastReceivers(Intent intent,
+ @ResolveInfoFlags int flags, @UserIdInt int userId) {
+ Log.w(TAG, "STAHP USING HIDDEN APIS KTHX");
+ return queryBroadcastReceiversAsUser(intent, flags, userId);
+ }
+
/**
* Determine the best service to handle for a given Intent.
*
@@ -3958,7 +3968,6 @@
* but the older observer interface will not get additional
* failure details.
*/
- // @SystemApi
public abstract void installPackage(
Uri packageURI, IPackageInstallObserver observer, int flags,
String installerPackageName);
@@ -3994,7 +4003,6 @@
* continue to be supported but the older observer interface
* will not get additional failure details.
*/
- // @SystemApi
public abstract void installPackageWithVerification(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
Uri verificationURI,
@@ -4148,7 +4156,6 @@
* on the system for other users, also install it for the calling user.
* @hide
*/
- // @SystemApi
public abstract int installExistingPackage(String packageName) throws NameNotFoundException;
/**
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index a66ea49..e555fa4 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -93,7 +93,7 @@
public static Collection<NetworkScorerAppData> getAllValidScorers(Context context) {
// Network scorer apps can only run as the primary user so exit early if we're not the
// primary user.
- if (UserHandle.getCallingUserId() != 0 /*USER_SYSTEM*/) {
+ if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) {
return Collections.emptyList();
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 887dd17..d1b3f59 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -518,6 +518,16 @@
public static final String DISALLOW_CAMERA = "no_camera";
/**
+ * Specifies if a user is not allowed to use cellular data when roaming. This can only be set by
+ * device owners. The default value is <code>false</code>.
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_DATA_ROAMING = "no_data_roaming";
+
+ /**
* Allows apps in the parent profile to handle web links from the managed profile.
*
* This user restriction has an effect only in a managed profile.
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index cf4587d..831481dd 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -44,6 +44,12 @@
private boolean mHasThumbTint = false;
private boolean mHasThumbTintMode = false;
+ private Drawable mTickMark;
+ private ColorStateList mTickMarkTintList = null;
+ private PorterDuff.Mode mTickMarkTintMode = null;
+ private boolean mHasTickMarkTint = false;
+ private boolean mHasTickMarkTintMode = false;
+
private int mThumbOffset;
private boolean mSplitTrack;
@@ -103,10 +109,25 @@
mHasThumbTint = true;
}
+ final Drawable tickMark = a.getDrawable(R.styleable.SeekBar_tickMark);
+ setTickMark(tickMark);
+
+ if (a.hasValue(R.styleable.SeekBar_tickMarkTintMode)) {
+ mTickMarkTintMode = Drawable.parseTintMode(a.getInt(
+ R.styleable.SeekBar_tickMarkTintMode, -1), mTickMarkTintMode);
+ mHasTickMarkTintMode = true;
+ }
+
+ if (a.hasValue(R.styleable.SeekBar_tickMarkTint)) {
+ mTickMarkTintList = a.getColorStateList(R.styleable.SeekBar_tickMarkTint);
+ mHasTickMarkTint = true;
+ }
+
mSplitTrack = a.getBoolean(R.styleable.SeekBar_splitTrack, false);
// Guess thumb offset if thumb != null, but allow layout to override.
- final int thumbOffset = a.getDimensionPixelOffset(R.styleable.SeekBar_thumbOffset, getThumbOffset());
+ final int thumbOffset = a.getDimensionPixelOffset(
+ R.styleable.SeekBar_thumbOffset, getThumbOffset());
setThumbOffset(thumbOffset);
final boolean useDisabledAlpha = a.getBoolean(R.styleable.SeekBar_useDisabledAlpha, true);
@@ -313,6 +334,123 @@
}
/**
+ * Sets the drawable displayed at each progress position, e.g. at each
+ * possible thumb position.
+ *
+ * @param tickMark the drawable to display at each progress position
+ */
+ public void setTickMark(Drawable tickMark) {
+ if (mTickMark != null) {
+ mTickMark.setCallback(null);
+ }
+
+ mTickMark = tickMark;
+
+ if (tickMark != null) {
+ tickMark.setCallback(this);
+ tickMark.setLayoutDirection(getLayoutDirection());
+ if (tickMark.isStateful()) {
+ tickMark.setState(getDrawableState());
+ }
+ applyTickMarkTint();
+ }
+
+ invalidate();
+ }
+
+ /**
+ * @return the drawable displayed at each progress position
+ */
+ public Drawable getTickMark() {
+ return mTickMark;
+ }
+
+ /**
+ * Applies a tint to the tick mark drawable. Does not modify the current tint
+ * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setTickMark(Drawable)} will automatically
+ * mutate the drawable and apply the specified tint and tint mode using
+ * {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#SeekBar_tickMarkTint
+ * @see #getTickMarkTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setTickMarkTintList(@Nullable ColorStateList tint) {
+ mTickMarkTintList = tint;
+ mHasTickMarkTint = true;
+
+ applyTickMarkTint();
+ }
+
+ /**
+ * Returns the tint applied to the tick mark drawable, if specified.
+ *
+ * @return the tint applied to the tick mark drawable
+ * @attr ref android.R.styleable#SeekBar_tickMarkTint
+ * @see #setTickMarkTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getTickMarkTintList() {
+ return mTickMarkTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setTickMarkTintList(ColorStateList)}} to the tick mark drawable. The
+ * default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#SeekBar_tickMarkTintMode
+ * @see #getTickMarkTintMode()
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setTickMarkTintMode(@Nullable PorterDuff.Mode tintMode) {
+ mTickMarkTintMode = tintMode;
+ mHasTickMarkTintMode = true;
+
+ applyTickMarkTint();
+ }
+
+ /**
+ * Returns the blending mode used to apply the tint to the tick mark drawable,
+ * if specified.
+ *
+ * @return the blending mode used to apply the tint to the tick mark drawable
+ * @attr ref android.R.styleable#SeekBar_tickMarkTintMode
+ * @see #setTickMarkTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getTickMarkTintMode() {
+ return mTickMarkTintMode;
+ }
+
+ private void applyTickMarkTint() {
+ if (mTickMark != null && (mHasTickMarkTint || mHasTickMarkTintMode)) {
+ mTickMark = mTickMark.mutate();
+
+ if (mHasTickMarkTint) {
+ mTickMark.setTintList(mTickMarkTintList);
+ }
+
+ if (mHasTickMarkTintMode) {
+ mTickMark.setTintMode(mTickMarkTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mTickMark.isStateful()) {
+ mTickMark.setState(getDrawableState());
+ }
+ }
+ }
+
+ /**
* Sets the amount of progress changed via the arrow keys.
*
* @param increment The amount to increment or decrement when the user
@@ -347,7 +485,7 @@
@Override
protected boolean verifyDrawable(Drawable who) {
- return who == mThumb || super.verifyDrawable(who);
+ return who == mThumb || who == mTickMark || super.verifyDrawable(who);
}
@Override
@@ -357,6 +495,10 @@
if (mThumb != null) {
mThumb.jumpToCurrentState();
}
+
+ if (mTickMark != null) {
+ mTickMark.jumpToCurrentState();
+ }
}
@Override
@@ -373,6 +515,12 @@
&& thumb.setState(getDrawableState())) {
invalidateDrawable(thumb);
}
+
+ final Drawable tickMark = mTickMark;
+ if (tickMark != null && tickMark.isStateful()
+ && tickMark.setState(getDrawableState())) {
+ invalidateDrawable(tickMark);
+ }
}
@Override
@@ -524,9 +672,36 @@
final int saveCount = canvas.save();
canvas.clipRect(tempRect, Op.DIFFERENCE);
super.drawTrack(canvas);
+ drawTickMarks(canvas);
canvas.restoreToCount(saveCount);
} else {
super.drawTrack(canvas);
+ drawTickMarks(canvas);
+ }
+ }
+
+ /**
+ * Draw the tick marks.
+ */
+ void drawTickMarks(Canvas canvas) {
+ if (mTickMark != null) {
+ final int count = getMax();
+ if (count > 1) {
+ final int w = mTickMark.getIntrinsicWidth();
+ final int h = mTickMark.getIntrinsicHeight();
+ final int halfW = w >= 0 ? w / 2 : 1;
+ final int halfH = h >= 0 ? h / 2 : 1;
+ mTickMark.setBounds(-halfW, -halfH, halfW, halfH);
+
+ final int spacing = (getWidth() - mPaddingLeft - mPaddingRight) / count;
+ final int saveCount = canvas.save();
+ canvas.translate(mPaddingLeft, getHeight() / 2);
+ for (int i = 0; i <= count; i++) {
+ mTickMark.draw(canvas);
+ canvas.translate(spacing, 0);
+ }
+ canvas.restoreToCount(saveCount);
+ }
}
}
@@ -535,12 +710,12 @@
*/
void drawThumb(Canvas canvas) {
if (mThumb != null) {
- canvas.save();
+ final int saveCount = canvas.save();
// Translate the padding. For the x, we need to allow the thumb to
// draw in its extra space
canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
mThumb.draw(canvas);
- canvas.restore();
+ canvas.restoreToCount(saveCount);
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7d9fd93..e8054fc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -391,6 +391,9 @@
<protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" />
<protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+ <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
+ <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
+
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
@@ -2192,6 +2195,21 @@
<permission android:name="android.permission.CLEAR_APP_USER_DATA"
android:protectionLevel="signature|installer" />
+ <!-- @hide Allows an application to get the URI permissions
+ granted to another application.
+ <p>Not for use by third-party applications
+ -->
+ <permission android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to clear the URI permissions
+ granted to another application.
+ <p>Not for use by third-party applications
+ -->
+ <permission
+ android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to delete cache files.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.DELETE_CACHE_FILES"
diff --git a/core/res/res/drawable/seekbar_tick_mark_material.xml b/core/res/res/drawable/seekbar_tick_mark_material.xml
new file mode 100644
index 0000000..d2c38a2
--- /dev/null
+++ b/core/res/res/drawable/seekbar_tick_mark_material.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval"
+ android:tint="?attr/colorControlNormal">
+ <size android:width="@dimen/progress_bar_height_material"
+ android:height="@dimen/progress_bar_height_material" />
+ <solid android:color="@color/white" />
+</shape>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9f13565..bf7a183 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4039,9 +4039,9 @@
should always be false for Material and beyond.
@hide Developers shouldn't need to change this. -->
<attr name="useDisabledAlpha" format="boolean" />
- <!-- Tint to apply to the button graphic. -->
+ <!-- Tint to apply to the thumb drawable. -->
<attr name="thumbTint" format="color" />
- <!-- Blending mode used to apply the button graphic tint. -->
+ <!-- Blending mode used to apply the thumb tint. -->
<attr name="thumbTintMode">
<!-- The tint is drawn on top of the drawable.
[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
@@ -4061,6 +4061,30 @@
result to valid color values. Saturate(S + D) -->
<enum name="add" value="16" />
</attr>
+ <!-- Drawable displayed at each progress position on a seekbar. -->
+ <attr name="tickMark" format="reference" />
+ <!-- Tint to apply to the tick mark drawable. -->
+ <attr name="tickMarkTint" format="color" />
+ <!-- Blending mode used to apply the tick mark tint. -->
+ <attr name="tickMarkTintMode">
+ <!-- The tint is drawn on top of the drawable.
+ [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+ <enum name="src_over" value="3" />
+ <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+ color channels are thrown out. [Sa * Da, Sc * Da] -->
+ <enum name="src_in" value="5" />
+ <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+ channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+ <enum name="src_atop" value="9" />
+ <!-- Multiplies the color and alpha channels of the drawable with those of
+ the tint. [Sa * Da, Sc * Dc] -->
+ <enum name="multiply" value="14" />
+ <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+ <enum name="screen" value="15" />
+ <!-- Combines the tint and drawable color and alpha channels, clamping the
+ result to valid color values. Saturate(S + D) -->
+ <enum name="add" value="16" />
+ </attr>
</declare-styleable>
<declare-styleable name="StackView">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 09c1717..ebb1b37 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2685,8 +2685,12 @@
<public type="attr" name="canControlMagnification" />
<public type="attr" name="languageTag" />
<public type="attr" name="pointerShape" />
+ <public type="attr" name="tickMark" />
+ <public type="attr" name="tickMarkTint" />
+ <public type="attr" name="tickMarkTintMode" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
+ <public type="style" name="Widget.Material.SeekBar.Discrete" />
<public type="id" name="accessibilityActionSetProgress" />
<public type="id" name="icon_frame" />
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 8485e59..3d5f6ab 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -745,6 +745,11 @@
<item name="background">@drawable/control_background_32dp_material</item>
</style>
+ <!-- A seek bar with tick marks at each progress value. -->
+ <style name="Widget.Material.SeekBar.Discrete">
+ <item name="tickMark">@drawable/seekbar_tick_mark_material</item>
+ </style>
+
<style name="Widget.Material.RatingBar" parent="Widget.RatingBar">
<item name="progressDrawable">@drawable/ratingbar_material</item>
<item name="indeterminateDrawable">@drawable/ratingbar_material</item>
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 097675a..f58ca31 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -753,5 +753,13 @@
}
}
+void BakedOpDispatcher::onCopyToLayerOp(BakedOpRenderer& renderer, const CopyToLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("TODO!");
+}
+
+void BakedOpDispatcher::onCopyFromLayerOp(BakedOpRenderer& renderer, const CopyFromLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("TODO!");
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index a0d5fae..4aebe3c 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -236,12 +236,13 @@
}
void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* clip) {
- // prepare scissor / stencil
+ // Prepare scissor (done before stencil, to simplify filling stencil)
mRenderState.scissor().setEnabled(clip != nullptr);
if (clip) {
mRenderState.scissor().set(mRenderTarget.viewportHeight, clip->rect);
}
+ // If stencil may be used for clipping, enable it, fill it, or disable it as appropriate
if (CC_LIKELY(!Properties::debugOverdraw)) {
// only modify stencil mode and content when it's not used for overdraw visualization
if (CC_UNLIKELY(clip && clip->mode != ClipMode::Rectangle)) {
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index e6b943a..b7c9281 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -75,5 +75,11 @@
clipSideFlags = OpClipSideFlags::Full;
}
+ResolvedRenderState::ResolvedRenderState(const Rect& dstRect)
+ : transform(Matrix4::identity())
+ , clipState(nullptr)
+ , clippedBounds(dstRect)
+ , clipSideFlags(OpClipSideFlags::None) {}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 9df4e3a..70b0484 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -58,6 +58,10 @@
// Constructor for unbounded ops without transform/clip (namely shadows)
ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot);
+ // Constructor for primitive ops without clip or transform
+ // NOTE: these ops can't be queried for RT clip / local clip
+ ResolvedRenderState(const Rect& dstRect);
+
Rect computeLocalSpaceClip() const {
Matrix4 inverse;
inverse.loadInverse(transform);
@@ -67,10 +71,12 @@
return outClip;
}
- Matrix4 transform;
+ // NOTE: Can only be used on clipped/snapshot based ops
const Rect& clipRect() const {
return clipState->rect;
}
+
+ // NOTE: Can only be used on clipped/snapshot based ops
bool requiresClip() const {
return clipSideFlags != OpClipSideFlags::None
|| CC_UNLIKELY(clipState->mode != ClipMode::Rectangle);
@@ -80,9 +86,11 @@
const ClipBase* getClipIfNeeded() const {
return requiresClip() ? clipState : nullptr;
}
+
+ Matrix4 transform;
const ClipBase* clipState = nullptr;
- int clipSideFlags = 0;
Rect clippedBounds;
+ int clipSideFlags = 0;
};
/**
@@ -135,12 +143,17 @@
return new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr);
}
+ static BakedOpState* directConstruct(LinearAllocator& allocator,
+ const Rect& dstRect, const RecordedOp& recordedOp) {
+ return new (allocator) BakedOpState(dstRect, recordedOp);
+ }
+
static void* operator new(size_t size, LinearAllocator& allocator) {
return allocator.alloc(size);
}
// computed state:
- const ResolvedRenderState computedState;
+ ResolvedRenderState computedState;
// simple state (straight pointer/value storage):
const float alpha;
@@ -163,6 +176,13 @@
, roundRectClipState(snapshot.roundRectClipState)
, projectionPathMask(snapshot.projectionPathMask)
, op(shadowOpPtr) {}
+
+ BakedOpState(const Rect& dstRect, const RecordedOp& recordedOp)
+ : computedState(dstRect)
+ , alpha(1.0f)
+ , roundRectClipState(nullptr)
+ , projectionPathMask(nullptr)
+ , op(&recordedOp) {}
};
}; // namespace uirenderer
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 3f492d5..34c3d60 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -240,8 +240,50 @@
}
}
+void OpReorderer::LayerReorderer::deferLayerClear(const Rect& rect) {
+ mClearRects.push_back(rect);
+}
+
+void OpReorderer::LayerReorderer::flushLayerClears(LinearAllocator& allocator) {
+ if (CC_UNLIKELY(!mClearRects.empty())) {
+ const int vertCount = mClearRects.size() * 4;
+ // put the verts in the frame allocator, since
+ // 1) SimpleRectsOps needs verts, not rects
+ // 2) even if mClearRects stored verts, std::vectors will move their contents
+ Vertex* const verts = (Vertex*) allocator.alloc(vertCount * sizeof(Vertex));
+
+ Vertex* currentVert = verts;
+ Rect bounds = mClearRects[0];
+ for (auto&& rect : mClearRects) {
+ bounds.unionWith(rect);
+ Vertex::set(currentVert++, rect.left, rect.top);
+ Vertex::set(currentVert++, rect.right, rect.top);
+ Vertex::set(currentVert++, rect.left, rect.bottom);
+ Vertex::set(currentVert++, rect.right, rect.bottom);
+ }
+ mClearRects.clear(); // discard rects before drawing so this method isn't reentrant
+
+ // One or more unclipped saveLayers have been enqueued, with deferred clears.
+ // Flush all of these clears with a single draw
+ SkPaint* paint = allocator.create<SkPaint>();
+ paint->setXfermodeMode(SkXfermode::kClear_Mode);
+ SimpleRectsOp* op = new (allocator) SimpleRectsOp(bounds,
+ Matrix4::identity(), nullptr, paint,
+ verts, vertCount);
+ BakedOpState* bakedState = BakedOpState::directConstruct(allocator, bounds, *op);
+
+
+ deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices);
+ }
+}
+
void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId) {
+ if (batchId != OpBatchType::CopyToLayer) {
+ // if first op after one or more unclipped saveLayers, flush the layer clears
+ flushLayerClears(allocator);
+ }
+
OpBatch* targetBatch = mBatchLookup[batchId];
size_t insertBatchIndex = mBatches.size();
@@ -260,10 +302,12 @@
}
}
-// insertion point of a new batch, will hopefully be immediately after similar batch
-// (generally, should be similar shader)
void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
+ if (batchId != OpBatchType::CopyToLayer) {
+ // if first op after one or more unclipped saveLayers, flush the layer clears
+ flushLayerClears(allocator);
+ }
MergingOpBatch* targetBatch = nullptr;
// Try to merge with any existing batch with same mergeId
@@ -726,6 +770,11 @@
deferStrokeableOp(op, tessBatchId(op));
}
+static bool hasMergeableClip(const BakedOpState& state) {
+ return state.computedState.clipState
+ || state.computedState.clipState->mode == ClipMode::Rectangle;
+}
+
void OpReorderer::deferBitmapOp(const BitmapOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
@@ -736,7 +785,8 @@
if (bakedState->computedState.transform.isSimple()
&& bakedState->computedState.transform.positiveScale()
&& PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
- && op.bitmap->colorType() != kAlpha_8_SkColorType) {
+ && op.bitmap->colorType() != kAlpha_8_SkColorType
+ && hasMergeableClip(*bakedState)) {
mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
// TODO: AssetAtlas in mergeId
currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
@@ -792,7 +842,8 @@
if (!bakedState) return; // quick rejected
if (bakedState->computedState.transform.isPureTranslate()
- && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && hasMergeableClip(*bakedState)) {
mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
// TODO: AssetAtlas in mergeId
@@ -849,7 +900,8 @@
batchid_t batchId = textBatchId(*(op.paint));
if (bakedState->computedState.transform.isPureTranslate()
- && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && hasMergeableClip(*bakedState)) {
mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
} else {
@@ -894,7 +946,8 @@
mLayerStack.pop_back();
}
-// TODO: test rejection at defer time, where the bounds become empty
+// TODO: defer time rejection (when bounds become empty) + tests
+// Option - just skip layers with no bounds at playback + defer?
void OpReorderer::deferBeginLayerOp(const BeginLayerOp& op) {
uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
@@ -904,7 +957,7 @@
// Combine all transforms used to present saveLayer content:
// parent content transform * canvas transform * bounds offset
- Matrix4 contentTransform(*previous->transform);
+ Matrix4 contentTransform(*(previous->transform));
contentTransform.multiply(op.localMatrix);
contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
@@ -961,10 +1014,53 @@
currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
} else {
// Layer won't be drawn - delete its drawing batches to prevent it from doing any work
+ // TODO: need to prevent any render work from being done
+ // - create layerop earlier for reject purposes?
mLayerReorderers[finishedLayerIndex].clear();
return;
}
}
+void OpReorderer::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) {
+ Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform));
+ boundsTransform.multiply(op.localMatrix);
+
+ Rect dstRect(op.unmappedBounds);
+ boundsTransform.mapRect(dstRect);
+ dstRect.doIntersect(mCanvasState.currentSnapshot()->getRenderTargetClip());
+
+ // Allocate a holding position for the layer object (copyTo will produce, copyFrom will consume)
+ OffscreenBuffer** layerHandle = mAllocator.create<OffscreenBuffer*>();
+
+ /**
+ * First, defer an operation to copy out the content from the rendertarget into a layer.
+ */
+ auto copyToOp = new (mAllocator) CopyToLayerOp(op, layerHandle);
+ BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator, dstRect, *copyToOp);
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer);
+
+ /**
+ * Defer a clear rect, so that clears from multiple unclipped layers can be drawn
+ * both 1) simultaneously, and 2) as long after the copyToLayer executes as possible
+ */
+ currentLayer().deferLayerClear(dstRect);
+
+ /**
+ * And stash an operation to copy that layer back under the rendertarget until
+ * a balanced EndUnclippedLayerOp is seen
+ */
+ auto copyFromOp = new (mAllocator) CopyFromLayerOp(op, layerHandle);
+ bakedState = BakedOpState::directConstruct(mAllocator, dstRect, *copyFromOp);
+ currentLayer().activeUnclippedSaveLayers.push_back(bakedState);
+}
+
+void OpReorderer::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& op) {
+ LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!");
+
+ BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back();
+ currentLayer().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer);
+ currentLayer().activeUnclippedSaveLayers.pop_back();
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 429913f..b824d02 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -53,6 +53,8 @@
Shadow,
TextureLayer,
Functor,
+ CopyToLayer,
+ CopyFromLayer,
Count // must be last
};
@@ -91,6 +93,8 @@
void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const;
+ void deferLayerClear(const Rect& dstRect);
+
bool empty() const {
return mBatches.empty();
}
@@ -107,7 +111,12 @@
OffscreenBuffer* offscreenBuffer;
const BeginLayerOp* beginLayerOp;
const RenderNode* renderNode;
+
+ // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
+ std::vector<BakedOpState*> activeUnclippedSaveLayers;
private:
+ void flushLayerClears(LinearAllocator& allocator);
+
std::vector<BatchBase*> mBatches;
/**
@@ -119,6 +128,8 @@
// Maps batch ids to the most recent *non-merging* batch of that id
OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
+
+ std::vector<Rect> mClearRects;
};
public:
@@ -147,7 +158,8 @@
*/
#define X(Type) \
[](void* renderer, const BakedOpState& state) { \
- StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), static_cast<const Type&>(*(state.op)), state); \
+ StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), \
+ static_cast<const Type&>(*(state.op)), state); \
},
static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
#undef X
@@ -233,8 +245,7 @@
void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
SkPath* createFrameAllocatedPath() {
- mFrameAllocatedPaths.emplace_back(new SkPath);
- return mFrameAllocatedPaths.back().get();
+ return mAllocator.create<SkPath>();
}
void deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
@@ -250,8 +261,6 @@
MAP_DEFERRABLE_OPS(X)
#undef X
- std::vector<std::unique_ptr<SkPath> > mFrameAllocatedPaths;
-
// List of every deferred layer's render state. Replayed in reverse order to render a frame.
std::vector<LayerReorderer> mLayerReorderers;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index b243f99..30d5c29 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -43,12 +43,28 @@
* the functions to which they dispatch. Parameter macros are executed for each op,
* in order, based on the op's type.
*
- * There are 4 types of op:
+ * There are 4 types of op, which defines dispatch/LUT capability:
*
- * Pre render - not directly consumed by renderer, reorder stage resolves this into renderable type
- * Render only - generated renderable ops - never passed to a reorderer
- * Unmergeable - reorderable, renderable (but not mergeable)
- * Mergeable - reorderable, renderable (and mergeable)
+ * | DisplayList | Render | Merge |
+ * -------------|-------------|-------------|-------------|
+ * PRE RENDER | Yes | | |
+ * RENDER ONLY | | Yes | |
+ * UNMERGEABLE | Yes | Yes | |
+ * MERGEABLE | Yes | Yes | Yes |
+ *
+ * PRE RENDER - These ops are recorded into DisplayLists, but can't be directly rendered. This
+ * may be because they need to be transformed into other op types (e.g. CirclePropsOp),
+ * be traversed to access multiple renderable ops within (e.g. RenderNodeOp), or because they
+ * modify renderbuffer lifecycle, instead of directly rendering content (the various LayerOps).
+ *
+ * RENDER ONLY - These ops cannot be recorded into DisplayLists, and are instead implicitly
+ * constructed from other commands/RenderNode properties. They cannot be merged.
+ *
+ * UNMERGEABLE - These ops can be recorded into DisplayLists and rendered directly, but do not
+ * support merged rendering.
+ *
+ * MERGEABLE - These ops can be recorded into DisplayLists and rendered individually, or merged
+ * under certain circumstances.
*/
#define MAP_OPS_BASED_ON_TYPE(PRE_RENDER_OP_FN, RENDER_ONLY_OP_FN, UNMERGEABLE_OP_FN, MERGEABLE_OP_FN) \
PRE_RENDER_OP_FN(RenderNodeOp) \
@@ -56,9 +72,13 @@
PRE_RENDER_OP_FN(RoundRectPropsOp) \
PRE_RENDER_OP_FN(BeginLayerOp) \
PRE_RENDER_OP_FN(EndLayerOp) \
+ PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \
+ PRE_RENDER_OP_FN(EndUnclippedLayerOp) \
\
RENDER_ONLY_OP_FN(ShadowOp) \
RENDER_ONLY_OP_FN(LayerOp) \
+ RENDER_ONLY_OP_FN(CopyToLayerOp) \
+ RENDER_ONLY_OP_FN(CopyFromLayerOp) \
\
UNMERGEABLE_OP_FN(ArcOp) \
UNMERGEABLE_OP_FN(BitmapMeshOp) \
@@ -99,15 +119,15 @@
*/
#define NULL_OP_FN(Type)
+#define MAP_DEFERRABLE_OPS(OP_FN) \
+ MAP_OPS_BASED_ON_TYPE(OP_FN, NULL_OP_FN, OP_FN, OP_FN)
+
#define MAP_MERGEABLE_OPS(OP_FN) \
MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, NULL_OP_FN, NULL_OP_FN, OP_FN)
#define MAP_RENDERABLE_OPS(OP_FN) \
MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, OP_FN, OP_FN, OP_FN)
-#define MAP_DEFERRABLE_OPS(OP_FN) \
- MAP_OPS_BASED_ON_TYPE(OP_FN, NULL_OP_FN, OP_FN, OP_FN)
-
// Generate OpId enum
#define IDENTITY_FN(Type) Type,
namespace RecordedOpId {
@@ -407,6 +427,46 @@
: RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
};
+struct BeginUnclippedLayerOp : RecordedOp {
+ BeginUnclippedLayerOp(BASE_PARAMS)
+ : SUPER(BeginUnclippedLayerOp) {}
+};
+
+struct EndUnclippedLayerOp : RecordedOp {
+ EndUnclippedLayerOp()
+ : RecordedOp(RecordedOpId::EndUnclippedLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
+};
+
+struct CopyToLayerOp : RecordedOp {
+ CopyToLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle)
+ : RecordedOp(RecordedOpId::CopyToLayerOp,
+ op.unmappedBounds,
+ op.localMatrix,
+ nullptr, // clip intentionally ignored
+ op.paint)
+ , layerHandle(layerHandle) {}
+
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ OffscreenBuffer** layerHandle;
+};
+
+
+// draw the parameter layer underneath
+struct CopyFromLayerOp : RecordedOp {
+ CopyFromLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle)
+ : RecordedOp(RecordedOpId::CopyFromLayerOp,
+ op.unmappedBounds,
+ op.localMatrix,
+ nullptr, // clip intentionally ignored
+ op.paint)
+ , layerHandle(layerHandle) {}
+
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ OffscreenBuffer** layerHandle;
+};
+
/**
* Draws an OffscreenBuffer.
*
@@ -424,12 +484,12 @@
, destroy(true) {}
LayerOp(RenderNode& node)
- : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
- , layerHandle(node.getLayerHandle())
- , alpha(node.properties().layerProperties().alpha() / 255.0f)
- , mode(node.properties().layerProperties().xferMode())
- , colorFilter(node.properties().layerProperties().colorFilter())
- , destroy(false) {}
+ : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
+ , layerHandle(node.getLayerHandle())
+ , alpha(node.properties().layerProperties().alpha() / 255.0f)
+ , mode(node.properties().layerProperties().xferMode())
+ , colorFilter(node.properties().layerProperties().colorFilter())
+ , destroy(false) {}
// Records a handle to the Layer object, since the Layer itself won't be
// constructed until after this operation is constructed.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index f7f6caf..78855e5 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -43,10 +43,10 @@
mDeferredBarrierType = DeferredBarrierType::InOrder;
mState.setDirtyClip(false);
- mRestoreSaveCount = -1;
}
DisplayList* RecordingCanvas::finishRecording() {
+ restoreToCount(1);
mPaintMap.clear();
mRegionMap.clear();
mPathMap.clear();
@@ -83,6 +83,8 @@
void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
if (removed.flags & Snapshot::kFlagIsFboLayer) {
addOp(new (alloc()) EndLayerOp());
+ } else if (removed.flags & Snapshot::kFlagIsLayer) {
+ addOp(new (alloc()) EndUnclippedLayerOp());
}
}
@@ -95,28 +97,18 @@
}
void RecordingCanvas::RecordingCanvas::restore() {
- if (mRestoreSaveCount < 0) {
- restoreToCount(getSaveCount() - 1);
- return;
- }
-
- mRestoreSaveCount--;
mState.restore();
}
void RecordingCanvas::restoreToCount(int saveCount) {
- mRestoreSaveCount = saveCount;
mState.restoreToCount(saveCount);
}
-int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
- SkCanvas::SaveFlags flags) {
- if (!(flags & SkCanvas::kClipToLayer_SaveFlag)) {
- LOG_ALWAYS_FATAL("unclipped layers not supported");
- }
+int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, SkCanvas::SaveFlags flags) {
// force matrix/clip isolation for layer
flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
-
+ bool clippedLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
const Snapshot& previous = *mState.currentSnapshot();
@@ -124,53 +116,70 @@
// operations will be able to store and restore the current clip and transform info, and
// quick rejection will be correct (for display lists)
- const Rect untransformedBounds(left, top, right, bottom);
+ const Rect unmappedBounds(left, top, right, bottom);
// determine clipped bounds relative to previous viewport.
- Rect visibleBounds = untransformedBounds;
+ Rect visibleBounds = unmappedBounds;
previous.transform->mapRect(visibleBounds);
+ if (CC_UNLIKELY(!clippedLayer
+ && previous.transform->rectToRect()
+ && visibleBounds.contains(previous.getRenderTargetClip()))) {
+ // unlikely case where an unclipped savelayer is recorded with a clip it can use,
+ // as none of its unaffected/unclipped area is visible
+ clippedLayer = true;
+ flags |= SkCanvas::kClipToLayer_SaveFlag;
+ }
visibleBounds.doIntersect(previous.getRenderTargetClip());
visibleBounds.snapToPixelBoundaries();
-
- Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight());
- visibleBounds.doIntersect(previousViewport);
+ visibleBounds.doIntersect(Rect(previous.getViewportWidth(), previous.getViewportHeight()));
// Map visible bounds back to layer space, and intersect with parameter bounds
Rect layerBounds = visibleBounds;
Matrix4 inverse;
inverse.loadInverse(*previous.transform);
inverse.mapRect(layerBounds);
- layerBounds.doIntersect(untransformedBounds);
+ layerBounds.doIntersect(unmappedBounds);
int saveValue = mState.save((int) flags);
Snapshot& snapshot = *mState.writableSnapshot();
- // layerBounds is now original bounds, but with clipped to clip
- // and viewport to ensure it's minimal size.
- if (layerBounds.isEmpty() || untransformedBounds.isEmpty()) {
+ // layerBounds is in original bounds space, but clipped by current recording clip
+ if (layerBounds.isEmpty() || unmappedBounds.isEmpty()) {
// Don't bother recording layer, since it's been rejected
- snapshot.resetClip(0, 0, 0, 0);
+ if (CC_LIKELY(clippedLayer)) {
+ snapshot.resetClip(0, 0, 0, 0);
+ }
return saveValue;
}
- auto previousClip = getRecordedClip(); // note: done while snapshot == previous
+ if (CC_LIKELY(clippedLayer)) {
+ auto previousClip = getRecordedClip(); // note: done before new snapshot's clip has changed
- snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
- snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
- snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+ snapshot.flags |= Snapshot::kFlagIsLayer | Snapshot::kFlagIsFboLayer;
+ snapshot.initializeViewport(unmappedBounds.getWidth(), unmappedBounds.getHeight());
+ snapshot.transform->loadTranslate(-unmappedBounds.left, -unmappedBounds.top, 0.0f);
- Rect clip = layerBounds;
- clip.translate(-untransformedBounds.left, -untransformedBounds.top);
- snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom);
- snapshot.roundRectClipState = nullptr;
+ Rect clip = layerBounds;
+ clip.translate(-unmappedBounds.left, -unmappedBounds.top);
+ snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom);
+ snapshot.roundRectClipState = nullptr;
- addOp(new (alloc()) BeginLayerOp(
- Rect(left, top, right, bottom),
- *previous.transform, // transform to *draw* with
- previousClip, // clip to *draw* with
- refPaint(paint)));
+ addOp(new (alloc()) BeginLayerOp(
+ unmappedBounds,
+ *previous.transform, // transform to *draw* with
+ previousClip, // clip to *draw* with
+ refPaint(paint)));
+ } else {
+ snapshot.flags |= Snapshot::kFlagIsLayer;
+
+ addOp(new (alloc()) BeginUnclippedLayerOp(
+ unmappedBounds,
+ *mState.currentSnapshot()->transform,
+ getRecordedClip(),
+ refPaint(paint)));
+ }
return saveValue;
}
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1a2ac97f..8aa7506 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -314,7 +314,6 @@
DisplayList* mDisplayList = nullptr;
bool mHighContrastText = false;
SkAutoTUnref<SkDrawFilter> mDrawFilter;
- int mRestoreSaveCount = -1;
}; // class RecordingCanvas
}; // namespace uirenderer
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 5fac3a1..dbaa905 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -116,7 +116,7 @@
* Indicates that this snapshot or an ancestor snapshot is
* an FBO layer.
*/
- kFlagFboTarget = 0x8,
+ kFlagFboTarget = 0x8, // TODO: remove for HWUI_NEW_OPS
};
/**
diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp
index 66dccb4..0ed70a0 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/OpReordererTests.cpp
@@ -423,7 +423,7 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-TEST(OpReorderer, saveLayerSimple) {
+TEST(OpReorderer, saveLayer_simple) {
class SaveLayerSimpleTestRenderer : public TestRendererBase {
public:
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
@@ -466,7 +466,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-TEST(OpReorderer, saveLayerNested) {
+TEST(OpReorderer, saveLayer_nested) {
/* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
* - startTemporaryLayer2, rect2 endLayer2
* - startTemporaryLayer1, rect1, drawLayer2, endLayer1
@@ -538,7 +538,7 @@
EXPECT_EQ(10, renderer.getIndex());
}
-TEST(OpReorderer, saveLayerContentRejection) {
+TEST(OpReorderer, saveLayer_contentRejection) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -559,7 +559,165 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
+TEST(OpReorderer, saveLayerUnclipped_simple) {
+ class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase {
+ public:
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_EQ(nullptr, state.computedState.clipState);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ ASSERT_NE(nullptr, op.paint);
+ ASSERT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint));
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ EXPECT_EQ(Rect(200, 200), op.unmappedBounds);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_EQ(nullptr, state.computedState.clipState);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.restore();
+ });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
+ SaveLayerUnclippedSimpleTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(OpReorderer, saveLayerUnclipped_mergedClears) {
+ class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase {
+ public:
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_GT(4, index);
+ EXPECT_EQ(5, op.unmappedBounds.getWidth());
+ EXPECT_EQ(5, op.unmappedBounds.getHeight());
+ if (index == 0) {
+ EXPECT_EQ(Rect(10, 10), state.computedState.clippedBounds);
+ } else if (index == 1) {
+ EXPECT_EQ(Rect(190, 0, 200, 10), state.computedState.clippedBounds);
+ } else if (index == 2) {
+ EXPECT_EQ(Rect(0, 190, 10, 200), state.computedState.clippedBounds);
+ } else if (index == 3) {
+ EXPECT_EQ(Rect(190, 190, 200, 200), state.computedState.clippedBounds);
+ }
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ ASSERT_EQ(op.vertexCount, 16u);
+ for (size_t i = 0; i < op.vertexCount; i++) {
+ auto v = op.vertices[i];
+ EXPECT_TRUE(v.x == 0 || v.x == 10 || v.x == 190 || v.x == 200);
+ EXPECT_TRUE(v.y == 0 || v.y == 10 || v.y == 190 || v.y == 200);
+ }
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ EXPECT_LT(5, mIndex++);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+
+ int restoreTo = canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.scale(2, 2);
+ canvas.saveLayerAlpha(0, 0, 5, 5, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.saveLayerAlpha(95, 0, 100, 5, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.saveLayerAlpha(0, 95, 5, 100, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.saveLayerAlpha(95, 95, 100, 100, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.restoreToCount(restoreTo);
+ });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
+ SaveLayerUnclippedMergedClearsTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(10, renderer.getIndex())
+ << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect.";
+}
+
+/* saveLayerUnclipped { saveLayer { saveLayerUnclipped { rect } } } will play back as:
+ * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer
+ * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe
+ */
+TEST(OpReorderer, saveLayerUnclipped_complex) {
+ class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
+ EXPECT_EQ(0, mIndex++); // savelayer first
+ return (OffscreenBuffer*)0xabcd;
+ }
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 1 || index == 7);
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 8);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ Matrix4 expected;
+ expected.loadTranslate(-100, -100, 0);
+ EXPECT_EQ(Rect(100, 100, 200, 200), state.computedState.clippedBounds);
+ EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform);
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 4 || index == 10);
+ }
+ void endLayer() override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(6, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(9, mIndex++);
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(11, mIndex++);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 600, 600, // 500x500 triggers clipping
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.saveLayerAlpha(100, 100, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag); // clipped
+ canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.drawRect(200, 200, 300, 300, SkPaint());
+ canvas.restore();
+ canvas.restore();
+ canvas.restore();
+ });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600,
+ createSyncedNodeList(node), sLightCenter);
+ SaveLayerUnclippedComplexTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(12, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(OpReorderer, hwLayer_simple) {
class HwLayerSimpleTestRenderer : public TestRendererBase {
public:
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
@@ -620,7 +778,7 @@
*layerHandle = nullptr;
}
-RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
+RENDERTHREAD_TEST(OpReorderer, hwLayer_complex) {
/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
* - startRepaintLayer(child), rect(grey), endLayer
* - startTemporaryLayer, drawLayer(child), endLayer
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index a63cb18..795ac30 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -232,7 +232,7 @@
TEST(RecordingCanvas, saveLayer_simple) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(10, 20, 190, 180, SkPaint());
canvas.restore();
});
@@ -264,12 +264,78 @@
EXPECT_EQ(3, count);
}
+TEST(RecordingCanvas, saveLayer_missingRestore) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ // Note: restore omitted, shouldn't result in unmatched save
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 2) {
+ EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
+ }
+ });
+ EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
+}
+
+TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.drawRect(10, 20, 190, 180, SkPaint());
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ switch(count++) {
+ case 0:
+ EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ break;
+ case 1:
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ break;
+ case 2:
+ EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId);
+ // Don't bother asserting recording state data - it's not used
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayer_addClipFlag) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op);
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.drawRect(10, 20, 190, 180, SkPaint());
+ canvas.restore();
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 0) {
+ EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId)
+ << "Clip + unclipped saveLayer should result in a clipped layer";
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
TEST(RecordingCanvas, saveLayer_viewportCrop) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
// shouldn't matter, since saveLayer will clip to its bounds
canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
- canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
@@ -295,7 +361,7 @@
canvas.rotate(45);
canvas.translate(-50, -50);
- canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(0, 0, 100, 100, SkPaint());
canvas.restore();
@@ -322,7 +388,7 @@
canvas.translate(-200, -200);
// area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
- canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index bcb459a..1896bbb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1010,6 +1010,11 @@
break;
default:
+ if (setting.startsWith(Settings.Global.DATA_ROAMING)) {
+ if ("0".equals(value)) return false;
+ restriction = UserManager.DISALLOW_DATA_ROAMING;
+ break;
+ }
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index 5c0f38c..e2d64b04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -149,4 +149,11 @@
public View getCurrentView() {
return this;
}
+
+ @Override
+ public void setNavigationIconHints(int hints, boolean force) {
+ // We do not need to set the navigation icon hints for a vehicle
+ // Calling setNavigationIconHints in the base class will result in a NPE as the car
+ // navigation bar does not have a back button.
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index a72b5d0..31631f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -57,4 +57,10 @@
carNavBar.setActivityStarter(this);
mNavigationBarView = carNavBar;
}
+
+ @Override
+ protected void repositionNavigationBar() {
+ // The navigation bar for a vehicle will not need to be repositioned, as it is always
+ // set at the bottom.
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 029dc1f..d721a77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1202,7 +1202,7 @@
mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
}
- private void repositionNavigationBar() {
+ protected void repositionNavigationBar() {
if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
prepareNavigationBarView();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f6f9f9d..fcb9962 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8638,6 +8638,35 @@
}
@Override
+ public ParceledListSlice<android.content.UriPermission> getGrantedUriPermissions(
+ String packageName, int userId) {
+ enforceCallingPermission(android.Manifest.permission.GET_APP_GRANTED_URI_PERMISSIONS,
+ "getGrantedUriPermissions");
+
+ final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
+ synchronized (this) {
+ final int size = mGrantedUriPermissions.size();
+ for (int i = 0; i < size; i++) {
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+ for (UriPermission perm : perms.values()) {
+ if (packageName.equals(perm.targetPkg) && perm.targetUserId == userId
+ && perm.persistedModeFlags != 0) {
+ result.add(perm.buildPersistedPublicApiObject());
+ }
+ }
+ }
+ }
+ return new ParceledListSlice<android.content.UriPermission>(result);
+ }
+
+ @Override
+ public void clearGrantedUriPermissions(String packageName, int userId) {
+ enforceCallingPermission(android.Manifest.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS,
+ "clearGrantedUriPermissions");
+ removeUriPermissionsForPackageLocked(packageName, userId, true);
+ }
+
+ @Override
public void showWaitingForDebugger(IApplicationThread who, boolean waiting) {
synchronized (this) {
ProcessRecord app =
@@ -11377,8 +11406,23 @@
}
}
- public void requestBugReport(boolean progress) {
- final String service = progress ? "bugreportplus" : "bugreport";
+ public void requestBugReport(int bugreportType) {
+ String service = null;
+ switch (bugreportType) {
+ case ActivityManager.BUGREPORT_OPTION_FULL:
+ service = "bugreport";
+ break;
+ case ActivityManager.BUGREPORT_OPTION_INTERACTIVE:
+ service = "bugreportplus";
+ break;
+ case ActivityManager.BUGREPORT_OPTION_REMOTE:
+ service = "bugreportremote";
+ break;
+ }
+ if (service == null) {
+ throw new IllegalArgumentException("Provided bugreport type is not correct, value: "
+ + bugreportType);
+ }
enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport");
SystemProperties.set("ctl.start", service);
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 9bbc3c1..f0ed790 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -33,6 +33,8 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
@@ -40,10 +42,11 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.List;
import java.util.Set;
/**
- * Utility methods for uesr restrictions.
+ * Utility methods for user restrictions.
*
* <p>See {@link UserManagerService} for the method suffixes.
*/
@@ -88,7 +91,8 @@
UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
UserManager.DISALLOW_RECORD_AUDIO,
UserManager.DISALLOW_CAMERA,
- UserManager.DISALLOW_RUN_IN_BACKGROUND
+ UserManager.DISALLOW_RUN_IN_BACKGROUND,
+ UserManager.DISALLOW_DATA_ROAMING
);
/**
@@ -113,7 +117,8 @@
UserManager.DISALLOW_SMS,
UserManager.DISALLOW_FUN,
UserManager.DISALLOW_SAFE_BOOT,
- UserManager.DISALLOW_CREATE_WINDOWS
+ UserManager.DISALLOW_CREATE_WINDOWS,
+ UserManager.DISALLOW_DATA_ROAMING
);
/**
@@ -315,6 +320,27 @@
.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, userId);
}
break;
+ case UserManager.DISALLOW_DATA_ROAMING:
+ if (newValue) {
+ // DISALLOW_DATA_ROAMING user restriction is set.
+
+ // Multi sim device.
+ SubscriptionManager subscriptionManager = new SubscriptionManager(context);
+ final List<SubscriptionInfo> subscriptionInfoList =
+ subscriptionManager.getActiveSubscriptionInfoList();
+ if (subscriptionInfoList != null) {
+ for (SubscriptionInfo subInfo : subscriptionInfoList) {
+ android.provider.Settings.Global.putStringForUser(cr,
+ android.provider.Settings.Global.DATA_ROAMING
+ + subInfo.getSubscriptionId(), "0", userId);
+ }
+ }
+
+ // Single sim device.
+ android.provider.Settings.Global.putStringForUser(cr,
+ android.provider.Settings.Global.DATA_ROAMING, "0", userId);
+ }
+ break;
case UserManager.DISALLOW_SHARE_LOCATION:
if (newValue) {
android.provider.Settings.Secure.putIntForUser(cr,
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 3eae7fc..a0f20aa 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -388,7 +388,8 @@
public void run() {
try {
// Take an "interactive" bugreport.
- ActivityManagerNative.getDefault().requestBugReport(true);
+ ActivityManagerNative.getDefault().requestBugReport(
+ ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
} catch (RemoteException e) {
}
}
@@ -404,7 +405,8 @@
}
try {
// Take a "full" bugreport.
- ActivityManagerNative.getDefault().requestBugReport(false);
+ ActivityManagerNative.getDefault().requestBugReport(
+ ActivityManager.BUGREPORT_OPTION_FULL);
} catch (RemoteException e) {
}
return false;
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index d250739..00e1acb 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -531,12 +531,14 @@
@Override
public void onPackageModified(String packageName) {
+ if (!mUserManager.isUserUnlocked(getChangingUserId())) return;
updateServices(packageName);
getOrCreateUserStateLocked(getChangingUserId()).prunePrintServices();
}
@Override
public void onPackageRemoved(String packageName, int uid) {
+ if (!mUserManager.isUserUnlocked(getChangingUserId())) return;
updateServices(packageName);
getOrCreateUserStateLocked(getChangingUserId()).prunePrintServices();
}
diff --git a/tools/layoutlib/.idea/compiler.xml b/tools/layoutlib/.idea/compiler.xml
index 5aaaf18..35961a2 100644
--- a/tools/layoutlib/.idea/compiler.xml
+++ b/tools/layoutlib/.idea/compiler.xml
@@ -21,7 +21,5 @@
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
- <bytecodeTargetLevel target="1.6" />
</component>
-</project>
-
+</project>
\ No newline at end of file
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
index e6f0bc3..c7f2c41 100644
--- a/tools/layoutlib/create/Android.mk
+++ b/tools/layoutlib/create/Android.mk
@@ -20,7 +20,7 @@
LOCAL_JAR_MANIFEST := manifest.txt
LOCAL_STATIC_JAVA_LIBRARIES := \
- asm-4.0
+ asm-5.0
LOCAL_MODULE := layoutlib_create
diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml
index 9b18e73..b2b14b4 100644
--- a/tools/layoutlib/create/create.iml
+++ b/tools/layoutlib/create/create.iml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
@@ -9,12 +9,12 @@
<sourceFolder url="file://$MODULE_DIR$/tests/mock_data" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/.settings" />
</content>
- <orderEntry type="inheritedJdk" />
+ <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
- <library name="asm-4.0">
+ <library name="asm-5.0">
<CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-4.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-5.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
index a6902a4..758bd48 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
@@ -21,7 +21,6 @@
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
@@ -44,7 +43,7 @@
abstract String renameInternalType(String name);
public AbstractClassAdapter(ClassVisitor cv) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
}
/**
@@ -239,7 +238,7 @@
* The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
*/
public RenameMethodAdapter(MethodVisitor mv) {
- super(Opcodes.ASM4, mv);
+ super(Main.ASM_VERSION, mv);
}
@Override
@@ -276,7 +275,8 @@
}
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
// The owner sometimes turns out to be a type descriptor. We try to detect it and fix.
if (owner.indexOf(';') > 0) {
owner = renameTypeDesc(owner);
@@ -285,7 +285,7 @@
}
desc = renameMethodDesc(desc);
- super.visitMethodInsn(opcode, owner, name, desc);
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
@@ -330,7 +330,7 @@
private final SignatureVisitor mSv;
public RenameSignatureAdapter(SignatureVisitor sv) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mSv = sv;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index c8b2b84..48544ca 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -23,7 +23,6 @@
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
@@ -65,7 +64,7 @@
/** Glob patterns of files to keep as is. */
private final String[] mIncludeFileGlobs;
/** Internal names of classes that contain method calls that need to be rewritten. */
- private final Set<String> mReplaceMethodCallClasses = new HashSet<String>();
+ private final Set<String> mReplaceMethodCallClasses = new HashSet<>();
/**
* Creates a new analyzer.
@@ -97,8 +96,8 @@
*/
public void analyze() throws IOException, LogAbortException {
- TreeMap<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ TreeMap<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsSourceJar, zipClasses, filesFound);
mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
@@ -189,7 +188,7 @@
*/
Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
throws LogAbortException {
- TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> found = new TreeMap<>();
mLog.debug("Find classes to include.");
@@ -318,10 +317,10 @@
Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutKeepClasses) {
- TreeMap<String, ClassReader> deps = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> new_deps = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> temp = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> deps = new TreeMap<>();
+ TreeMap<String, ClassReader> new_deps = new TreeMap<>();
+ TreeMap<String, ClassReader> new_keep = new TreeMap<>();
+ TreeMap<String, ClassReader> temp = new TreeMap<>();
DependencyVisitor visitor = getVisitor(zipClasses,
inOutKeepClasses, new_keep,
@@ -399,7 +398,7 @@
Map<String, ClassReader> outKeep,
Map<String,ClassReader> inDeps,
Map<String,ClassReader> outDeps) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mZipClasses = zipClasses;
mInKeep = inKeep;
mOutKeep = outKeep;
@@ -557,7 +556,7 @@
private class MyFieldVisitor extends FieldVisitor {
public MyFieldVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
@Override
@@ -630,7 +629,7 @@
private String mOwnerClass;
public MyMethodVisitor(String ownerClass) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mOwnerClass = ownerClass;
}
@@ -719,7 +718,8 @@
// instruction that invokes a method
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
// owner is the internal name of the method's owner class
considerName(owner);
@@ -779,7 +779,7 @@
private class MySignatureVisitor extends SignatureVisitor {
public MySignatureVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
// ---------------------------------------------------
@@ -878,7 +878,7 @@
private class MyAnnotationVisitor extends AnnotationVisitor {
public MyAnnotationVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
// Visits a primitive value of an annotation
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 8f0ad01..5b99a6b 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -91,7 +91,7 @@
mLog = log;
mOsDestJar = osDestJar;
ArrayList<Class<?>> injectedClasses =
- new ArrayList<Class<?>>(Arrays.asList(createInfo.getInjectedClasses()));
+ new ArrayList<>(Arrays.asList(createInfo.getInjectedClasses()));
// Search for and add anonymous inner classes also.
ListIterator<Class<?>> iter = injectedClasses.listIterator();
while (iter.hasNext()) {
@@ -107,25 +107,25 @@
}
}
mInjectClasses = injectedClasses.toArray(new Class<?>[0]);
- mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+ mStubMethods = new HashSet<>(Arrays.asList(createInfo.getOverriddenMethods()));
// Create the map/set of methods to change to delegates
- mDelegateMethods = new HashMap<String, Set<String>>();
+ mDelegateMethods = new HashMap<>();
addToMap(createInfo.getDelegateMethods(), mDelegateMethods);
for (String className : createInfo.getDelegateClassNatives()) {
className = binaryToInternalClassName(className);
Set<String> methods = mDelegateMethods.get(className);
if (methods == null) {
- methods = new HashSet<String>();
+ methods = new HashSet<>();
mDelegateMethods.put(className, methods);
}
methods.add(DelegateClassAdapter.ALL_NATIVES);
}
// Create the map of classes to rename.
- mRenameClasses = new HashMap<String, String>();
- mClassesNotRenamed = new HashSet<String>();
+ mRenameClasses = new HashMap<>();
+ mClassesNotRenamed = new HashSet<>();
String[] renameClasses = createInfo.getRenamedClasses();
int n = renameClasses.length;
for (int i = 0; i < n; i += 2) {
@@ -138,7 +138,7 @@
}
// Create a map of classes to be refactored.
- mRefactorClasses = new HashMap<String, String>();
+ mRefactorClasses = new HashMap<>();
String[] refactorClasses = createInfo.getJavaPkgClasses();
n = refactorClasses.length;
for (int i = 0; i < n; i += 2) {
@@ -149,7 +149,7 @@
}
// create the map of renamed class -> return type of method to delete.
- mDeleteReturns = new HashMap<String, Set<String>>();
+ mDeleteReturns = new HashMap<>();
String[] deleteReturns = createInfo.getDeleteReturns();
Set<String> returnTypes = null;
String renamedClass = null;
@@ -172,12 +172,12 @@
// just a standard return type, we add it to the list.
if (returnTypes == null) {
- returnTypes = new HashSet<String>();
+ returnTypes = new HashSet<>();
}
returnTypes.add(binaryToInternalClassName(className));
}
- mPromotedFields = new HashMap<String, Set<String>>();
+ mPromotedFields = new HashMap<>();
addToMap(createInfo.getPromotedFields(), mPromotedFields);
mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
@@ -197,7 +197,7 @@
String methodOrFieldName = entry.substring(pos + 1);
Set<String> set = map.get(className);
if (set == null) {
- set = new HashSet<String>();
+ set = new HashSet<>();
map.put(className, set);
}
set.add(methodOrFieldName);
@@ -247,7 +247,7 @@
/** Generates the final JAR */
public void generate() throws IOException {
- TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
+ TreeMap<String, byte[]> all = new TreeMap<>();
for (Class<?> clazz : mInjectClasses) {
String name = classToEntryPath(clazz);
@@ -314,7 +314,7 @@
* e.g. for the input "android.view.View" it returns "android/view/View.class"
*/
String classNameToEntryPath(String className) {
- return className.replaceAll("\\.", "/").concat(".class");
+ return className.replace('.', '/').concat(".class");
}
/**
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
index 2c955fd..4748a7c 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
@@ -31,7 +31,7 @@
*/
public class ClassHasNativeVisitor extends ClassVisitor {
public ClassHasNativeVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
private boolean mHasNativeMethods = false;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index b571c5a..3ca7590 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -111,7 +111,7 @@
public Set<String> getExcludedClasses() {
String[] refactoredClasses = getJavaPkgClasses();
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
- Set<String> excludedClasses = new HashSet<String>(count);
+ Set<String> excludedClasses = new HashSet<>(count);
for (int i = 0; i < refactoredClasses.length; i+=2) {
excludedClasses.add(refactoredClasses[i]);
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index 7ef7566..cbb3a8a 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -60,7 +60,7 @@
ClassVisitor cv,
String className,
Set<String> delegateMethods) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mLog = log;
mClassName = className;
mDelegateMethods = delegateMethods;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index cca9e57..da8babc 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -124,7 +124,7 @@
String desc,
boolean isStatic,
boolean isStaticClass) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mLog = log;
mOrgWriter = mvOriginal;
mDelWriter = mvDelegate;
@@ -188,7 +188,7 @@
mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
}
- ArrayList<Type> paramTypes = new ArrayList<Type>();
+ ArrayList<Type> paramTypes = new ArrayList<>();
String delegateClassName = mClassName + DELEGATE_SUFFIX;
boolean pushedArg0 = false;
int maxStack = 0;
@@ -253,7 +253,8 @@
mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
delegateClassName,
mMethodName,
- desc);
+ desc,
+ false);
Type returnType = Type.getReturnType(mDesc);
mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
@@ -371,9 +372,9 @@
}
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (mOrgWriter != null) {
- mOrgWriter.visitMethodInsn(opcode, owner, name, desc);
+ mOrgWriter.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
index 61b64a2..aa68ea0 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
@@ -26,7 +26,6 @@
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
@@ -82,7 +81,7 @@
Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet());
- List<Map<String, Set<String>>> result = new ArrayList<Map<String,Set<String>>>(2);
+ List<Map<String, Set<String>>> result = new ArrayList<>(2);
result.add(deps);
result.add(missing);
return result;
@@ -151,7 +150,7 @@
* class name => ASM ClassReader. Class names are in the form "android.view.View".
*/
Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
- TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> classes = new TreeMap<>();
for (String jarPath : jarPathList) {
ZipFile zip = new ZipFile(jarPath);
@@ -202,7 +201,7 @@
// The dependencies that we'll collect.
// It's a map Class name => uses class names.
- Map<String, Set<String>> dependencyMap = new TreeMap<String, Set<String>>();
+ Map<String, Set<String>> dependencyMap = new TreeMap<>();
DependencyVisitor visitor = getVisitor();
@@ -211,7 +210,7 @@
for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
String name = entry.getKey();
- TreeSet<String> set = new TreeSet<String>();
+ TreeSet<String> set = new TreeSet<>();
dependencyMap.put(name, set);
visitor.setDependencySet(set);
@@ -240,7 +239,7 @@
private Map<String, Set<String>> findMissingClasses(
Map<String, Set<String>> deps,
Set<String> zipClasses) {
- Map<String, Set<String>> missing = new TreeMap<String, Set<String>>();
+ Map<String, Set<String>> missing = new TreeMap<>();
for (Entry<String, Set<String>> entry : deps.entrySet()) {
String name = entry.getKey();
@@ -250,7 +249,7 @@
// This dependency doesn't exist in the zip classes.
Set<String> set = missing.get(dep);
if (set == null) {
- set = new TreeSet<String>();
+ set = new TreeSet<>();
missing.put(dep, set);
}
set.add(name);
@@ -284,7 +283,7 @@
* Creates a new visitor that will find all the dependencies for the visited class.
*/
public DependencyVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
/**
@@ -435,7 +434,7 @@
private class MyFieldVisitor extends FieldVisitor {
public MyFieldVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
@Override
@@ -510,7 +509,7 @@
private class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
@@ -598,7 +597,8 @@
// instruction that invokes a method
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
// owner is the internal name of the method's owner class
if (!considerDesc(owner) && owner.indexOf('/') != -1) {
@@ -654,7 +654,7 @@
private class MySignatureVisitor extends SignatureVisitor {
public MySignatureVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
// ---------------------------------------------------
@@ -753,7 +753,7 @@
private class MyAnnotationVisitor extends AnnotationVisitor {
public MyAnnotationVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
// Visits a primitive value of an annotation
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
index 37fc096..1941ab4 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
@@ -42,9 +42,9 @@
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass",
- "()Ljava/lang/Class;");
+ "()Ljava/lang/Class;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
- "()Ljava/lang/ClassLoader;");
+ "()Ljava/lang/ClassLoader;", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
index ea2b9c9..c834808 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
@@ -19,7 +19,6 @@
import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Opcodes;
/**
* Injects methods into some classes.
@@ -29,7 +28,7 @@
private final ICreateInfo.InjectMethodRunnable mRunnable;
public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mRunnable = runnable;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 383168f..9bb91e5 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -16,6 +16,8 @@
package com.android.tools.layoutlib.create;
+import org.objectweb.asm.Opcodes;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -52,13 +54,15 @@
public boolean listOnlyMissingDeps = false;
}
+ public static final int ASM_VERSION = Opcodes.ASM5;
+
public static final Options sOptions = new Options();
public static void main(String[] args) {
Log log = new Log();
- ArrayList<String> osJarPath = new ArrayList<String>();
+ ArrayList<String> osJarPath = new ArrayList<>();
String[] osDestJar = { null };
if (!processArgs(log, args, osJarPath, osDestJar)) {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
index 6fc2b24..faba4d7 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
@@ -36,41 +36,40 @@
* @param isNative True if the method was a native method.
* @param caller The calling object. Null for static methods, "this" for instance methods.
*/
- public void onInvokeV(String signature, boolean isNative, Object caller);
+ void onInvokeV(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
* @see #onInvokeV(String, boolean, Object)
* @return an integer, or a boolean, or a short or a byte.
*/
- public int onInvokeI(String signature, boolean isNative, Object caller);
+ int onInvokeI(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
* @see #onInvokeV(String, boolean, Object)
* @return a long.
*/
- public long onInvokeL(String signature, boolean isNative, Object caller);
+ long onInvokeL(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
* @see #onInvokeV(String, boolean, Object)
* @return a float.
*/
- public float onInvokeF(String signature, boolean isNative, Object caller);
+ float onInvokeF(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
* @see #onInvokeV(String, boolean, Object)
* @return a double.
*/
- public double onInvokeD(String signature, boolean isNative, Object caller);
+ double onInvokeD(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
* @see #onInvokeV(String, boolean, Object)
* @return an object.
*/
- public Object onInvokeA(String signature, boolean isNative, Object caller);
+ Object onInvokeA(String signature, boolean isNative, Object caller);
}
-
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
index 4c87b3c..7ccafc3 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
@@ -28,7 +28,7 @@
public final class OverrideMethod {
/** Map of method overridden. */
- private static HashMap<String, MethodListener> sMethods = new HashMap<String, MethodListener>();
+ private static HashMap<String, MethodListener> sMethods = new HashMap<>();
/** Default listener for all method not listed in sMethods. Nothing if null. */
private static MethodListener sDefaultListener = null;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
index e4b70da..05af033 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
@@ -24,7 +24,6 @@
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
-import static org.objectweb.asm.Opcodes.ASM4;
/**
* Promotes given fields to public visibility.
@@ -35,7 +34,7 @@
private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED);
public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) {
- super(ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mFieldNames = fieldNames;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 5e47261..bf94415 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -43,11 +43,11 @@
* Descriptors for specialized versions {@link System#arraycopy} that are not present on the
* Desktop VM.
*/
- private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<String>(Arrays.asList(
+ private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<>(Arrays.asList(
"([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
"([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
- private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(5);
+ private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<>(5);
private static final String ANDROID_LOCALE_CLASS =
"com/android/layoutlib/bridge/android/AndroidLocale";
@@ -232,7 +232,7 @@
private final String mOriginalClassName;
public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mOriginalClassName = originalClassName;
}
@@ -245,11 +245,12 @@
private class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor(MethodVisitor mv) {
- super(Opcodes.ASM4, mv);
+ super(Main.ASM_VERSION, mv);
}
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc,
+ boolean itf) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) {
MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
@@ -261,7 +262,7 @@
break;
}
}
- super.visitMethodInsn(opcode, owner, name, desc);
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
index 416b73a..b5ab738 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
@@ -50,7 +50,7 @@
public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
String invokeSignature, boolean isStatic, boolean isNative) {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
mParentVisitor = mv;
mReturnType = returnType;
mInvokeSignature = invokeSignature;
@@ -82,7 +82,8 @@
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeV",
- "(Ljava/lang/String;ZLjava/lang/Object;)V");
+ "(Ljava/lang/String;ZLjava/lang/Object;)V",
+ false);
mParentVisitor.visitInsn(Opcodes.RETURN);
break;
case Type.BOOLEAN:
@@ -93,7 +94,8 @@
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeI",
- "(Ljava/lang/String;ZLjava/lang/Object;)I");
+ "(Ljava/lang/String;ZLjava/lang/Object;)I",
+ false);
switch(sort) {
case Type.BOOLEAN:
Label l1 = new Label();
@@ -119,21 +121,24 @@
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeL",
- "(Ljava/lang/String;ZLjava/lang/Object;)J");
+ "(Ljava/lang/String;ZLjava/lang/Object;)J",
+ false);
mParentVisitor.visitInsn(Opcodes.LRETURN);
break;
case Type.FLOAT:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeF",
- "(Ljava/lang/String;ZLjava/lang/Object;)F");
+ "(Ljava/lang/String;ZLjava/lang/Object;)F",
+ false);
mParentVisitor.visitInsn(Opcodes.FRETURN);
break;
case Type.DOUBLE:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeD",
- "(Ljava/lang/String;ZLjava/lang/Object;)D");
+ "(Ljava/lang/String;ZLjava/lang/Object;)D",
+ false);
mParentVisitor.visitInsn(Opcodes.DRETURN);
break;
case Type.ARRAY:
@@ -141,7 +146,8 @@
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeA",
- "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;");
+ "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;",
+ false);
mParentVisitor.visitTypeInsn(Opcodes.CHECKCAST, mReturnType.getInternalName());
mParentVisitor.visitInsn(Opcodes.ARETURN);
break;
@@ -282,9 +288,9 @@
}
@Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (mIsInitMethod) {
- mParentVisitor.visitMethodInsn(opcode, owner, name, desc);
+ mParentVisitor.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index d9ecf98..a28ae69 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -49,7 +49,7 @@
public TransformClassAdapter(Log logger, Set<String> stubMethods,
Set<String> deleteReturns, String className, ClassVisitor cv,
boolean stubNativesOnly) {
- super(Opcodes.ASM4, cv);
+ super(Main.ASM_VERSION, cv);
mLog = logger;
mStubMethods = stubMethods;
mClassName = className;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
index ed2c128..7d6c4ec 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
@@ -28,4 +28,5 @@
/**
* Closes the object and release any system resources it holds.
*/
- void close() throws Exception; }
+ void close() throws Exception;
+}
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
index c197d57..dafb9c6 100644
--- a/tools/layoutlib/create/tests/Android.mk
+++ b/tools/layoutlib/create/tests/Android.mk
@@ -24,7 +24,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LIBRARIES := layoutlib_create junit
-LOCAL_STATIC_JAVA_LIBRARIES := asm-4.0
+LOCAL_STATIC_JAVA_LIBRARIES := asm-5.0
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 78e2c48..f86917a 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -17,13 +17,8 @@
package com.android.tools.layoutlib.create;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
@@ -32,11 +27,15 @@
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
/**
* Unit tests for some methods of {@link AsmAnalyzer}.
*/
@@ -51,26 +50,22 @@
mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
- mOsJarPath = new ArrayList<String>();
+ mOsJarPath = new ArrayList<>();
+ //noinspection ConstantConditions
mOsJarPath.add(url.getFile());
- Set<String> excludeClasses = new HashSet<String>(1);
- excludeClasses.add("java.lang.JavaClass");
+ Set<String> excludeClasses = Collections.singleton("java.lang.JavaClass");
String[] includeFiles = new String[]{"mock_android/data/data*"};
mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */,
null /* includeGlobs */, excludeClasses, includeFiles);
}
- @After
- public void tearDown() throws Exception {
- }
-
@Test
public void testParseZip() throws IOException {
- Map<String, ClassReader> map = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> map = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, map, filesFound);
@@ -101,11 +96,11 @@
@Test
public void testFindClass() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> found = new TreeMap<>();
ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
zipClasses, found);
@@ -120,11 +115,11 @@
@Test
public void testFindGlobs() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> found = new TreeMap<>();
// this matches classes, a package match returns nothing
found.clear();
@@ -183,11 +178,11 @@
@Test
public void testFindClassesDerivingFrom() throws LogAbortException, IOException {
- Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> found = new TreeMap<>();
mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
@@ -209,14 +204,14 @@
@Test
public void testDependencyVisitor() throws IOException, LogAbortException {
- Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>();
- TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> keep = new TreeMap<>();
+ TreeMap<String, ClassReader> new_keep = new TreeMap<>();
+ TreeMap<String, ClassReader> in_deps = new TreeMap<>();
+ TreeMap<String, ClassReader> out_deps = new TreeMap<>();
ClassReader cr = mAa.findClass("mock_android.widget.LinearLayout", zipClasses, keep);
DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 8a2235b..c4dd7ee 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -18,12 +18,6 @@
package com.android.tools.layoutlib.create;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -31,7 +25,6 @@
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.io.ByteArrayOutputStream;
@@ -44,7 +37,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -52,11 +44,18 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
/**
* Unit tests for some methods of {@link AsmGenerator}.
*/
public class AsmGeneratorTest {
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
private MockLog mLog;
private ArrayList<String> mOsJarPath;
private String mOsDestJar;
@@ -70,7 +69,8 @@
mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
- mOsJarPath = new ArrayList<String>();
+ mOsJarPath = new ArrayList<>();
+ //noinspection ConstantConditions
mOsJarPath.add(url.getFile());
mTempFile = File.createTempFile("mock", ".jar");
@@ -98,18 +98,18 @@
@Override
public String[] getDelegateMethods() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getDelegateClassNatives() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
@@ -123,7 +123,7 @@
@Override
public String[] getJavaPkgClasses() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
@@ -134,17 +134,17 @@
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedFields() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return new HashMap<String, InjectMethodRunnable>(0);
+ return Collections.emptyMap();
}
};
@@ -155,7 +155,7 @@
new String[] { // include classes
"**"
},
- new HashSet<String>(0) /* excluded classes */,
+ Collections.<String>emptySet() /* excluded classes */,
new String[]{} /* include files */);
aa.analyze();
agen.generate();
@@ -178,24 +178,24 @@
@Override
public String[] getDelegateMethods() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getDelegateClassNatives() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
@@ -214,17 +214,17 @@
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedFields() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return new HashMap<String, InjectMethodRunnable>(0);
+ return Collections.emptyMap();
}
};
@@ -235,14 +235,14 @@
new String[] { // include classes
"**"
},
- new HashSet<String>(1),
+ Collections.<String>emptySet(),
new String[] { /* include files */
"mock_android/data/data*"
});
aa.analyze();
agen.generate();
- Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> output = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsDestJar, output, filesFound);
boolean injectedClassFound = false;
for (ClassReader cr: output.values()) {
@@ -265,35 +265,35 @@
@Override
public String[] getDelegateMethods() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getDelegateClassNatives() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getJavaPkgClasses() {
// classes to refactor (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Set<String> getExcludedClasses() {
- Set<String> set = new HashSet<String>(2);
+ Set<String> set = new HashSet<>(2);
set.add("mock_android.dummy.InnerTest");
set.add("java.lang.JavaClass");
return set;
@@ -302,17 +302,17 @@
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedFields() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return new HashMap<String, InjectMethodRunnable>(0);
+ return Collections.emptyMap();
}
};
@@ -329,8 +329,8 @@
});
aa.analyze();
agen.generate();
- Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> output = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsDestJar, output, filesFound);
for (String s : output.keySet()) {
assertFalse(excludedClasses.contains(s));
@@ -351,55 +351,52 @@
@Override
public String[] getDelegateMethods() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getDelegateClassNatives() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getJavaPkgClasses() {
// classes to refactor (so that we can replace them)
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Set<String> getExcludedClasses() {
- return new HashSet<String>(0);
+ return Collections.emptySet();
}
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public String[] getPromotedFields() {
- return new String[0];
+ return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- HashMap<String, InjectMethodRunnable> map =
- new HashMap<String, InjectMethodRunnable>(1);
- map.put("mock_android.util.EmptyArray",
+ return Collections.singletonMap("mock_android.util.EmptyArray",
InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
- return map;
}
};
@@ -415,8 +412,8 @@
});
aa.analyze();
agen.generate();
- Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
- Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ Map<String, ClassReader> output = new TreeMap<>();
+ Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsDestJar, output, filesFound);
final String modifiedClass = "mock_android.util.EmptyArray";
final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
@@ -424,11 +421,8 @@
ZipEntry entry = zipFile.getEntry(modifiedClassPath);
assertNotNull(entry);
final byte[] bytes;
- final InputStream inputStream = zipFile.getInputStream(entry);
- try {
+ try (InputStream inputStream = zipFile.getInputStream(entry)) {
bytes = getByteArray(inputStream);
- } finally {
- inputStream.close();
}
ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
@Override
@@ -489,7 +483,7 @@
boolean mInjectedClassFound = false;
TestClassVisitor() {
- super(Opcodes.ASM4);
+ super(Main.ASM_VERSION);
}
@Override
@@ -514,7 +508,7 @@
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
- return new MethodVisitor(Opcodes.ASM4, mv) {
+ return new MethodVisitor(Main.ASM_VERSION, mv) {
@Override
public void visitFieldInsn(int opcode, String owner, String name,
@@ -540,10 +534,10 @@
@Override
public void visitMethodInsn(int opcode, String owner, String name,
- String desc) {
+ String desc, boolean itf) {
assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
assertTrue(testType(Type.getType(desc)));
- super.visitMethodInsn(opcode, owner, name, desc);
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
}
};
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
index 0135c40..0cdcdc0 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -60,7 +60,7 @@
* Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
*/
private static class MockClassHasNativeVisitor extends ClassHasNativeVisitor {
- private ArrayList<String> mMethodsFound = new ArrayList<String>();
+ private ArrayList<String> mMethodsFound = new ArrayList<>();
public String[] getMethodsFound() {
return mMethodsFound.toArray(new String[mMethodsFound.size()]);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index e37a09b..0912fb1 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -88,7 +88,7 @@
// Now process it but tell the delegate to not modify any method
ClassWriter cw = new ClassWriter(0 /*flags*/);
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
@@ -152,7 +152,7 @@
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
delegateMethods.add("<init>");
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
@@ -166,7 +166,7 @@
ClassWriter cw = new ClassWriter(0 /*flags*/);
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
@@ -217,7 +217,7 @@
@Test
public void testDelegateInner() throws Throwable {
// We'll delegate the "get" method of both the inner and outer class.
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
delegateMethods.add("get");
delegateMethods.add("privateMethod");
@@ -300,7 +300,7 @@
@Test
public void testDelegateStaticInner() throws Throwable {
// We'll delegate the "get" method of both the inner and outer class.
- HashSet<String> delegateMethods = new HashSet<String>();
+ HashSet<String> delegateMethods = new HashSet<>();
delegateMethods.add("get");
// Generate the delegate for the outer class.
@@ -367,7 +367,7 @@
*/
private abstract class ClassLoader2 extends ClassLoader {
- private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>();
+ private final Map<String, byte[]> mClassDefs = new HashMap<>();
public ClassLoader2() {
super(null);
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk
new file mode 100644
index 0000000..35d28fb
--- /dev/null
+++ b/tools/preload2/Android.mk
@@ -0,0 +1,32 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+# To connect to devices (and take hprof dumps).
+LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt
+
+# To process hprof dumps.
+LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib
+
+# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
+# convenience (and to not depend on internal JDK APIs).
+LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit
+
+LOCAL_MODULE:= preload2
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Copy the preload-tool shell script to the host's bin directory.
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := preload-tool
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/preload-tool $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
diff --git a/tools/preload2/preload-tool b/tools/preload2/preload-tool
new file mode 100644
index 0000000..36dbc1c
--- /dev/null
+++ b/tools/preload2/preload-tool
@@ -0,0 +1,37 @@
+# Copyright (C) 2015 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.
+
+# This script is used on the host only. It uses a common subset
+# shell dialect that should work well. It is partially derived
+# from art/tools/art.
+
+function follow_links() {
+ if [ z"$BASH_SOURCE" != z ]; then
+ file="$BASH_SOURCE"
+ else
+ file="$0"
+ fi
+ while [ -h "$file" ]; do
+ # On Mac OS, readlink -f doesn't work.
+ file="$(readlink "$file")"
+ done
+ echo "$file"
+}
+
+
+PROG_NAME="$(follow_links)"
+PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
+ANDROID_ROOT=$PROG_DIR/..
+
+java -cp $ANDROID_ROOT/framework/preload2.jar com.android.preload.Main
diff --git a/tools/preload2/src/com/android/preload/ClientUtils.java b/tools/preload2/src/com/android/preload/ClientUtils.java
new file mode 100644
index 0000000..71ef025
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ClientUtils.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2015 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.preload;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+
+/**
+ * Helper class for common communication with a Client (the ddms name for a running application).
+ *
+ * Instances take a default timeout parameter that's applied to all functions without explicit
+ * timeout. Timeouts are in milliseconds.
+ */
+public class ClientUtils {
+
+ private int defaultTimeout;
+
+ public ClientUtils() {
+ this(10000);
+ }
+
+ public ClientUtils(int defaultTimeout) {
+ this.defaultTimeout = defaultTimeout;
+ }
+
+ /**
+ * Shortcut for findClient with default timeout.
+ */
+ public Client findClient(IDevice device, String processName, int processPid) {
+ return findClient(device, processName, processPid, defaultTimeout);
+ }
+
+ /**
+ * Find the client with the given process name or process id. The name takes precedence over
+ * the process id (if valid). Stop looking after the given timeout.
+ *
+ * @param device The device to communicate with.
+ * @param processName The name of the process. May be null.
+ * @param processPid The pid of the process. Values less than or equal to zero are ignored.
+ * @param timeout The amount of milliseconds to wait, at most.
+ * @return The client, if found. Otherwise null.
+ */
+ public Client findClient(IDevice device, String processName, int processPid, int timeout) {
+ WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout);
+ return wfc.get();
+ }
+
+ /**
+ * Shortcut for findAllClients with default timeout.
+ */
+ public Client[] findAllClients(IDevice device) {
+ return findAllClients(device, defaultTimeout);
+ }
+
+ /**
+ * Retrieve all clients known to the given device. Wait at most the given timeout.
+ *
+ * @param device The device to investigate.
+ * @param timeout The amount of milliseconds to wait, at most.
+ * @return An array of clients running on the given device. May be null depending on the
+ * device implementation.
+ */
+ public Client[] findAllClients(IDevice device, int timeout) {
+ if (device.hasClients()) {
+ return device.getClients();
+ }
+ WaitForClients wfc = new WaitForClients(device, timeout);
+ return wfc.get();
+ }
+
+ private static class WaitForClient implements IClientChangeListener {
+
+ private IDevice device;
+ private String processName;
+ private int processPid;
+ private long timeout;
+ private Client result;
+
+ public WaitForClient(IDevice device, String processName, int processPid, long timeout) {
+ this.device = device;
+ this.processName = processName;
+ this.processPid = processPid;
+ this.timeout = timeout;
+ this.result = null;
+ }
+
+ public Client get() {
+ synchronized (this) {
+ AndroidDebugBridge.addClientChangeListener(this);
+
+ // Maybe it's already there.
+ if (result == null) {
+ result = searchForClient(device);
+ }
+
+ if (result == null) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Note: doesn't guard for spurious wakeup.
+ }
+ }
+ }
+
+ AndroidDebugBridge.removeClientChangeListener(this);
+ return result;
+ }
+
+ private Client searchForClient(IDevice device) {
+ if (processName != null) {
+ Client tmp = device.getClient(processName);
+ if (tmp != null) {
+ return tmp;
+ }
+ }
+ if (processPid > 0) {
+ String name = device.getClientName(processPid);
+ if (name != null && !name.isEmpty()) {
+ Client tmp = device.getClient(name);
+ if (tmp != null) {
+ return tmp;
+ }
+ }
+ }
+ if (processPid > 0) {
+ // Try manual search.
+ for (Client cl : device.getClients()) {
+ if (cl.getClientData().getPid() == processPid
+ && cl.getClientData().getClientDescription() != null) {
+ return cl;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean isTargetClient(Client c) {
+ if (processPid > 0 && c.getClientData().getPid() == processPid) {
+ return true;
+ }
+ if (processName != null
+ && processName.equals(c.getClientData().getClientDescription())) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void clientChanged(Client arg0, int arg1) {
+ synchronized (this) {
+ if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
+ if (isTargetClient(arg0)) {
+ result = arg0;
+ notifyAll();
+ }
+ }
+ }
+ }
+ }
+
+ private static class WaitForClients implements IClientChangeListener {
+
+ private IDevice device;
+ private long timeout;
+
+ public WaitForClients(IDevice device, long timeout) {
+ this.device = device;
+ this.timeout = timeout;
+ }
+
+ public Client[] get() {
+ synchronized (this) {
+ AndroidDebugBridge.addClientChangeListener(this);
+
+ if (device.hasClients()) {
+ return device.getClients();
+ }
+
+ try {
+ wait(timeout); // Note: doesn't guard for spurious wakeup.
+ } catch (InterruptedException exc) {
+ }
+
+ // We will be woken up when the first client data arrives. Sleep a little longer
+ // to give (hopefully all of) the rest of the clients a chance to become available.
+ // Note: a loop with timeout is brittle as well and complicated, just accept this
+ // for now.
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException exc) {
+ }
+ }
+
+ AndroidDebugBridge.removeClientChangeListener(this);
+
+ return device.getClients();
+ }
+
+ @Override
+ public void clientChanged(Client arg0, int arg1) {
+ synchronized (this) {
+ if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
+ notifyAll();
+ }
+ }
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java
new file mode 100644
index 0000000..72de7b5
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DeviceUtils.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2015 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.preload;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
+import com.android.preload.classdataretrieval.hprof.Hprof;
+import com.android.ddmlib.DdmPreferences;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+
+import java.util.Date;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for some device routines.
+ */
+public class DeviceUtils {
+
+ public static void init(int debugPort) {
+ DdmPreferences.setSelectedDebugPort(debugPort);
+
+ Hprof.init();
+
+ AndroidDebugBridge.init(true);
+
+ AndroidDebugBridge.createBridge();
+ }
+
+ /**
+ * Run a command in the shell on the device.
+ */
+ public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) {
+ doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit);
+ }
+
+ /**
+ * Run a command in the shell on the device. Collects and returns the console output.
+ */
+ public static String doShellReturnString(IDevice device, String cmdline, long timeout,
+ TimeUnit unit) {
+ CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver();
+ doShell(device, cmdline, rec, timeout, unit);
+ return rec.toString();
+ }
+
+ /**
+ * Run a command in the shell on the device, directing all output to the given receiver.
+ */
+ public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver,
+ long timeout, TimeUnit unit) {
+ try {
+ device.executeShellCommand(cmdline, receiver, timeout, unit);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Run am start on the device.
+ */
+ public static void doAMStart(IDevice device, String name, String activity) {
+ doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Find the device with the given serial. Give up after the given timeout (in milliseconds).
+ */
+ public static IDevice findDevice(String serial, int timeout) {
+ WaitForDevice wfd = new WaitForDevice(serial, timeout);
+ return wfd.get();
+ }
+
+ /**
+ * Get all devices ddms knows about. Wait at most for the given timeout.
+ */
+ public static IDevice[] findDevices(int timeout) {
+ WaitForDevice wfd = new WaitForDevice(null, timeout);
+ wfd.get();
+ return AndroidDebugBridge.getBridge().getDevices();
+ }
+
+ /**
+ * Return the build type of the given device. This is the value of the "ro.build.type"
+ * system property.
+ */
+ public static String getBuildType(IDevice device) {
+ try {
+ Future<String> buildType = device.getSystemProperty("ro.build.type");
+ return buildType.get(500, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ }
+ return null;
+ }
+
+ /**
+ * Check whether the given device has a pre-optimized boot image. More precisely, checks
+ * whether /system/framework/ * /boot.art exists.
+ */
+ public static boolean hasPrebuiltBootImage(IDevice device) {
+ String ret =
+ doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS);
+
+ return !ret.contains("No such file or directory");
+ }
+
+ /**
+ * Remove files involved in a standard build that interfere with collecting data. This will
+ * remove /etc/preloaded-classes, which determines which classes are allocated already in the
+ * boot image. It also deletes any compiled boot image on the device. Then it restarts the
+ * device.
+ *
+ * This is a potentially long-running operation, as the boot after the deletion may take a while.
+ * The method will abort after the given timeout.
+ */
+ public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) {
+ String oldContent =
+ DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS);
+ if (oldContent.trim().equals("")) {
+ System.out.println("Preloaded-classes already empty.");
+ return true;
+ }
+
+ // Stop the system server etc.
+ doShell(device, "stop", 100, TimeUnit.MILLISECONDS);
+
+ // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount,"
+ // but AndroidDebugBridge doesn't expose it.
+ doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS);
+ doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
+ // We do need an empty file.
+ doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
+
+ // Delete the files in the dalvik cache.
+ doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS);
+
+ // We'll try to use dev.bootcomplete to know when the system server is back up. But stop
+ // doesn't reset it, so do it manually.
+ doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS);
+
+ // Start the system server.
+ doShell(device, "start", 100, TimeUnit.MILLISECONDS);
+
+ // Do a loop checking each second whether bootcomplete. Wait for at most the given
+ // threshold.
+ Date startDate = new Date();
+ for (;;) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeup.
+ }
+ // Check whether bootcomplete.
+ String ret =
+ doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS);
+ if (ret.trim().equals("1")) {
+ break;
+ }
+ System.out.println("Still not booted: " + ret);
+
+ // Check whether we timed out. This is a simplistic check that doesn't take into account
+ // things like switches in time.
+ Date endDate = new Date();
+ long seconds =
+ TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS);
+ if (seconds > preloadedWaitTimeInSeconds) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Enable method-tracing on device. The system should be restarted after this.
+ */
+ public static void enableTracing(IDevice device) {
+ // Disable selinux.
+ doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS);
+
+ // Make the profile directory world-writable.
+ doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS);
+
+ // Enable streaming method tracing with a small 1K buffer.
+ doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-file "
+ + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS);
+ }
+
+ private static class NullShellOutputReceiver implements IShellOutputReceiver {
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public void addOutput(byte[] arg0, int arg1, int arg2) {}
+ }
+
+ private static class CollectStringShellOutputReceiver implements IShellOutputReceiver {
+
+ private StringBuilder builder = new StringBuilder();
+
+ @Override
+ public String toString() {
+ String ret = builder.toString();
+ // Strip trailing newlines. They are especially ugly because adb uses DOS line endings.
+ while (ret.endsWith("\r") || ret.endsWith("\n")) {
+ ret = ret.substring(0, ret.length() - 1);
+ }
+ return ret;
+ }
+
+ @Override
+ public void addOutput(byte[] arg0, int arg1, int arg2) {
+ builder.append(new String(arg0, arg1, arg2));
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ }
+
+ private static class WaitForDevice {
+
+ private String serial;
+ private long timeout;
+ private IDevice device;
+
+ public WaitForDevice(String serial, long timeout) {
+ this.serial = serial;
+ this.timeout = timeout;
+ device = null;
+ }
+
+ public IDevice get() {
+ if (device == null) {
+ WaitForDeviceListener wfdl = new WaitForDeviceListener(serial);
+ synchronized (wfdl) {
+ AndroidDebugBridge.addDeviceChangeListener(wfdl);
+
+ // Check whether we already know about this device.
+ IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
+ if (serial != null) {
+ for (IDevice d : devices) {
+ if (serial.equals(d.getSerialNumber())) {
+ // Only accept if there are clients already. Else wait for the callback informing
+ // us that we now have clients.
+ if (d.hasClients()) {
+ device = d;
+ }
+
+ break;
+ }
+ }
+ } else {
+ if (devices.length > 0) {
+ device = devices[0];
+ }
+ }
+
+ if (device == null) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeups.
+ }
+ device = wfdl.getDevice();
+ }
+
+ AndroidDebugBridge.removeDeviceChangeListener(wfdl);
+ }
+ }
+
+ if (device != null) {
+ // Wait for clients.
+ WaitForClientsListener wfcl = new WaitForClientsListener(device);
+ synchronized (wfcl) {
+ AndroidDebugBridge.addDeviceChangeListener(wfcl);
+
+ if (!device.hasClients()) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeups.
+ }
+ }
+
+ AndroidDebugBridge.removeDeviceChangeListener(wfcl);
+ }
+ }
+
+ return device;
+ }
+
+ private static class WaitForDeviceListener implements IDeviceChangeListener {
+
+ private String serial;
+ private IDevice device;
+
+ public WaitForDeviceListener(String serial) {
+ this.serial = serial;
+ }
+
+ public IDevice getDevice() {
+ return device;
+ }
+
+ @Override
+ public void deviceChanged(IDevice arg0, int arg1) {
+ // We may get a device changed instead of connected. Handle like a connection.
+ deviceConnected(arg0);
+ }
+
+ @Override
+ public void deviceConnected(IDevice arg0) {
+ if (device != null) {
+ // Ignore updates.
+ return;
+ }
+
+ if (serial == null || serial.equals(arg0.getSerialNumber())) {
+ device = arg0;
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void deviceDisconnected(IDevice arg0) {
+ // Ignore disconnects.
+ }
+
+ }
+
+ private static class WaitForClientsListener implements IDeviceChangeListener {
+
+ private IDevice myDevice;
+
+ public WaitForClientsListener(IDevice myDevice) {
+ this.myDevice = myDevice;
+ }
+
+ @Override
+ public void deviceChanged(IDevice arg0, int arg1) {
+ if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) {
+ // Got a client list, done here.
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void deviceConnected(IDevice arg0) {
+ }
+
+ @Override
+ public void deviceDisconnected(IDevice arg0) {
+ }
+
+ }
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/DumpData.java b/tools/preload2/src/com/android/preload/DumpData.java
new file mode 100644
index 0000000..d997224
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpData.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 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.preload;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Holds the collected data for a process.
+ */
+public class DumpData {
+ /**
+ * Name of the package (=application).
+ */
+ String packageName;
+
+ /**
+ * A map of class name to a string for the classloader. This may be a toString equivalent,
+ * or just a unique ID.
+ */
+ Map<String, String> dumpData;
+
+ /**
+ * The Date when this data was captured. Mostly for display purposes.
+ */
+ Date date;
+
+ /**
+ * A cached value for the number of boot classpath classes (classloader value in dumpData is
+ * null).
+ */
+ int bcpClasses;
+
+ public DumpData(String packageName, Map<String, String> dumpData, Date date) {
+ this.packageName = packageName;
+ this.dumpData = dumpData;
+ this.date = date;
+
+ countBootClassPath();
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public Map<String, String> getDumpData() {
+ return dumpData;
+ }
+
+ public void countBootClassPath() {
+ bcpClasses = 0;
+ for (Map.Entry<String, String> e : dumpData.entrySet()) {
+ if (e.getValue() == null) {
+ bcpClasses++;
+ }
+ }
+ }
+
+ // Return an inverted mapping.
+ public Map<String, Set<String>> invertData() {
+ Map<String, Set<String>> ret = new HashMap<>();
+ for (Map.Entry<String, String> e : dumpData.entrySet()) {
+ if (!ret.containsKey(e.getValue())) {
+ ret.put(e.getValue(), new HashSet<String>());
+ }
+ ret.get(e.getValue()).add(e.getKey());
+ }
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/DumpDataIO.java b/tools/preload2/src/com/android/preload/DumpDataIO.java
new file mode 100644
index 0000000..28625c5
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpDataIO.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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.preload;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.io.FileReader;
+import java.text.DateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Helper class for serialization and deserialization of a collection of DumpData objects to XML.
+ */
+public class DumpDataIO {
+
+ /**
+ * Serialize the given collection to an XML document. Returns the produced string.
+ */
+ public static String serialize(Collection<DumpData> data) {
+ // We'll do this by hand, constructing a DOM or similar is too complicated for our simple
+ // use case.
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("<preloaded-classes-data>\n");
+
+ for (DumpData d : data) {
+ serialize(d, sb);
+ }
+
+ sb.append("</preloaded-classes-data>\n");
+ return sb.toString();
+ }
+
+ private static void serialize(DumpData d, StringBuilder sb) {
+ sb.append("<data package=\"" + d.packageName + "\" date=\"" +
+ DateFormat.getDateTimeInstance().format(d.date) +"\">\n");
+
+ for (Map.Entry<String, String> e : d.dumpData.entrySet()) {
+ sb.append("<class name=\"" + e.getKey() + "\" classloader=\"" + e.getValue() + "\"/>\n");
+ }
+
+ sb.append("</data>\n");
+ }
+
+ /**
+ * Load a collection of DumpData objects from the given file.
+ */
+ public static Collection<DumpData> deserialize(File f) throws Exception {
+ // Use SAX parsing. Our format is very simple. Don't do any schema validation or such.
+
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setNamespaceAware(false);
+ SAXParser saxParser = spf.newSAXParser();
+
+ XMLReader xmlReader = saxParser.getXMLReader();
+ DumpDataContentHandler ddch = new DumpDataContentHandler();
+ xmlReader.setContentHandler(ddch);
+ xmlReader.parse(new InputSource(new FileReader(f)));
+
+ return ddch.data;
+ }
+
+ private static class DumpDataContentHandler extends DefaultHandler {
+ Collection<DumpData> data = new LinkedList<DumpData>();
+ DumpData openData = null;
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ if (qName.equals("data")) {
+ if (openData != null) {
+ throw new IllegalStateException();
+ }
+ String pkg = attributes.getValue("package");
+ String dateString = attributes.getValue("date");
+
+ if (pkg == null || dateString == null) {
+ throw new IllegalArgumentException();
+ }
+
+ try {
+ Date date = DateFormat.getDateTimeInstance().parse(dateString);
+ openData = new DumpData(pkg, new HashMap<String, String>(), date);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ } else if (qName.equals("class")) {
+ if (openData == null) {
+ throw new IllegalStateException();
+ }
+ String className = attributes.getValue("name");
+ String classLoader = attributes.getValue("classloader");
+
+ if (className == null || classLoader == null) {
+ throw new IllegalArgumentException();
+ }
+
+ openData.dumpData.put(className, classLoader.equals("null") ? null : classLoader);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (qName.equals("data")) {
+ if (openData == null) {
+ throw new IllegalStateException();
+ }
+ openData.countBootClassPath();
+
+ data.add(openData);
+ openData = null;
+ }
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/DumpTableModel.java b/tools/preload2/src/com/android/preload/DumpTableModel.java
new file mode 100644
index 0000000..d97cbf0
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpTableModel.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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.preload;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * A table model for collected DumpData. This is both the internal storage as well as the model
+ * for display.
+ */
+public class DumpTableModel extends AbstractTableModel {
+
+ private List<DumpData> data = new ArrayList<DumpData>();
+
+ public void addData(DumpData d) {
+ data.add(d);
+ fireTableRowsInserted(data.size() - 1, data.size() - 1);
+ }
+
+ public void clear() {
+ int size = data.size();
+ if (size > 0) {
+ data.clear();
+ fireTableRowsDeleted(0, size - 1);
+ }
+ }
+
+ public List<DumpData> getData() {
+ return data;
+ }
+
+ @Override
+ public int getRowCount() {
+ return data.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 4;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0:
+ return "Package";
+ case 1:
+ return "Date";
+ case 2:
+ return "# All Classes";
+ case 3:
+ return "# Boot Classpath Classes";
+
+ default:
+ throw new IndexOutOfBoundsException(String.valueOf(column));
+ }
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ DumpData d = data.get(rowIndex);
+ switch (columnIndex) {
+ case 0:
+ return d.packageName;
+ case 1:
+ return d.date;
+ case 2:
+ return d.dumpData.size();
+ case 3:
+ return d.bcpClasses;
+
+ default:
+ throw new IndexOutOfBoundsException(String.valueOf(columnIndex));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java
new file mode 100644
index 0000000..ca5b0e0
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/Main.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 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.preload;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.actions.ClearTableAction;
+import com.android.preload.actions.ComputeThresholdAction;
+import com.android.preload.actions.ComputeThresholdXAction;
+import com.android.preload.actions.DeviceSpecific;
+import com.android.preload.actions.ExportAction;
+import com.android.preload.actions.ImportAction;
+import com.android.preload.actions.ReloadListAction;
+import com.android.preload.actions.RunMonkeyAction;
+import com.android.preload.actions.ScanAllPackagesAction;
+import com.android.preload.actions.ScanPackageAction;
+import com.android.preload.actions.ShowDataAction;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+import com.android.preload.classdataretrieval.hprof.Hprof;
+import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
+import com.android.preload.ui.UI;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.Action;
+import javax.swing.DefaultListModel;
+
+public class Main {
+
+ /**
+ * Enable tracing mode. This is a work-in-progress to derive compiled-methods data, so it is
+ * off for now.
+ */
+ public final static boolean ENABLE_TRACING = false;
+
+ /**
+ * Ten-second timeout.
+ */
+ public final static int DEFAULT_TIMEOUT_MILLIS = 10 * 1000;
+
+ /**
+ * Hprof timeout. Two minutes.
+ */
+ public final static int HPROF_TIMEOUT_MILLIS = 120 * 1000;
+
+ private IDevice device;
+ private static ClientUtils clientUtils;
+
+ private DumpTableModel dataTableModel;
+ private DefaultListModel<Client> clientListModel;
+
+ private UI ui;
+
+ // Actions that need to be updated once a device is selected.
+ private Collection<DeviceSpecific> deviceSpecificActions;
+
+ // Current main instance.
+ private static Main top;
+ private static boolean useJdwpClassDataRetriever = false;
+
+ public final static String CLASS_PRELOAD_BLACKLIST = "android.app.AlarmManager$" + "|"
+ + "android.app.SearchManager$" + "|" + "android.os.FileObserver$" + "|"
+ + "com.android.server.PackageManagerService\\$AppDirObserver$" + "|" +
+
+
+ // Threads
+ "android.os.AsyncTask$" + "|" + "android.pim.ContactsAsyncHelper$" + "|"
+ + "android.webkit.WebViewClassic\\$1$" + "|" + "java.lang.ProcessManager$" + "|"
+ + "(.*\\$NoPreloadHolder$)";
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ Main m = new Main();
+ top = m;
+
+ m.startUp();
+ }
+
+ public Main() {
+ clientListModel = new DefaultListModel<Client>();
+ dataTableModel = new DumpTableModel();
+
+ clientUtils = new ClientUtils(DEFAULT_TIMEOUT_MILLIS); // Client utils with 10s timeout.
+
+ List<Action> actions = new ArrayList<Action>();
+ actions.add(new ReloadListAction(clientUtils, null, clientListModel));
+ actions.add(new ClearTableAction(dataTableModel));
+ actions.add(new RunMonkeyAction(null, dataTableModel));
+ actions.add(new ScanPackageAction(clientUtils, null, dataTableModel));
+ actions.add(new ScanAllPackagesAction(clientUtils, null, dataTableModel));
+ actions.add(new ComputeThresholdAction("Compute preloaded-classes", dataTableModel, 2,
+ CLASS_PRELOAD_BLACKLIST));
+ actions.add(new ComputeThresholdAction("Compute compiled-classes", dataTableModel, 1,
+ null));
+ actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel,
+ CLASS_PRELOAD_BLACKLIST));
+ actions.add(new ShowDataAction(dataTableModel));
+ actions.add(new ImportAction(dataTableModel));
+ actions.add(new ExportAction(dataTableModel));
+
+ deviceSpecificActions = new ArrayList<DeviceSpecific>();
+ for (Action a : actions) {
+ if (a instanceof DeviceSpecific) {
+ deviceSpecificActions.add((DeviceSpecific)a);
+ }
+ }
+
+ ui = new UI(clientListModel, dataTableModel, actions);
+ ui.setVisible(true);
+ }
+
+ public static UI getUI() {
+ return top.ui;
+ }
+
+ public static ClassDataRetriever getClassDataRetriever() {
+ if (useJdwpClassDataRetriever) {
+ return new JDWPClassDataRetriever();
+ } else {
+ return new Hprof(HPROF_TIMEOUT_MILLIS);
+ }
+ }
+
+ public IDevice getDevice() {
+ return device;
+ }
+
+ public void setDevice(IDevice device) {
+ this.device = device;
+ for (DeviceSpecific ds : deviceSpecificActions) {
+ ds.setDevice(device);
+ }
+ }
+
+ public DefaultListModel<Client> getClientListModel() {
+ return clientListModel;
+ }
+
+ static class DeviceWrapper {
+ IDevice device;
+
+ public DeviceWrapper(IDevice d) {
+ device = d;
+ }
+
+ @Override
+ public String toString() {
+ return device.getName() + " (#" + device.getSerialNumber() + ")";
+ }
+ }
+
+ private void startUp() {
+ getUI().showWaitDialog();
+ initDevice();
+
+ // Load clients.
+ new ReloadListAction(clientUtils, getDevice(), clientListModel).run();
+
+ getUI().hideWaitDialog();
+ }
+
+ private void initDevice() {
+ DeviceUtils.init(DEFAULT_TIMEOUT_MILLIS);
+
+ IDevice devices[] = DeviceUtils.findDevices(DEFAULT_TIMEOUT_MILLIS);
+ if (devices == null || devices.length == 0) {
+ throw new RuntimeException("Could not find any devices...");
+ }
+
+ getUI().hideWaitDialog();
+
+ DeviceWrapper deviceWrappers[] = new DeviceWrapper[devices.length];
+ for (int i = 0; i < devices.length; i++) {
+ deviceWrappers[i] = new DeviceWrapper(devices[i]);
+ }
+
+ DeviceWrapper ret = Main.getUI().showChoiceDialog("Choose a device", "Choose device",
+ deviceWrappers);
+ if (ret != null) {
+ setDevice(ret.device);
+ } else {
+ System.exit(0);
+ }
+
+ boolean prepare = Main.getUI().showConfirmDialog("Prepare device?",
+ "Do you want to prepare the device? This is highly recommended.");
+ if (prepare) {
+ String buildType = DeviceUtils.getBuildType(device);
+ if (buildType == null || (!buildType.equals("userdebug") && !buildType.equals("eng"))) {
+ Main.getUI().showMessageDialog("Need a userdebug or eng build! (Found " + buildType
+ + ")");
+ return;
+ }
+ if (DeviceUtils.hasPrebuiltBootImage(device)) {
+ Main.getUI().showMessageDialog("Cannot prepare a device with pre-optimized boot "
+ + "image!");
+ return;
+ }
+
+ if (ENABLE_TRACING) {
+ DeviceUtils.enableTracing(device);
+ }
+
+ Main.getUI().showMessageDialog("The device will reboot. This will potentially take a "
+ + "long time. Please be patient.");
+ if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) {
+ Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!");
+ }
+ }
+ }
+
+ public static Map<String, String> findAndGetClassData(IDevice device, String packageName)
+ throws Exception {
+ Client client = clientUtils.findClient(device, packageName, -1);
+ if (client == null) {
+ throw new RuntimeException("Could not find client...");
+ }
+ System.out.println("Found client: " + client);
+
+ return getClassDataRetriever().getClassData(client);
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
new file mode 100644
index 0000000..fbf83d2
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+public abstract class AbstractThreadedAction extends AbstractAction implements Runnable {
+
+ protected AbstractThreadedAction(String title) {
+ super(title);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ new Thread(this).start();
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
new file mode 100644
index 0000000..7906417
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.ddmlib.IDevice;
+
+import java.awt.event.ActionEvent;
+
+public abstract class AbstractThreadedDeviceSpecificAction extends AbstractThreadedAction
+ implements DeviceSpecific {
+
+ protected IDevice device;
+
+ protected AbstractThreadedDeviceSpecificAction(String title, IDevice device) {
+ super(title);
+ this.device = device;
+ }
+
+ @Override
+ public void setDevice(IDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (device == null) {
+ return;
+ }
+ super.actionPerformed(e);
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
new file mode 100644
index 0000000..c0e4795
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.preload.DumpTableModel;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+public class ClearTableAction extends AbstractAction {
+ private final DumpTableModel dataTableModel;
+
+ public ClearTableAction(DumpTableModel dataTableModel) {
+ super("Clear");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dataTableModel.clear();
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
new file mode 100644
index 0000000..b524716
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+
+/**
+ * Compute an intersection of classes from the given data. A class is in the intersection if it
+ * appears in at least the number of threshold given packages. An optional blacklist can be
+ * used to filter classes from the intersection.
+ */
+public class ComputeThresholdAction extends AbstractAction implements Runnable {
+ protected int threshold;
+ private Pattern blacklist;
+ private DumpTableModel dataTableModel;
+
+ /**
+ * Create an action with the given parameters. The blacklist is a regular expression
+ * that filters classes.
+ */
+ public ComputeThresholdAction(String name, DumpTableModel dataTableModel, int threshold,
+ String blacklist) {
+ super(name);
+ this.dataTableModel = dataTableModel;
+ this.threshold = threshold;
+ if (blacklist != null) {
+ this.blacklist = Pattern.compile(blacklist);
+ }
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ List<DumpData> data = dataTableModel.getData();
+ if (data.size() == 0) {
+ Main.getUI().showMessageDialog("No data available, please scan packages or run "
+ + "monkeys.");
+ return;
+ }
+ if (data.size() == 1) {
+ Main.getUI().showMessageDialog("Cannot compute list from only one data set, please "
+ + "scan packages or run monkeys.");
+ return;
+ }
+
+ new Thread(this).start();
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ Map<String, Set<String>> uses = new HashMap<String, Set<String>>();
+ for (DumpData d : dataTableModel.getData()) {
+ Main.getUI().updateWaitDialog("Merging " + d.getPackageName());
+ updateClassUse(d.getPackageName(), uses, getBootClassPathClasses(d.getDumpData()));
+ }
+
+ Main.getUI().updateWaitDialog("Computing thresholded set");
+ Set<String> result = fromThreshold(uses, blacklist, threshold);
+ Main.getUI().hideWaitDialog();
+
+ boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size()
+ + " classes, would you like to save to disk?", "Save?");
+ if (ret) {
+ JFileChooser jfc = new JFileChooser();
+ int ret2 = jfc.showSaveDialog(Main.getUI());
+ if (ret2 == JFileChooser.APPROVE_OPTION) {
+ File f = jfc.getSelectedFile();
+ saveSet(result, f);
+ }
+ }
+ }
+
+ private Set<String> fromThreshold(Map<String, Set<String>> classUses, Pattern blacklist,
+ int threshold) {
+ TreeSet<String> ret = new TreeSet<>(); // TreeSet so it's nicely ordered by name.
+
+ for (Map.Entry<String, Set<String>> e : classUses.entrySet()) {
+ if (e.getValue().size() >= threshold) {
+ if (blacklist == null || !blacklist.matcher(e.getKey()).matches()) {
+ ret.add(e.getKey());
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ private static void updateClassUse(String pkg, Map<String, Set<String>> classUses,
+ Set<String> classes) {
+ for (String className : classes) {
+ Set<String> old = classUses.get(className);
+ if (old == null) {
+ classUses.put(className, new HashSet<String>());
+ }
+ classUses.get(className).add(pkg);
+ }
+ }
+
+ private static Set<String> getBootClassPathClasses(Map<String, String> source) {
+ Set<String> ret = new HashSet<>();
+ for (Map.Entry<String, String> e : source.entrySet()) {
+ if (e.getValue() == null) {
+ ret.add(e.getKey());
+ }
+ }
+ return ret;
+ }
+
+ private static void saveSet(Set<String> result, File f) {
+ try {
+ PrintWriter out = new PrintWriter(f);
+ for (String s : result) {
+ out.println(s);
+ }
+ out.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
new file mode 100644
index 0000000..3ec0a4c
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+public class ComputeThresholdXAction extends ComputeThresholdAction {
+
+ public ComputeThresholdXAction(String name, DumpTableModel dataTableModel,
+ String blacklist) {
+ super(name, dataTableModel, 1, blacklist);
+ }
+
+ @Override
+ public void run() {
+ String value = Main.getUI().showInputDialog("Threshold?");
+
+ if (value != null) {
+ try {
+ threshold = Integer.parseInt(value);
+ super.run();
+ } catch (Exception exc) {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
new file mode 100644
index 0000000..35a8f26
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.ddmlib.IDevice;
+
+/**
+ * Marks an action as being device-specific. The user must set the device through the specified
+ * method if the device selection changes.
+ *
+ * Implementors must tolerate a null device (for example, with a no-op). This includes calling
+ * any methods before setDevice has been called.
+ */
+public interface DeviceSpecific {
+
+ /**
+ * Set the device that should be used. Note that there is no restriction on calling other
+ * methods of the implementor before a setDevice call. Neither is device guaranteed to be
+ * non-null.
+ *
+ * @param device The device to use going forward.
+ */
+ public void setDevice(IDevice device);
+}
diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java
new file mode 100644
index 0000000..cb8b3df
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ExportAction.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.preload.DumpDataIO;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.PrintWriter;
+
+import javax.swing.AbstractAction;
+
+public class ExportAction extends AbstractAction implements Runnable {
+ private File lastSaveFile;
+ private DumpTableModel dataTableModel;
+
+ public ExportAction(DumpTableModel dataTableModel) {
+ super("Export data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ lastSaveFile = Main.getUI().showSaveDialog();
+ if (lastSaveFile != null) {
+ new Thread(this).start();
+ }
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ String serialized = DumpDataIO.serialize(dataTableModel.getData());
+
+ if (serialized != null) {
+ try {
+ PrintWriter out = new PrintWriter(lastSaveFile);
+ out.println(serialized);
+ out.close();
+
+ Main.getUI().hideWaitDialog();
+ } catch (Exception e) {
+ Main.getUI().hideWaitDialog();
+ Main.getUI().showMessageDialog("Failed writing: " + e.getMessage());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java
new file mode 100644
index 0000000..5c19765
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ImportAction.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpDataIO;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.Collection;
+
+import javax.swing.AbstractAction;
+
+public class ImportAction extends AbstractAction implements Runnable {
+ private File[] lastOpenFiles;
+ private DumpTableModel dataTableModel;
+
+ public ImportAction(DumpTableModel dataTableModel) {
+ super("Import data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ lastOpenFiles = Main.getUI().showOpenDialog(true);
+ if (lastOpenFiles != null) {
+ new Thread(this).start();
+ }
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ for (File f : lastOpenFiles) {
+ try {
+ Collection<DumpData> data = DumpDataIO.deserialize(f);
+
+ for (DumpData d : data) {
+ dataTableModel.addData(d);
+ }
+ } catch (Exception e) {
+ Main.getUI().showMessageDialog("Failed reading: " + e.getMessage());
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
new file mode 100644
index 0000000..29f0557
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.swing.DefaultListModel;
+
+public class ReloadListAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private final DefaultListModel<Client> clientListModel;
+
+ public ReloadListAction(ClientUtils utils, IDevice device,
+ DefaultListModel<Client> clientListModel) {
+ super("Reload", device);
+ this.clientUtils = utils;
+ this.clientListModel = clientListModel;
+ }
+
+ @Override
+ public void run() {
+ Client[] clients = clientUtils.findAllClients(device);
+ if (clients != null) {
+ Arrays.sort(clients, new ClientComparator());
+ }
+ clientListModel.removeAllElements();
+ for (Client c : clients) {
+ clientListModel.addElement(c);
+ }
+ }
+
+ private static class ClientComparator implements Comparator<Client> {
+
+ @Override
+ public int compare(Client o1, Client o2) {
+ String s1 = o1.getClientData().getClientDescription();
+ String s2 = o2.getClientData().getClientDescription();
+
+ if (s1 == null || s2 == null) {
+ // Not good, didn't get all data?
+ return (s1 == null) ? -1 : 1;
+ }
+
+ return s1.compareTo(s2);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
new file mode 100644
index 0000000..385e857
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.ddmlib.IDevice;
+import com.android.preload.DeviceUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.AbstractAction;
+
+public class RunMonkeyAction extends AbstractAction implements DeviceSpecific {
+
+ private final static String DEFAULT_MONKEY_PACKAGES =
+ "com.android.calendar,com.android.gallery3d";
+
+ private IDevice device;
+ private DumpTableModel dataTableModel;
+
+ public RunMonkeyAction(IDevice device, DumpTableModel dataTableModel) {
+ super("Run monkey");
+ this.device = device;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void setDevice(IDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String packages = Main.getUI().showInputDialog("Please enter packages name to run with"
+ + " the monkey, or leave empty for default.");
+ if (packages == null) {
+ return;
+ }
+ if (packages.isEmpty()) {
+ packages = DEFAULT_MONKEY_PACKAGES;
+ }
+ new Thread(new RunMonkeyRunnable(packages)).start();
+ }
+
+ private class RunMonkeyRunnable implements Runnable {
+
+ private String packages;
+ private final static int ITERATIONS = 1000;
+
+ public RunMonkeyRunnable(String packages) {
+ this.packages = packages;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ String pkgs[] = packages.split(",");
+
+ for (String pkg : pkgs) {
+ Main.getUI().updateWaitDialog("Running monkey on " + pkg);
+
+ try {
+ // Stop running app.
+ forceStop(pkg);
+
+ // Little bit of breather here.
+ try {
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ }
+
+ DeviceUtils.doShell(device, "monkey -p " + pkg + " " + ITERATIONS, 1,
+ TimeUnit.MINUTES);
+
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+ Map<String, String> data = Main.findAndGetClassData(device, pkg);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ // Stop running app.
+ forceStop(pkg);
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+
+ private void forceStop(String packageName) {
+ // Stop running app.
+ DeviceUtils.doShell(device, "force-stop " + packageName, 5, TimeUnit.SECONDS);
+ DeviceUtils.doShell(device, "kill " + packageName, 5, TimeUnit.SECONDS);
+ DeviceUtils.doShell(device, "kill `pid " + packageName + "`", 5, TimeUnit.SECONDS);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
new file mode 100644
index 0000000..d74b8a3
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.util.Date;
+import java.util.Map;
+
+public class ScanAllPackagesAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private DumpTableModel dataTableModel;
+
+ public ScanAllPackagesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
+ super("Scan all packages", device);
+ this.clientUtils = utils;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ Client[] clients = clientUtils.findAllClients(device);
+ for (Client c : clients) {
+ String pkg = c.getClientData().getClientDescription();
+ Main.getUI().showWaitDialog();
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+
+ try {
+ Map<String, String> data = Main.getClassDataRetriever().getClassData(c);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
new file mode 100644
index 0000000..98492bd
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.util.Date;
+import java.util.Map;
+
+public class ScanPackageAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private DumpTableModel dataTableModel;
+
+ public ScanPackageAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
+ super("Scan package", device);
+ this.clientUtils = utils;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ Client client = Main.getUI().getSelectedClient();
+ if (client != null) {
+ work(client);
+ } else {
+ Client[] clients = clientUtils.findAllClients(device);
+ if (clients.length > 0) {
+ ClientWrapper[] clientWrappers = new ClientWrapper[clients.length];
+ for (int i = 0; i < clientWrappers.length; i++) {
+ clientWrappers[i] = new ClientWrapper(clients[i]);
+ }
+ Main.getUI().hideWaitDialog();
+
+ ClientWrapper ret = Main.getUI().showChoiceDialog("Choose a package to scan",
+ "Choose package",
+ clientWrappers);
+ if (ret != null) {
+ work(ret.client);
+ }
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+
+ private void work(Client c) {
+ String pkg = c.getClientData().getClientDescription();
+ Main.getUI().showWaitDialog();
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+
+ try {
+ Map<String, String> data = Main.findAndGetClassData(device, pkg);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static class ClientWrapper {
+ private Client client;
+
+ public ClientWrapper(Client c) {
+ client = c;
+ }
+
+ @Override
+ public String toString() {
+ return client.getClientData().getClientDescription() + " (pid "
+ + client.getClientData().getPid() + ")";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
new file mode 100644
index 0000000..2bb175f
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+public class ShowDataAction extends AbstractAction {
+ private DumpTableModel dataTableModel;
+
+ public ShowDataAction(DumpTableModel dataTableModel) {
+ super("Show data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // TODO(agampe): Auto-generated method stub
+ int selRow = Main.getUI().getSelectedDataTableRow();
+ if (selRow != -1) {
+ DumpData data = dataTableModel.getData().get(selRow);
+ Map<String, Set<String>> inv = data.invertData();
+
+ StringBuilder builder = new StringBuilder();
+
+ // First bootclasspath.
+ add(builder, "Boot classpath:", inv.get(null));
+
+ // Now everything else.
+ for (String k : inv.keySet()) {
+ if (k != null) {
+ builder.append("==================\n\n");
+ add(builder, k, inv.get(k));
+ }
+ }
+
+ JFrame newFrame = new JFrame(data.getPackageName() + " " + data.getDate());
+ newFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+ newFrame.getContentPane().add(new JScrollPane(new JTextArea(builder.toString())),
+ BorderLayout.CENTER);
+ newFrame.setSize(800, 600);
+ newFrame.setLocationRelativeTo(null);
+ newFrame.setVisible(true);
+ }
+ }
+
+ private void add(StringBuilder builder, String head, Set<String> set) {
+ builder.append(head);
+ builder.append('\n');
+ addSet(builder, set);
+ builder.append('\n');
+ }
+
+ private void addSet(StringBuilder builder, Set<String> set) {
+ if (set == null) {
+ builder.append(" NONE\n");
+ return;
+ }
+ List<String> sorted = new ArrayList<>(set);
+ Collections.sort(sorted);
+ for (String s : sorted) {
+ builder.append(s);
+ builder.append('\n');
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
new file mode 100644
index 0000000..f04360f
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 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.preload.classdataretrieval;
+
+import com.android.ddmlib.Client;
+
+import java.util.Map;
+
+/**
+ * Retrieve a class-to-classloader map for loaded classes from the client.
+ */
+public interface ClassDataRetriever {
+
+ public Map<String, String> getClassData(Client client);
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
new file mode 100644
index 0000000..8d797ee
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 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.preload.classdataretrieval.hprof;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GeneralHprofDumpHandler implements IHprofDumpHandler {
+
+ private List<IHprofDumpHandler> handlers = new ArrayList<>();
+
+ public void addHandler(IHprofDumpHandler h) {
+ synchronized (handlers) {
+ handlers.add(h);
+ }
+ }
+
+ public void removeHandler(IHprofDumpHandler h) {
+ synchronized (handlers) {
+ handlers.remove(h);
+ }
+ }
+
+ private List<IHprofDumpHandler> getIterationList() {
+ synchronized (handlers) {
+ return new ArrayList<>(handlers);
+ }
+ }
+
+ @Override
+ public void onEndFailure(Client arg0, String arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onEndFailure(arg0, arg1);
+ }
+ }
+
+ @Override
+ public void onSuccess(String arg0, Client arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onSuccess(arg0, arg1);
+ }
+ }
+
+ @Override
+ public void onSuccess(byte[] arg0, Client arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onSuccess(arg0, arg1);
+ }
+ }
+ }
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
new file mode 100644
index 0000000..21b7a04
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 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.preload.classdataretrieval.hprof;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+import com.android.preload.ui.NullProgressMonitor;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Queries;
+import com.android.tools.perflib.heap.Snapshot;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class Hprof implements ClassDataRetriever {
+
+ private static GeneralHprofDumpHandler hprofHandler;
+
+ public static void init() {
+ synchronized(Hprof.class) {
+ if (hprofHandler == null) {
+ ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler());
+ }
+ }
+ }
+
+ public static File doHprof(Client client, int timeout) {
+ GetHprof gh = new GetHprof(client, timeout);
+ return gh.get();
+ }
+
+ /**
+ * Return a map of class names to class-loader names derived from the hprof dump.
+ *
+ * @param hprofLocalFile
+ */
+ public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception {
+ Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile));
+
+ Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null);
+ Map<String, String> retValue = new HashMap<String, String>();
+ for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) {
+ for (ClassObj c : e.getValue()) {
+ String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString();
+ String cName = c.getClassName();
+ int aDepth = 0;
+ while (cName.endsWith("[]")) {
+ cName = cName.substring(0, cName.length()-2);
+ aDepth++;
+ }
+ String newName = transformPrimitiveClass(cName);
+ if (aDepth > 0) {
+ // Need to use kind-a descriptor syntax. If it was transformed, it is primitive.
+ if (newName.equals(cName)) {
+ newName = "L" + newName + ";";
+ }
+ for (int i = 0; i < aDepth; i++) {
+ newName = "[" + newName;
+ }
+ }
+ retValue.put(newName, cl);
+ }
+ }
+
+ // Free up memory.
+ snapshot.dispose();
+
+ return retValue;
+ }
+
+ private static Map<String, String> primitiveMapping;
+
+ static {
+ primitiveMapping = new HashMap<>();
+ primitiveMapping.put("boolean", "Z");
+ primitiveMapping.put("byte", "B");
+ primitiveMapping.put("char", "C");
+ primitiveMapping.put("double", "D");
+ primitiveMapping.put("float", "F");
+ primitiveMapping.put("int", "I");
+ primitiveMapping.put("long", "J");
+ primitiveMapping.put("short", "S");
+ primitiveMapping.put("void", "V");
+ }
+
+ private static String transformPrimitiveClass(String name) {
+ String rep = primitiveMapping.get(name);
+ if (rep != null) {
+ return rep;
+ }
+ return name;
+ }
+
+ private static class GetHprof implements IHprofDumpHandler {
+
+ private File target;
+ private long timeout;
+ private Client client;
+
+ public GetHprof(Client client, long timeout) {
+ this.client = client;
+ this.timeout = timeout;
+ }
+
+ public File get() {
+ synchronized (this) {
+ hprofHandler.addHandler(this);
+ client.dumpHprof();
+ if (target == null) {
+ try {
+ wait(timeout);
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+ }
+
+ hprofHandler.removeHandler(this);
+ return target;
+ }
+
+ private void wakeUp() {
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onEndFailure(Client arg0, String arg1) {
+ System.out.println("GetHprof.onEndFailure");
+ if (client == arg0) {
+ wakeUp();
+ }
+ }
+
+ private static File createTargetFile() {
+ try {
+ return File.createTempFile("ddms", ".hprof");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void onSuccess(String arg0, Client arg1) {
+ System.out.println("GetHprof.onSuccess");
+ if (client == arg1) {
+ try {
+ target = createTargetFile();
+ arg1.getDevice().getSyncService().pullFile(arg0,
+ target.getAbsoluteFile().toString(), new NullProgressMonitor());
+ } catch (Exception e) {
+ e.printStackTrace();
+ target = null;
+ }
+ wakeUp();
+ }
+ }
+
+ @Override
+ public void onSuccess(byte[] arg0, Client arg1) {
+ System.out.println("GetHprof.onSuccess");
+ if (client == arg1) {
+ try {
+ target = createTargetFile();
+ BufferedOutputStream out =
+ new BufferedOutputStream(new FileOutputStream(target));
+ out.write(arg0);
+ out.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ target = null;
+ }
+ wakeUp();
+ }
+ }
+ }
+
+ private int timeout;
+
+ public Hprof(int timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public Map<String, String> getClassData(Client client) {
+ File hprofLocalFile = Hprof.doHprof(client, timeout);
+ if (hprofLocalFile == null) {
+ throw new RuntimeException("Failed getting dump...");
+ }
+ System.out.println("Dump file is " + hprofLocalFile);
+
+ try {
+ return analyzeHprof(hprofLocalFile);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
new file mode 100644
index 0000000..dbd4c89
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 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.preload.classdataretrieval.jdwp;
+
+import com.android.ddmlib.Client;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPTestCase;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPUnitDebuggeeWrapper;
+import org.apache.harmony.jpda.tests.share.JPDALogWriter;
+import org.apache.harmony.jpda.tests.share.JPDATestOptions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JDWPClassDataRetriever extends JDWPTestCase implements ClassDataRetriever {
+
+ private final Client client;
+
+ public JDWPClassDataRetriever() {
+ this(null);
+ }
+
+ public JDWPClassDataRetriever(Client client) {
+ this.client = client;
+ }
+
+
+ @Override
+ protected String getDebuggeeClassName() {
+ return "<unset>";
+ }
+
+ @Override
+ public Map<String, String> getClassData(Client client) {
+ return new JDWPClassDataRetriever(client).retrieve();
+ }
+
+ private Map<String, String> retrieve() {
+ if (client == null) {
+ throw new IllegalStateException();
+ }
+
+ settings = createTestOptions("localhost:" + String.valueOf(client.getDebuggerListenPort()));
+ settings.setDebuggeeSuspend("n");
+
+ logWriter = new JPDALogWriter(System.out, "", false);
+
+ try {
+ internalSetUp();
+
+ return retrieveImpl();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ internalTearDown();
+ }
+ }
+
+ private Map<String, String> retrieveImpl() {
+ try {
+ // Suspend the app.
+ {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.SuspendCommand);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return null;
+ }
+ }
+
+ // List all classes.
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.AllClassesCommand);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return null;
+ }
+
+ int classCount = reply.getNextValueAsInt();
+ System.out.println("Runtime reported " + classCount + " classes.");
+
+ Map<Long, String> classes = new HashMap<Long, String>();
+ Map<Long, String> arrayClasses = new HashMap<Long, String>();
+
+ for (int i = 0; i < classCount; i++) {
+ byte refTypeTag = reply.getNextValueAsByte();
+ long typeID = reply.getNextValueAsReferenceTypeID();
+ String signature = reply.getNextValueAsString();
+ /* int status = */ reply.getNextValueAsInt();
+
+ switch (refTypeTag) {
+ case JDWPConstants.TypeTag.CLASS:
+ case JDWPConstants.TypeTag.INTERFACE:
+ classes.put(typeID, signature);
+ break;
+
+ case JDWPConstants.TypeTag.ARRAY:
+ arrayClasses.put(typeID, signature);
+ break;
+ }
+ }
+
+ Map<String, String> result = new HashMap<String, String>();
+
+ // Parse all classes.
+ for (Map.Entry<Long, String> entry : classes.entrySet()) {
+ long typeID = entry.getKey();
+ String signature = entry.getValue();
+
+ if (!checkClass(typeID, signature, result)) {
+ System.err.println("Issue investigating " + signature);
+ }
+ }
+
+ // For arrays, look at the leaf component type.
+ for (Map.Entry<Long, String> entry : arrayClasses.entrySet()) {
+ long typeID = entry.getKey();
+ String signature = entry.getValue();
+
+ if (!checkArrayClass(typeID, signature, result)) {
+ System.err.println("Issue investigating " + signature);
+ }
+ }
+
+ return result;
+ } finally {
+ // Resume the app.
+ {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.ResumeCommand);
+ /* ReplyPacket reply = */ debuggeeWrapper.vmMirror.performCommand(packet);
+ }
+ }
+ }
+
+ private boolean checkClass(long typeID, String signature, Map<String, String> result) {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+ JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
+ packet.setNextValueAsReferenceTypeID(typeID);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return false;
+ }
+
+ long classLoaderID = reply.getNextValueAsObjectID();
+
+ // TODO: Investigate the classloader to have a better string?
+ String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
+
+ result.put(getClassName(signature), classLoaderString);
+
+ return true;
+ }
+
+ private boolean checkArrayClass(long typeID, String signature, Map<String, String> result) {
+ // Classloaders of array classes are the same as the component class'.
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+ JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
+ packet.setNextValueAsReferenceTypeID(typeID);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return false;
+ }
+
+ long classLoaderID = reply.getNextValueAsObjectID();
+
+ // TODO: Investigate the classloader to have a better string?
+ String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
+
+ // For array classes, we *need* the signature directly.
+ result.put(signature, classLoaderString);
+
+ return true;
+ }
+
+ private static String getClassName(String signature) {
+ String withoutLAndSemicolon = signature.substring(1, signature.length() - 1);
+ return withoutLAndSemicolon.replace('/', '.');
+ }
+
+
+ private static JPDATestOptions createTestOptions(String address) {
+ JPDATestOptions options = new JPDATestOptions();
+ options.setAttachConnectorKind();
+ options.setTimeout(1000);
+ options.setWaitingTime(1000);
+ options.setTransportAddress(address);
+ return options;
+ }
+
+ @Override
+ protected JDWPUnitDebuggeeWrapper createDebuggeeWrapper() {
+ return new PreloadDebugeeWrapper(settings, logWriter);
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
new file mode 100644
index 0000000..b9df6d0
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 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.preload.classdataretrieval.jdwp;
+
+import org.apache.harmony.jpda.tests.framework.LogWriter;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPManualDebuggeeWrapper;
+import org.apache.harmony.jpda.tests.share.JPDATestOptions;
+
+import java.io.IOException;
+
+public class PreloadDebugeeWrapper extends JDWPManualDebuggeeWrapper {
+
+ public PreloadDebugeeWrapper(JPDATestOptions options, LogWriter writer) {
+ super(options, writer);
+ }
+
+ @Override
+ protected Process launchProcess(String cmdLine) throws IOException {
+ return null;
+ }
+
+ @Override
+ protected void WaitForProcessExit(Process process) {
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
new file mode 100644
index 0000000..f45aad06
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 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.preload.ui;
+
+import com.android.ddmlib.SyncService.ISyncProgressMonitor;
+
+public class NullProgressMonitor implements ISyncProgressMonitor {
+
+ @Override
+ public void advance(int arg0) {}
+
+ @Override
+ public boolean isCanceled() {
+ return false;
+ }
+
+ @Override
+ public void start(int arg0) {}
+
+ @Override
+ public void startSubTask(String arg0) {}
+
+ @Override
+ public void stop() {}
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/ui/UI.java b/tools/preload2/src/com/android/preload/ui/UI.java
new file mode 100644
index 0000000..47174dd
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ui/UI.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2015 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.preload.ui;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.io.File;
+import java.util.List;
+
+import javax.swing.Action;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JToolBar;
+import javax.swing.ListModel;
+import javax.swing.SwingUtilities;
+import javax.swing.table.TableModel;
+
+public class UI extends JFrame {
+
+ private JList<Client> clientList;
+ private JTable dataTable;
+
+ // Shared file chooser, means the directory is retained.
+ private JFileChooser jfc;
+
+ public UI(ListModel<Client> clientListModel,
+ TableModel dataTableModel,
+ List<Action> actions) {
+ super("Preloaded-classes computation");
+
+ getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)),
+ BorderLayout.WEST);
+ clientList.setCellRenderer(new ClientListCellRenderer());
+ // clientList.addListSelectionListener(listener);
+
+ dataTable = new JTable(dataTableModel);
+ getContentPane().add(new JScrollPane(dataTable), BorderLayout.CENTER);
+
+ JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL);
+ for (Action a : actions) {
+ if (a == null) {
+ toolbar.addSeparator();
+ } else {
+ toolbar.add(a);
+ }
+ }
+ getContentPane().add(toolbar, BorderLayout.PAGE_START);
+
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ setBounds(100, 100, 800, 600);
+ }
+
+ public Client getSelectedClient() {
+ return clientList.getSelectedValue();
+ }
+
+ public int getSelectedDataTableRow() {
+ return dataTable.getSelectedRow();
+ }
+
+ private JDialog currentWaitDialog = null;
+
+ public void showWaitDialog() {
+ if (currentWaitDialog == null) {
+ currentWaitDialog = new JDialog(this, "Please wait...", true);
+ currentWaitDialog.getContentPane().add(new JLabel("Please be patient."),
+ BorderLayout.CENTER);
+ JProgressBar progress = new JProgressBar(JProgressBar.HORIZONTAL);
+ progress.setIndeterminate(true);
+ currentWaitDialog.getContentPane().add(progress, BorderLayout.SOUTH);
+ currentWaitDialog.setSize(200, 100);
+ currentWaitDialog.setLocationRelativeTo(null);
+ showWaitDialogLater();
+ }
+ }
+
+ private void showWaitDialogLater() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(true); // This is blocking.
+ }
+ }
+ });
+ }
+
+ public void updateWaitDialog(String s) {
+ if (currentWaitDialog != null) {
+ ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s);
+ Dimension prefSize = currentWaitDialog.getPreferredSize();
+ Dimension curSize = currentWaitDialog.getSize();
+ if (prefSize.width > curSize.width || prefSize.height > curSize.height) {
+ currentWaitDialog.setSize(Math.max(prefSize.width, curSize.width),
+ Math.max(prefSize.height, curSize.height));
+ currentWaitDialog.invalidate();
+ }
+ }
+ }
+
+ public void hideWaitDialog() {
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ currentWaitDialog = null;
+ }
+ }
+
+ public void showMessageDialog(String s) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ JOptionPane.showMessageDialog(this, s);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public boolean showConfirmDialog(String title, String message) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ return JOptionPane.showConfirmDialog(this, title, message, JOptionPane.YES_NO_OPTION)
+ == JOptionPane.YES_OPTION;
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public String showInputDialog(String message) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ return JOptionPane.showInputDialog(message);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T showChoiceDialog(String title, String message, T[] choices) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ return (T)JOptionPane.showInputDialog(this,
+ title,
+ message,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ choices,
+ choices[0]);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public File showSaveDialog() {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ if (jfc == null) {
+ jfc = new JFileChooser();
+ }
+
+ int ret = jfc.showSaveDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION) {
+ return jfc.getSelectedFile();
+ } else {
+ return null;
+ }
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public File[] showOpenDialog(boolean multi) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ if (jfc == null) {
+ jfc = new JFileChooser();
+ }
+
+ jfc.setMultiSelectionEnabled(multi);
+ int ret = jfc.showOpenDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION) {
+ return jfc.getSelectedFiles();
+ } else {
+ return null;
+ }
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ private class ClientListCellRenderer extends DefaultListCellRenderer {
+
+ @Override
+ public Component getListCellRendererComponent(JList<?> list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ ClientData cd = ((Client) value).getClientData();
+ String s = cd.getClientDescription() + " (pid " + cd.getPid() + ")";
+ return super.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus);
+ }
+ }
+}