Merge "AAPT2: Be more strict parsing references"
diff --git a/api/current.txt b/api/current.txt
index dabc084..bb751cc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -933,6 +933,7 @@
field public static final int port = 16842793; // 0x1010029
field public static final int positiveButtonText = 16843253; // 0x10101f5
field public static final int preferenceCategoryStyle = 16842892; // 0x101008c
+ field public static final int preferenceFragmentStyle = 16844039; // 0x1010507
field public static final int preferenceInformationStyle = 16842893; // 0x101008d
field public static final int preferenceLayoutChild = 16842900; // 0x1010094
field public static final int preferenceScreenStyle = 16842891; // 0x101008b
@@ -1710,11 +1711,13 @@
field public static final int icon = 16908294; // 0x1020006
field public static final int icon1 = 16908295; // 0x1020007
field public static final int icon2 = 16908296; // 0x1020008
+ field public static final int icon_frame = 16908350; // 0x102003e
field public static final int input = 16908297; // 0x1020009
field public static final int inputArea = 16908318; // 0x102001e
field public static final int inputExtractEditText = 16908325; // 0x1020025
field public static final int keyboardView = 16908326; // 0x1020026
field public static final int list = 16908298; // 0x102000a
+ field public static final int list_container = 16908351; // 0x102003f
field public static final int mask = 16908334; // 0x102002e
field public static final int message = 16908299; // 0x102000b
field public static final int navigationBarBackground = 16908336; // 0x1020030
@@ -1734,6 +1737,7 @@
field public static final int stopSelectingText = 16908329; // 0x1020029
field public static final int summary = 16908304; // 0x1020010
field public static final int switchInputMethod = 16908324; // 0x1020024
+ field public static final int switch_widget = 16908352; // 0x1020040
field public static final int tabcontent = 16908305; // 0x1020011
field public static final int tabhost = 16908306; // 0x1020012
field public static final int tabs = 16908307; // 0x1020013
@@ -5707,6 +5711,7 @@
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
+ method public java.lang.String getDeviceOwnerLockScreenInfo();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -5759,6 +5764,7 @@
method public void setCameraDisabled(android.content.ComponentName, boolean);
method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+ method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -9914,6 +9920,7 @@
public static class Resources.NotFoundException extends java.lang.RuntimeException {
ctor public Resources.NotFoundException();
ctor public Resources.NotFoundException(java.lang.String);
+ ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception);
}
public final class Resources.Theme {
diff --git a/api/system-current.txt b/api/system-current.txt
index 19d9aa2..65ecdc1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1025,6 +1025,7 @@
field public static final int port = 16842793; // 0x1010029
field public static final int positiveButtonText = 16843253; // 0x10101f5
field public static final int preferenceCategoryStyle = 16842892; // 0x101008c
+ field public static final int preferenceFragmentStyle = 16844039; // 0x1010507
field public static final int preferenceInformationStyle = 16842893; // 0x101008d
field public static final int preferenceLayoutChild = 16842900; // 0x1010094
field public static final int preferenceScreenStyle = 16842891; // 0x101008b
@@ -1806,11 +1807,13 @@
field public static final int icon = 16908294; // 0x1020006
field public static final int icon1 = 16908295; // 0x1020007
field public static final int icon2 = 16908296; // 0x1020008
+ field public static final int icon_frame = 16908350; // 0x102003e
field public static final int input = 16908297; // 0x1020009
field public static final int inputArea = 16908318; // 0x102001e
field public static final int inputExtractEditText = 16908325; // 0x1020025
field public static final int keyboardView = 16908326; // 0x1020026
field public static final int list = 16908298; // 0x102000a
+ field public static final int list_container = 16908351; // 0x102003f
field public static final int mask = 16908334; // 0x102002e
field public static final int message = 16908299; // 0x102000b
field public static final int navigationBarBackground = 16908336; // 0x1020030
@@ -1830,6 +1833,7 @@
field public static final int stopSelectingText = 16908329; // 0x1020029
field public static final int summary = 16908304; // 0x1020010
field public static final int switchInputMethod = 16908324; // 0x1020024
+ field public static final int switch_widget = 16908352; // 0x1020040
field public static final int tabcontent = 16908305; // 0x1020011
field public static final int tabhost = 16908306; // 0x1020012
field public static final int tabs = 16908307; // 0x1020013
@@ -5833,6 +5837,7 @@
method public deprecated java.lang.String getDeviceInitializerApp();
method public deprecated android.content.ComponentName getDeviceInitializerComponent();
method public java.lang.String getDeviceOwner();
+ method public java.lang.String getDeviceOwnerLockScreenInfo();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -5891,6 +5896,7 @@
method public void setCameraDisabled(android.content.ComponentName, boolean);
method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+ method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -10255,6 +10261,7 @@
public static class Resources.NotFoundException extends java.lang.RuntimeException {
ctor public Resources.NotFoundException();
ctor public Resources.NotFoundException(java.lang.String);
+ ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception);
}
public final class Resources.Theme {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 351064a..65de4ca 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1558,6 +1558,16 @@
readFromParcel(source);
}
+ /**
+ * Resets this info state to the initial state.
+ * @hide
+ */
+ public void reset() {
+ taskWidth = 0;
+ taskHeight = 0;
+ screenOrientation = 0;
+ }
+
/** @hide */
public void saveToXml(XmlSerializer out) throws IOException {
out.attribute(null, ATTR_TASK_WIDTH, Integer.toString(taskWidth));
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 65c63f6..4449e4f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2611,6 +2611,14 @@
return true;
}
+ case UPDATE_DEVICE_OWNER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String packageName = data.readString();
+ updateDeviceOwner(packageName);
+ reply.writeNoException();
+ return true;
+ }
+
case GET_PACKAGE_PROCESS_STATE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String pkg = data.readString();
@@ -6155,6 +6163,18 @@
}
@Override
+ public void updateDeviceOwner(String packageName) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ mRemote.transact(UPDATE_DEVICE_OWNER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
public int getPackageProcessState(String packageName, String callingPackage)
throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index cf2452b..b69a480 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -518,6 +518,7 @@
public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake)
throws RemoteException;
public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException;
+ public void updateDeviceOwner(String packageName) throws RemoteException;
public int getPackageProcessState(String packageName, String callingPackage)
throws RemoteException;
@@ -880,6 +881,7 @@
int NOTE_ALARM_FINISH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+292;
int GET_PACKAGE_PROCESS_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+293;
int SHOW_LOCK_TASK_ESCAPE_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+294;
+ int UPDATE_DEVICE_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+295;
int KEYGUARD_GOING_AWAY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+296;
int REGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+297;
int UNREGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+298;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b89c5a6..faed7a0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -122,7 +122,10 @@
* Provisioning adds a managed profile and sets the MDM as the profile owner who has full
* control over the profile.
*
- * In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this intent must contain the
+ * <p>It is possible to check if provisioning is allowed or not by querying the method
+ * {@link #isProvisioningAllowed(String)}.
+ *
+ * <p>In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this intent must contain the
* extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}.
* As of {@link android.os.Build.VERSION_CODES#M}, it should contain the extra
* {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, although specifying only
@@ -157,9 +160,8 @@
* been completed. Use {@link #isProvisioningAllowed(String)} to check if provisioning is
* allowed.
*
- * This intent should contain the extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME},
- * although specifying only {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is also
- * supported.
+ * <p>This intent should contain the extra
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}.
*
* <p> If provisioning fails, the device returns to its previous state.
*
@@ -185,10 +187,10 @@
* employee or client.
*
* <p> An intent with this action can be sent only on an unprovisioned device.
- * It is possible to check if the device is provisioned or not by looking at
- * {@link android.provider.Settings.Global#DEVICE_PROVISIONED}
+ * It is possible to check if provisioning is allowed or not by querying the method
+ * {@link #isProvisioningAllowed(String)}.
*
- * The intent contains the following extras:
+ * <p>The intent contains the following extras:
* <ul>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
* <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
@@ -212,6 +214,53 @@
= "android.app.action.PROVISION_MANAGED_DEVICE";
/**
+ * Activity action: Starts the provisioning flow which sets up a managed device.
+ * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
+ *
+ * <p>NOTE: This is only supported on split system user devices, and puts the device into a
+ * management state that is distinct from that reached by
+ * {@link #ACTION_PROVISION_MANAGED_DEVICE} - specifically the device owner runs on the system
+ * user, and only has control over device-wide policies, not individual users and their data.
+ * The primary benefit is that multiple non-system users are supported when provisioning using
+ * this form of device management.
+ *
+ * <p> During device owner provisioning a device admin app is set as the owner of the device.
+ * A device owner has full control over the device. The device owner can not be modified by the
+ * user.
+ *
+ * <p> A typical use case would be a device that is owned by a company, but used by either an
+ * employee or client.
+ *
+ * <p> An intent with this action can be sent only on an unprovisioned device.
+ * It is possible to check if provisioning is allowed or not by querying the method
+ * {@link #isProvisioningAllowed(String)}.
+ *
+ * <p>The intent contains the following extras:
+ * <ul>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+ * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+ * </ul>
+ *
+ * <p> When device owner provisioning has completed, an intent of the type
+ * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
+ * device owner.
+ *
+ * <p> If provisioning fails, the device is factory reset.
+ *
+ * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
+ * of the provisioning flow was successful, although this doesn't guarantee the full flow will
+ * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
+ * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
+ = "android.app.action.PROVISION_MANAGED_SHAREABLE_DEVICE";
+
+ /**
* A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
* allows a mobile device management application or NFC programmer application which starts
* managed provisioning to pass data to the management application instance after provisioning.
@@ -595,7 +644,7 @@
* extra field. This will invoke a UI to bring the user through adding the profile owner admin
* to remotely control restrictions on the user.
*
- * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the
+ * <p>The intent must be invoked via {@link Activity#startActivityForResult()} to receive the
* result of whether or not the user approved the action. If approved, the result will
* be {@link Activity#RESULT_OK} and the component will be set as an active admin as well
* as a profile owner.
@@ -1662,7 +1711,16 @@
* Force a new device unlock password (the password needed to access the
* entire device, not for individual accounts) on the user. This takes
* effect immediately.
- * The given password must be sufficient for the
+ *
+ * <p>Calling this from a managed profile that shares the password with the owner profile
+ * will throw a security exception.
+ *
+ * <p><em>Note: This API has been limited as of {@link android.os.Build.VERSION_CODES#N} for
+ * device admins that are not device owner and not profile owner.
+ * The password can now only be changed if there is currently no password set. Device owner
+ * and profile owner can still do this.</em>
+ *
+ * <p>The given password must be sufficient for the
* current password quality and length constraints as returned by
* {@link #getPasswordQuality(ComponentName)} and
* {@link #getPasswordMinimumLength(ComponentName)}; if it does not meet
@@ -1672,19 +1730,20 @@
* the currently active quality will be increased to match.
*
* <p>Calling with a null or empty password will clear any existing PIN,
- * pattern or password if the current password constraints allow it.
+ * pattern or password if the current password constraints allow it. <em>Note: This will not
+ * work in {@link android.os.Build.VERSION_CODES#N} and later for device admins that are not
+ * device owner and not profile owner. Once set, the password cannot be changed to null or
+ * empty, except by device owner or profile owner.</em>
*
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
* this method; if it has not, a security exception will be thrown.
*
- * <p>Calling this from a managed profile will throw a security exception.
- *
* @param password The new password for the user. Null or empty clears the password.
* @param flags May be 0 or combination of {@link #RESET_PASSWORD_REQUIRE_ENTRY} and
* {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}.
* @return Returns true if the password was applied, or false if it is
- * not acceptable for the current constraints.
+ * not acceptable for the current constraints or if the user has not been decrypted yet.
*/
public boolean resetPassword(String password, int flags) {
if (mService != null) {
@@ -1792,7 +1851,7 @@
public void wipeData(int flags) {
if (mService != null) {
try {
- mService.wipeData(flags, myUserId());
+ mService.wipeData(flags);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2668,14 +2727,14 @@
* does *not* check weather the device owner is actually running on the current user.
*/
public boolean isDeviceOwnerApp(String packageName) {
- if (mService != null) {
- try {
- return mService.isDeviceOwnerPackage(packageName);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed talking with device policy service", e);
- }
+ if (packageName == null) {
+ return false;
}
- return false;
+ final ComponentName deviceOwner = getDeviceOwnerComponent();
+ if (deviceOwner == null) {
+ return false;
+ }
+ return packageName.equals(deviceOwner.getPackageName());
}
/**
@@ -2869,9 +2928,6 @@
*/
public boolean setProfileOwner(@NonNull ComponentName admin, @Deprecated String ownerName,
int userHandle) throws IllegalArgumentException {
- if (admin == null) {
- throw new NullPointerException("admin cannot be null");
- }
if (mService != null) {
try {
if (ownerName == null) {
@@ -2887,6 +2943,42 @@
}
/**
+ * Sets the device owner information to be shown on the lock screen.
+ *
+ * <p>If the device owner information is {@code null} or empty then the device owner info is
+ * cleared and the user owner info is shown on the lock screen if it is set.
+ *
+ * @param admin The name of the admin component to check.
+ * @param info Device owner information which will be displayed instead of the user
+ * owner info.
+ * @return Whether the device owner information has been set.
+ */
+ public boolean setDeviceOwnerLockScreenInfo(@NonNull ComponentName admin, String info) {
+ if (mService != null) {
+ try {
+ return mService.setDeviceOwnerLockScreenInfo(admin, info);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed talking with device policy service", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return The device owner information. If it is not set returns {@code null}.
+ */
+ public String getDeviceOwnerLockScreenInfo() {
+ if (mService != null) {
+ try {
+ return mService.getDeviceOwnerLockScreenInfo();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed talking with device policy service", re);
+ }
+ }
+ return null;
+ }
+
+ /**
* Sets the enabled state of the profile. A profile should be enabled only once it is ready to
* be used. Only the profile owner can call this.
*
@@ -2996,7 +3088,7 @@
/**
* @hide
- * @param userId The user for whom to fetch the profile owner name, if any.
+ * @param user The user for whom to fetch the profile owner name, if any.
* @return the human readable name of the organisation associated with this profile owner or
* null if one is not set.
* @throws IllegalArgumentException if the userId is invalid.
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 5a46cd5..4270e16 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -80,10 +80,4 @@
* This method always returns a new {@link Bundle}.
*/
public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
-
- /**
- * @return true if a package is a device admin (possibly DO or PO) running on
- * user {@code userId}.
- */
- public abstract boolean isDeviceAdminPackage(int userId, String packageName);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 95a22ef..7601cf2 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -81,7 +81,7 @@
void lockNow();
- void wipeData(int flags, int userHandle);
+ void wipeData(int flags);
ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
ComponentName getGlobalProxyAdmin(int userHandle);
@@ -114,7 +114,6 @@
void reportSuccessfulPasswordAttempt(int userHandle);
boolean setDeviceOwner(in ComponentName who, String ownerName, int userId);
- boolean isDeviceOwnerPackage(String packageName);
ComponentName getDeviceOwner();
String getDeviceOwnerName();
void clearDeviceOwner(String packageName);
@@ -127,6 +126,9 @@
void clearProfileOwner(in ComponentName who);
boolean hasUserSetupCompleted();
+ boolean setDeviceOwnerLockScreenInfo(in ComponentName who, String deviceOwnerInfo);
+ String getDeviceOwnerLockScreenInfo();
+
boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
void uninstallCaCerts(in ComponentName admin, in String[] aliases);
void enforceCanManageCaCerts(in ComponentName admin);
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 9c880d3..52ec4cc 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -479,6 +479,14 @@
public static final int PRIVATE_FLAG_ENCRYPTION_AWARE = 1 << 6;
/**
+ * Value for {@link #privateFlags}: set to {@code true} if the application
+ * is AutoPlay.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_AUTOPLAY = 1<<6;
+
+ /**
* Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
* {@hide}
*/
@@ -1049,6 +1057,13 @@
/**
* @hide
*/
+ public boolean isAutoPlayApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_AUTOPLAY) != 0;
+ }
+
+ /**
+ * @hide
+ */
@Override protected ApplicationInfo getApplicationInfo() {
return this;
}
diff --git a/core/java/android/content/pm/AppsQueryHelper.java b/core/java/android/content/pm/AppsQueryHelper.java
index a5a8e3f..084bc18 100644
--- a/core/java/android/content/pm/AppsQueryHelper.java
+++ b/core/java/android/content/pm/AppsQueryHelper.java
@@ -18,13 +18,11 @@
import android.Manifest;
import android.app.AppGlobals;
-import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethod;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,36 +42,37 @@
public static int GET_NON_LAUNCHABLE_APPS = 1;
/**
- * Return apps with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission
+ * Return apps with {@link Manifest.permission#INTERACT_ACROSS_USERS} permission
*/
public static int GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM = 1 << 1;
/**
- * Return all input methods that are marked as default.
- * <p>When this flag is set, {@code user} specified in
- * {@link #queryApps(int, boolean, UserHandle)} must be
- * {@link UserHandle#myUserId user of the current process}.
+ * Return all input methods available for the current user.
*/
- public static int GET_DEFAULT_IMES = 1 << 2;
+ public static int GET_IMES = 1 << 2;
- private final Context mContext;
+ private final IPackageManager mPackageManager;
private List<ApplicationInfo> mAllApps;
- public AppsQueryHelper(Context context) {
- mContext = context;
+ public AppsQueryHelper(IPackageManager packageManager) {
+ mPackageManager = packageManager;
+ }
+
+ public AppsQueryHelper() {
+ this(AppGlobals.getPackageManager());
}
/**
* Return a List of all packages that satisfy a specified criteria.
* @param flags search flags. Use any combination of {@link #GET_NON_LAUNCHABLE_APPS},
- * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_DEFAULT_IMES}.
+ * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_IMES}.
* @param systemAppsOnly if true, only system apps will be returned
* @param user user, whose apps are queried
*/
public List<String> queryApps(int flags, boolean systemAppsOnly, UserHandle user) {
boolean nonLaunchableApps = (flags & GET_NON_LAUNCHABLE_APPS) > 0;
boolean interactAcrossUsers = (flags & GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM) > 0;
- boolean defaultImes = (flags & GET_DEFAULT_IMES) > 0;
+ boolean imes = (flags & GET_IMES) > 0;
if (mAllApps == null) {
mAllApps = getAllApps(user.getIdentifier());
}
@@ -113,7 +112,6 @@
}
}
}
-
if (interactAcrossUsers) {
final List<PackageInfo> packagesHoldingPermissions = getPackagesHoldingPermission(
Manifest.permission.INTERACT_ACROSS_USERS, user.getIdentifier());
@@ -129,29 +127,18 @@
}
}
- if (defaultImes) {
- if (UserHandle.myUserId() != user.getIdentifier()) {
- throw new IllegalArgumentException("Specified user handle " + user
- + " is not a user of the current process.");
- }
- List<InputMethodInfo> imis = getInputMethodList();
- int imisSize = imis.size();
- ArraySet<String> defaultImePackages = new ArraySet<>();
- for (int i = 0; i < imisSize; i++) {
- InputMethodInfo imi = imis.get(i);
- if (imi.isDefault(mContext)) {
- defaultImePackages.add(imi.getPackageName());
- }
- }
- final int allAppsSize = mAllApps.size();
- for (int i = 0; i < allAppsSize; i++) {
- final ApplicationInfo appInfo = mAllApps.get(i);
- if (systemAppsOnly && !appInfo.isSystemApp()) {
+ if (imes) {
+ final List<ResolveInfo> resolveInfos = queryIntentServicesAsUser(
+ new Intent(InputMethod.SERVICE_INTERFACE), user.getIdentifier());
+ final int resolveInfosSize = resolveInfos.size();
+
+ for (int i = 0; i < resolveInfosSize; i++) {
+ ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+ if (systemAppsOnly && !serviceInfo.applicationInfo.isSystemApp()) {
continue;
}
- final String packageName = appInfo.packageName;
- if (defaultImePackages.contains(packageName)) {
- result.add(packageName);
+ if (!result.contains(serviceInfo.packageName)) {
+ result.add(serviceInfo.packageName);
}
}
}
@@ -163,9 +150,8 @@
@SuppressWarnings("unchecked")
protected List<ApplicationInfo> getAllApps(int userId) {
try {
- return AppGlobals.getPackageManager().getInstalledApplications(
- PackageManager.GET_UNINSTALLED_PACKAGES
- | PackageManager.GET_DISABLED_COMPONENTS, userId).getList();
+ return mPackageManager.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS, userId).getList();
} catch (RemoteException e) {
throw new IllegalStateException("Package manager has died", e);
}
@@ -173,17 +159,22 @@
@VisibleForTesting
protected List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int userId) {
- return mContext.getPackageManager()
- .queryIntentActivitiesAsUser(intent, PackageManager.GET_DISABLED_COMPONENTS
- | PackageManager.GET_UNINSTALLED_PACKAGES, userId);
+ try {
+ return mPackageManager.queryIntentActivities(intent, null,
+ PackageManager.GET_DISABLED_COMPONENTS
+ | PackageManager.GET_UNINSTALLED_PACKAGES,
+ userId);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Package manager has died", e);
+ }
}
@VisibleForTesting
- @SuppressWarnings("unchecked")
- protected List<PackageInfo> getPackagesHoldingPermission(String perm, int userId) {
+ protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int userId) {
try {
- return AppGlobals.getPackageManager().getPackagesHoldingPermissions(new String[]{perm},
- 0, userId).getList();
+ return mPackageManager.queryIntentServices(intent, null,
+ PackageManager.GET_META_DATA
+ | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
} catch (RemoteException e) {
throw new IllegalStateException("Package manager has died", e);
}
@@ -191,9 +182,13 @@
@VisibleForTesting
@SuppressWarnings("unchecked")
- protected List<InputMethodInfo> getInputMethodList() {
- InputMethodManager imm = (InputMethodManager)
- mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
- return imm.getInputMethodList();
+ protected List<PackageInfo> getPackagesHoldingPermission(String perm, int userId) {
+ try {
+ return mPackageManager.getPackagesHoldingPermissions(new String[]{perm}, 0,
+ userId).getList();
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Package manager has died", e);
+ }
}
+
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 0606e35..7b3dde4 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -48,7 +48,6 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Trace;
-import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LocaleList;
@@ -68,7 +67,6 @@
import java.io.IOException;
import java.io.InputStream;
-import java.lang.ref.WeakReference;
import java.util.Locale;
/**
@@ -120,9 +118,6 @@
private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>>
sPreloadedColorStateLists = new LongSparseArray<>();
- private static final String CACHE_NOT_THEMED = "";
- private static final String CACHE_NULL_THEME = "null_theme";
-
// Pool of TypedArrays targeted to this Resources object.
final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
@@ -130,10 +125,11 @@
static Resources mSystem = null;
private static boolean sPreloaded;
- private static int sPreloadedDensity;
+
+ /** Lock object used to protect access to caches and configuration. */
+ private final Object mAccessLock = new Object();
// These are protected by mAccessLock.
- private final Object mAccessLock = new Object();
private final Configuration mTmpConfig = new Configuration();
private final DrawableCache mDrawableCache = new DrawableCache(this);
private final DrawableCache mColorDrawableCache = new DrawableCache(this);
@@ -147,7 +143,12 @@
/** Used to inflate drawable objects from XML. */
private DrawableInflater mDrawableInflater;
+ /** Lock object used to protect access to {@link #mTmpValue}. */
+ private final Object mTmpValueLock = new Object();
+
+ /** Single-item pool used to minimize TypedValue allocations. */
private TypedValue mTmpValue = new TypedValue();
+
private boolean mPreloading;
private int mLastCachedXmlBlockIndex = -1;
@@ -249,6 +250,10 @@
public NotFoundException(String name) {
super(name);
}
+
+ public NotFoundException(String name, Exception cause) {
+ super(name, cause);
+ }
}
/**
@@ -621,18 +626,15 @@
* @see #getDimensionPixelSize
*/
public float getDimension(@DimenRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(value.data, mMetrics);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -656,19 +658,15 @@
* @see #getDimensionPixelSize
*/
public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelOffset(
- value.data, mMetrics);
+ return TypedValue.complexToDimensionPixelOffset(value.data, mMetrics);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -693,19 +691,15 @@
* @see #getDimensionPixelOffset
*/
public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(
- value.data, mMetrics);
+ return TypedValue.complexToDimensionPixelSize(value.data, mMetrics);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -727,18 +721,15 @@
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*/
public float getFraction(@FractionRes int id, int base, int pbase) {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_FRACTION) {
return TypedValue.complexToFraction(value.data, base, pbase);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -801,24 +792,14 @@
* not exist.
*/
@Nullable
- public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
- mTmpValue = null;
- }
- getValue(id, value, true);
+ public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
+ throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
+ return loadDrawable(value, id, theme);
+ } finally {
+ releaseTempTypedValue(value);
}
- final Drawable res = loadDrawable(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
- }
- return res;
}
/**
@@ -849,7 +830,8 @@
*/
@Deprecated
@Nullable
- public Drawable getDrawableForDensity(@DrawableRes int id, int density) throws NotFoundException {
+ public Drawable getDrawableForDensity(@DrawableRes int id, int density)
+ throws NotFoundException {
return getDrawableForDensity(id, density, null);
}
@@ -869,14 +851,8 @@
*/
@Nullable
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
- mTmpValue = null;
- }
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
getValueForDensity(id, density, value, true);
/*
@@ -893,15 +869,11 @@
value.density = (value.density * mMetrics.densityDpi) / density;
}
}
- }
- final Drawable res = loadDrawable(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
+ return loadDrawable(value, id, theme);
+ } finally {
+ releaseTempTypedValue(value);
}
- return res;
}
/**
@@ -963,33 +935,21 @@
*/
@ColorInt
public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
- mTmpValue = value;
return value.data;
} else if (value.type != TypedValue.TYPE_STRING) {
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
}
- mTmpValue = null;
- }
- final ColorStateList csl = loadColorStateList(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
+ final ColorStateList csl = loadColorStateList(value, id, theme);
+ return csl.getDefaultColor();
+ } finally {
+ releaseTempTypedValue(value);
}
-
- return csl.getDefaultColor();
}
/**
@@ -1043,25 +1003,12 @@
@Nullable
public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
- mTmpValue = null;
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
+ return loadColorStateList(value, id, theme);
+ } finally {
+ releaseTempTypedValue(value);
}
-
- final ColorStateList res = loadColorStateList(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
- }
-
- return res;
}
/**
@@ -1078,19 +1025,16 @@
* @return Returns the boolean value contained in the resource.
*/
public boolean getBoolean(@BoolRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type >= TypedValue.TYPE_FIRST_INT
- && value.type <= TypedValue.TYPE_LAST_INT) {
+ && value.type <= TypedValue.TYPE_LAST_INT) {
return value.data != 0;
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -1106,19 +1050,16 @@
* @return Returns the integer value contained in the resource.
*/
public int getInteger(@IntegerRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type >= TypedValue.TYPE_FIRST_INT
- && value.type <= TypedValue.TYPE_LAST_INT) {
+ && value.type <= TypedValue.TYPE_LAST_INT) {
return value.data;
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -1136,17 +1077,15 @@
* @hide Pending API council approval.
*/
public float getFloat(int id) {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_FLOAT) {
return value.getFloat();
}
- throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -1238,22 +1177,60 @@
*
*/
public InputStream openRawResource(@RawRes int id) throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ return openRawResource(id, value);
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Returns a TypedValue populated with data for the specified resource ID
+ * that's suitable for temporary use. The obtained TypedValue should be
+ * released using {@link #releaseTempTypedValue(TypedValue)}.
+ *
+ * @param id the resource ID for which data should be obtained
+ * @return a populated typed value suitable for temporary use
+ */
+ private TypedValue obtainTempTypedValue(@AnyRes int id) {
+ final TypedValue value = obtainTempTypedValue();
+ getValue(id, value, true);
+ return value;
+ }
+
+ /**
+ * Returns a TypedValue suitable for temporary use. The obtained TypedValue
+ * should be released using {@link #releaseTempTypedValue(TypedValue)}.
+ *
+ * @return a typed value suitable for temporary use
+ */
+ private TypedValue obtainTempTypedValue() {
+ TypedValue tmpValue = null;
+ synchronized (mTmpValueLock) {
+ if (mTmpValue != null) {
+ tmpValue = mTmpValue;
mTmpValue = null;
}
}
- InputStream res = openRawResource(id, value);
- synchronized (mAccessLock) {
+ if (tmpValue == null) {
+ return new TypedValue();
+ }
+ return tmpValue;
+ }
+
+ /**
+ * Returns a TypedValue to the pool. After calling this method, the
+ * specified TypedValue should no longer be accessed.
+ *
+ * @param value the typed value to return to the pool
+ */
+ private void releaseTempTypedValue(TypedValue value) {
+ synchronized (mTmpValueLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
- return res;
}
/**
@@ -1307,32 +1284,14 @@
*/
public AssetFileDescriptor openRawResourceFd(@RawRes int id)
throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
- mTmpValue = null;
- }
- getValue(id, value, true);
- }
+ final TypedValue value = obtainTempTypedValue(id);
try {
- return mAssets.openNonAssetFd(
- value.assetCookie, value.string.toString());
+ return mAssets.openNonAssetFd(value.assetCookie, value.string.toString());
} catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + value.string.toString()
- + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
+ throw new NotFoundException("File " + value.string.toString() + " from drawable "
+ + "resource ID #0x" + Integer.toHexString(id), e);
} finally {
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
- }
+ releaseTempTypedValue(value);
}
}
@@ -2015,8 +1974,8 @@
Build.VERSION.RESOURCES_SDK_INT);
if (DEBUG_CONFIG) {
- Slog.i(TAG, "**** Updating config of " + this + ": final config is " + mConfiguration
- + " final compat is " + mCompatibilityInfo);
+ Slog.i(TAG, "**** Updating config of " + this + ": final config is "
+ + mConfiguration + " final compat is " + mCompatibilityInfo);
}
mDrawableCache.onConfigurationChange(configChanges);
@@ -2402,8 +2361,7 @@
}
sPreloaded = true;
mPreloading = true;
- sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE;
- mConfiguration.densityDpi = sPreloadedDensity;
+ mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
updateConfiguration(null, null);
}
}
@@ -2740,19 +2698,16 @@
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 1fc69c0..73bb426 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -3182,8 +3182,8 @@
}
/**
- * Sets GPS processing method. It will store up to 32 characters
- * in JPEG EXIF header.
+ * Sets GPS processing method. The method will be stored in a UTF-8 string up to 31 bytes
+ * long, in the JPEG EXIF header.
*
* @param processing_method The processing method to get this location.
*/
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 35a1d96..f61892e 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -472,13 +472,13 @@
* <li>The maximum available resolution for RAW_SENSOR streams
* will match either the value in
* {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize} or
- * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</li>
+ * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.</li>
* <li>All DNG-related optional metadata entries are provided
* by the camera device.</li>
* </ul>
*
- * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3f566eb..67835a0 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -556,6 +556,10 @@
* Set a capture request field to a value. The field definitions can be
* found in {@link CaptureRequest}.
*
+ * <p>Setting a field to {@code null} will remove that field from the capture request.
+ * Unless the field is optional, removing it will likely produce an error from the camera
+ * device when the request is submitted.</p>
+ *
* @param key The metadata field to write.
* @param value The value to set the field to, which must be of a matching
* type to the key.
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index 25ef8b5..6729347 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -20,6 +20,9 @@
import com.android.org.conscrypt.TrustManagerImpl;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
@@ -36,7 +39,11 @@
*/
public class X509TrustManagerExtensions {
- final TrustManagerImpl mDelegate;
+ private final TrustManagerImpl mDelegate;
+ // Methods to use when mDelegate is not a TrustManagerImpl and duck typing is being used.
+ private final X509TrustManager mTrustManager;
+ private final Method mCheckServerTrusted;
+ private final Method mIsUserAddedCertificate;
/**
* Constructs a new X509TrustManagerExtensions wrapper.
@@ -47,10 +54,31 @@
public X509TrustManagerExtensions(X509TrustManager tm) throws IllegalArgumentException {
if (tm instanceof TrustManagerImpl) {
mDelegate = (TrustManagerImpl) tm;
- } else {
- mDelegate = null;
- throw new IllegalArgumentException("tm is an instance of " + tm.getClass().getName() +
- " which is not a supported type of X509TrustManager");
+ mTrustManager = null;
+ mCheckServerTrusted = null;
+ mIsUserAddedCertificate = null;
+ return;
+ }
+ // Use duck typing if possible.
+ mDelegate = null;
+ mTrustManager = tm;
+ // Check that the hostname aware checkServerTrusted is present.
+ try {
+ mCheckServerTrusted = tm.getClass().getMethod("checkServerTrusted",
+ X509Certificate[].class,
+ String.class,
+ String.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Required method"
+ + " checkServerTrusted(X509Certificate[], String, String, String) missing");
+ }
+ // Check that isUserAddedCertificate is present.
+ try {
+ mIsUserAddedCertificate = tm.getClass().getMethod("isUserAddedCertificate",
+ X509Certificate.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ "Required method isUserAddedCertificate(X509Certificate) missing");
}
}
@@ -66,7 +94,24 @@
*/
public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
String host) throws CertificateException {
- return mDelegate.checkServerTrusted(chain, authType, host);
+ if (mDelegate != null) {
+ return mDelegate.checkServerTrusted(chain, authType, host);
+ } else {
+ try {
+ return (List<X509Certificate>) mCheckServerTrusted.invoke(mTrustManager, chain,
+ authType, host);
+ } catch (IllegalAccessException e) {
+ throw new CertificateException("Failed to call checkServerTrusted", e);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof CertificateException) {
+ throw (CertificateException) e.getCause();
+ }
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new CertificateException("checkServerTrusted failed", e.getCause());
+ }
+ }
}
/**
@@ -80,7 +125,21 @@
* otherwise.
*/
public boolean isUserAddedCertificate(X509Certificate cert) {
- return mDelegate.isUserAddedCertificate(cert);
+ if (mDelegate != null) {
+ return mDelegate.isUserAddedCertificate(cert);
+ } else {
+ try {
+ return (Boolean) mIsUserAddedCertificate.invoke(mTrustManager, cert);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Failed to call isUserAddedCertificate", e);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ } else {
+ throw new RuntimeException("isUserAddedCertificate failed", e.getCause());
+ }
+ }
+ }
}
/**
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 5852f5f..f2aea08 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -624,6 +624,32 @@
}
/**
+ * {@hide}
+ * This will be the new name for writeFileDescriptor, for consistency.
+ **/
+ public final void writeRawFileDescriptor(FileDescriptor val) {
+ nativeWriteFileDescriptor(mNativePtr, val);
+ }
+
+ /**
+ * {@hide}
+ * Write an array of FileDescriptor objects into the Parcel.
+ *
+ * @param value The array of objects to be written.
+ */
+ public final void writeRawFileDescriptorArray(FileDescriptor[] value) {
+ if (value != null) {
+ int N = value.length;
+ writeInt(N);
+ for (int i=0; i<N; i++) {
+ writeRawFileDescriptor(value[i]);
+ }
+ } else {
+ writeInt(-1);
+ }
+ }
+
+ /**
* Write a byte value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
@@ -1700,6 +1726,41 @@
return nativeReadFileDescriptor(mNativePtr);
}
+ /**
+ * {@hide}
+ * Read and return a new array of FileDescriptors from the parcel.
+ * @return the FileDescriptor array, or null if the array is null.
+ **/
+ public final FileDescriptor[] createRawFileDescriptorArray() {
+ int N = readInt();
+ if (N < 0) {
+ return null;
+ }
+ FileDescriptor[] f = new FileDescriptor[N];
+ for (int i = 0; i < N; i++) {
+ f[i] = readRawFileDescriptor();
+ }
+ return f;
+ }
+
+ /**
+ * {@hide}
+ * Read an array of FileDescriptors from a parcel.
+ * The passed array must be exactly the length of the array in the parcel.
+ * @return the FileDescriptor array, or null if the array is null.
+ **/
+ public final void readRawFileDescriptorArray(FileDescriptor[] val) {
+ int N = readInt();
+ if (N == val.length) {
+ for (int i=0; i<N; i++) {
+ val[i] = readRawFileDescriptor();
+ }
+ } else {
+ throw new RuntimeException("bad array lengths");
+ }
+ }
+
+
/*package*/ static native FileDescriptor openFileDescriptor(String file,
int mode) throws FileNotFoundException;
/*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index db04c71..3e496b6 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -23,14 +23,15 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
import android.widget.ListView;
/**
@@ -179,6 +180,27 @@
}
@Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ TypedArray a = getActivity().obtainStyledAttributes(null,
+ com.android.internal.R.styleable.PreferenceFragment,
+ com.android.internal.R.attr.preferenceFragmentStyle,
+ 0);
+
+ ListView lv = (ListView) view.findViewById(android.R.id.list);
+ if (lv != null) {
+ Drawable divider =
+ a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider);
+ if (divider != null) {
+ lv.setDivider(divider);
+ }
+ }
+
+ a.recycle();
+ }
+
+ @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 9c3cefc..aa8674e 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -122,7 +122,7 @@
protected void onBindView(View view) {
super.onBindView(view);
- View checkableView = view.findViewById(com.android.internal.R.id.switchWidget);
+ View checkableView = view.findViewById(com.android.internal.R.id.switch_widget);
if (checkableView != null && checkableView instanceof Checkable) {
if (checkableView instanceof Switch) {
final Switch switchView = (Switch) checkableView;
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 90d30d6..2afbb99 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -60,7 +60,7 @@
private Margins mMinMargins;
private int mColorMode;
- private int mDuplexMode = DUPLEX_MODE_NONE;
+ private int mDuplexMode;
PrintAttributes() {
/* hide constructor */
@@ -403,7 +403,7 @@
mResolution = null;
mMinMargins = null;
mColorMode = 0;
- mDuplexMode = DUPLEX_MODE_NONE;
+ mDuplexMode = 0;
}
/**
@@ -1427,10 +1427,6 @@
/**
* Creates a new {@link PrintAttributes} instance.
- * <p>
- * If you do not specify a duplex mode, the default
- * {@link #DUPLEX_MODE_NONE} will be used.
- * </p>
*
* @return The new instance.
*/
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 503854e..8906f9b 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -41,7 +41,7 @@
private final List<CertificatesEntryRef> mCertificatesEntryRefs;
private Set<TrustAnchor> mAnchors;
private final Object mAnchorsLock = new Object();
- private X509TrustManager mTrustManager;
+ private NetworkSecurityTrustManager mTrustManager;
private final Object mTrustManagerLock = new Object();
private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
@@ -78,7 +78,7 @@
return mPins;
}
- public X509TrustManager getTrustManager() {
+ public NetworkSecurityTrustManager getTrustManager() {
synchronized(mTrustManagerLock) {
if (mTrustManager == null) {
mTrustManager = new NetworkSecurityTrustManager(this);
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index e69082d..7f5b3ca 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -71,9 +71,28 @@
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
- List<X509Certificate> trustedChain =
- mDelegate.checkServerTrusted(certs, authType, (String) null);
+ checkServerTrusted(certs, authType, null);
+ }
+
+ /**
+ * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
+ * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
+ * modify without modifying those callers.
+ */
+ public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
+ String host) throws CertificateException {
+ List<X509Certificate> trustedChain = mDelegate.checkServerTrusted(certs, authType, host);
checkPins(trustedChain);
+ return trustedChain;
+ }
+
+ /**
+ * Check if the provided certificate is a user added certificate authority.
+ * This is required by android.net.http.X509TrustManagerExtensions.
+ */
+ public boolean isUserAddedCertificate(X509Certificate cert) {
+ // TODO: Figure out the right way to handle this, and if it is still even used.
+ return false;
}
private void checkPins(List<X509Certificate> chain) throws CertificateException {
diff --git a/core/java/android/security/net/config/RootTrustManager.java b/core/java/android/security/net/config/RootTrustManager.java
index 1338b9f..b87bf1f 100644
--- a/core/java/android/security/net/config/RootTrustManager.java
+++ b/core/java/android/security/net/config/RootTrustManager.java
@@ -18,6 +18,7 @@
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.util.List;
import javax.net.ssl.X509TrustManager;
@@ -61,10 +62,24 @@
config.getTrustManager().checkServerTrusted(certs, authType);
}
- public void checkServerTrusted(X509Certificate[] certs, String authType, String hostname)
- throws CertificateException {
+ /**
+ * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
+ * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
+ * modify without modifying those callers.
+ */
+ public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
+ String hostname) throws CertificateException {
NetworkSecurityConfig config = mConfig.getConfigForHostname(hostname);
- config.getTrustManager().checkServerTrusted(certs, authType);
+ return config.getTrustManager().checkServerTrusted(certs, authType, hostname);
+ }
+
+ /**
+ * Check if the provided certificate is a user added certificate authority.
+ * This is required by android.net.http.X509TrustManagerExtensions.
+ */
+ public boolean isUserAddedCertificate(X509Certificate cert) {
+ // TODO: Figure out the right way to handle this, and if it is still even used.
+ return false;
}
@Override
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index cebb8f0..f099479 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -27,21 +27,21 @@
static final String LOGTAG = PathParser.class.getSimpleName();
/**
- * @param pathData The string representing a path, the same as "d" string in svg file.
+ * @param pathString The string representing a path, the same as "d" string in svg file.
* @return the generated Path object.
*/
- public static Path createPathFromPathData(String pathData) {
- Path path = new Path();
- PathDataNode[] nodes = createNodesFromPathData(pathData);
- if (nodes != null) {
- try {
- PathDataNode.nodesToPath(nodes, path);
- } catch (RuntimeException e) {
- throw new RuntimeException("Error in parsing " + pathData, e);
- }
- return path;
+ public static Path createPathFromPathData(String pathString) {
+ if (pathString == null) {
+ throw new IllegalArgumentException("Path string can not be null.");
}
- return null;
+ Path path = new Path();
+ boolean hasValidPathData = nParseStringForPath(path.mNativePath, pathString,
+ pathString.length());
+ if (!hasValidPathData) {
+ throw new IllegalArgumentException("Path string: " + pathString +
+ " does not contain valid path data");
+ }
+ return path;
}
/**
@@ -701,4 +701,7 @@
}
}
}
+
+ private static native boolean nParseStringForPath(long pathPtr, String pathString,
+ int stringLength);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 66b05a2..461506b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12968,10 +12968,6 @@
mPrivateFlags |= PFLAG_DIRTY;
- // Release any resources in-case we don't end up drawing again
- // as anything cached is no longer valid
- resetDisplayList();
-
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d80c6a3..fdea1ba 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1625,7 +1625,7 @@
if (mPendingConfiguration.seq != 0) {
if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: "
+ mPendingConfiguration);
- updateConfiguration(mPendingConfiguration, !mFirst);
+ updateConfiguration(new Configuration(mPendingConfiguration), !mFirst);
mPendingConfiguration.seq = 0;
}
@@ -1638,6 +1638,8 @@
final boolean stableInsetsChanged = !mPendingStableInsets.equals(
mAttachInfo.mStableInsets);
final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
+ final boolean surfaceSizeChanged = (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
if (contentInsetsChanged) {
mAttachInfo.mContentInsets.set(mPendingContentInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
@@ -1723,10 +1725,20 @@
mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.destroy();
}
- } else if (surfaceGenerationId != mSurface.getGenerationId() &&
- mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
+ } else if ((surfaceGenerationId != mSurface.getGenerationId()
+ || surfaceSizeChanged)
+ && mSurfaceHolder == null
+ && mAttachInfo.mHardwareRenderer != null) {
mFullRedrawNeeded = true;
try {
+ // Need to do updateSurface (which leads to CanvasContext::setSurface and
+ // re-create the EGLSurface) if either the Surface changed (as indicated by
+ // generation id), or WindowManager changed the surface size. The latter is
+ // because on some chips, changing the consumer side's BufferQueue size may
+ // not take effect immediately unless we create a new EGLSurface.
+ // Note that frame size change doesn't always imply surface size change (eg.
+ // drag resizing uses fullscreen surface), need to check surfaceSizeChanged
+ // flag from WindowManager.
mAttachInfo.mHardwareRenderer.updateSurface(mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 43d643e..c08e1b5 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -77,6 +77,11 @@
public static final int RELAYOUT_RES_DRAG_RESIZING = 0x8;
/**
+ * The window manager has changed the size of the surface from the last call.
+ */
+ public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x10;
+
+ /**
* Flag for relayout: the client will be later giving
* internal insets; as a result, the window will not impact other window
* layouts until the insets are given.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ee06806..122b83a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -443,30 +443,18 @@
if (DEBUG) {
Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence);
}
- boolean startInput = false;
+ final boolean startInput;
synchronized (mH) {
- if (mBindSequence == sequence) {
- if (false) {
- // XXX the server has already unbound!
- if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
- try {
- mCurMethod.finishInput();
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
- }
- }
- }
- clearBindingLocked();
-
- // If we were actively using the last input method, then
- // we would like to re-connect to the next input method.
- if (mServedView != null && mServedView.isFocused()) {
- mServedConnecting = true;
- }
- if (mActive) {
- startInput = true;
- }
+ if (mBindSequence != sequence) {
+ return;
}
+ clearBindingLocked();
+ // If we were actively using the last input method, then
+ // we would like to re-connect to the next input method.
+ if (mServedView != null && mServedView.isFocused()) {
+ mServedConnecting = true;
+ }
+ startInput = mActive;
}
if (startInput) {
startInputInner(null, 0, 0, 0);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2fabe33..2983053 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2705,11 +2705,22 @@
mIsShowingUp = false;
}
- private class SuggestionInfo {
+ private final class SuggestionInfo {
int suggestionStart, suggestionEnd; // range of actual suggestion within text
- SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
+
+ // the SuggestionSpan that this TextView represents
+ @Nullable
+ SuggestionSpan suggestionSpan;
+
int suggestionIndex; // the index of this suggestion inside suggestionSpan
- SpannableStringBuilder text = new SpannableStringBuilder();
+
+ @Nullable
+ final SpannableStringBuilder text = new SpannableStringBuilder();
+
+ void clear() {
+ suggestionSpan = null;
+ text.clear();
+ }
}
private class SuggestionAdapter extends BaseAdapter {
@@ -2863,9 +2874,11 @@
return Math.min(positionY, displayMetrics.heightPixels - height);
}
- @Override
- public void hide() {
- super.hide();
+ private void hideWithCleanUp() {
+ for (final SuggestionInfo info : mSuggestionInfos) {
+ info.clear();
+ }
+ hide();
}
private boolean updateSuggestions() {
@@ -3017,7 +3030,7 @@
}
mTextView.deleteText_internal(spanUnionStart, spanUnionEnd);
}
- hide();
+ hideWithCleanUp();
return;
}
@@ -3025,7 +3038,7 @@
final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan);
if (spanStart < 0 || spanEnd <= spanStart) {
// Span has been removed
- hide();
+ hideWithCleanUp();
return;
}
@@ -3100,7 +3113,7 @@
mTextView.setCursorPosition_internal(newCursorPosition, newCursorPosition);
}
- hide();
+ hideWithCleanUp();
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c54a574..b5d994d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3975,6 +3975,15 @@
}
((Editable) mText).append(text, start, end);
+
+ if (mAutoLinkMask != 0) {
+ boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
+ // Do not change the movement method for text that support text selection as it
+ // would prevent an arbitrary cursor displacement.
+ if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
+ setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ }
}
private void updateTextColors() {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 60ef4a4..889c7b3 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -126,6 +126,8 @@
private static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+ private static final String LOCK_SCREEN_DEVICE_OWNER_INFO = "lockscreen.device_owner_info";
+
private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
// Maximum allowed number of repeated or ordered characters in a sequence before we'll
@@ -578,6 +580,29 @@
}
/**
+ * Sets the device owner information. If the information is {@code null} or empty then the
+ * device owner info is cleared.
+ *
+ * @param info Device owner information which will be displayed instead of the user
+ * owner info.
+ */
+ public void setDeviceOwnerInfo(String info) {
+ if (info != null && info.isEmpty()) {
+ info = null;
+ }
+
+ setString(LOCK_SCREEN_DEVICE_OWNER_INFO, info, UserHandle.USER_SYSTEM);
+ }
+
+ public String getDeviceOwnerInfo() {
+ return getString(LOCK_SCREEN_DEVICE_OWNER_INFO, UserHandle.USER_SYSTEM);
+ }
+
+ public boolean isDeviceOwnerInfoEnabled() {
+ return getDeviceOwnerInfo() != null;
+ }
+
+ /**
* Compute the password quality from the given password string.
*/
static public int computePasswordQuality(String password) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ecee2b2..4d648ce 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -89,6 +89,7 @@
android_util_Binder.cpp \
android_util_EventLog.cpp \
android_util_Log.cpp \
+ android_util_PathParser.cpp \
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 4e93730..815d330 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -110,6 +110,7 @@
extern int register_android_content_AssetManager(JNIEnv* env);
extern int register_android_util_EventLog(JNIEnv* env);
extern int register_android_util_Log(JNIEnv* env);
+extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
extern int register_android_graphics_Canvas(JNIEnv* env);
@@ -1300,6 +1301,7 @@
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
+ REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_content_AssetManager),
REG_JNI(register_android_content_StringBlock),
REG_JNI(register_android_content_XmlBlock),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 703a9bd..a805b6d 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -1006,6 +1006,7 @@
// is disposed.
int dupFd = dup(blob.fd());
if (dupFd < 0) {
+ ALOGE("Error allocating dup fd. Error:%d", errno);
blob.release();
SkSafeUnref(ctable);
doThrowRE(env, "Could not allocate dup blob fd.");
diff --git a/core/jni/android_util_PathParser.cpp b/core/jni/android_util_PathParser.cpp
new file mode 100644
index 0000000..245aa0f
--- /dev/null
+++ b/core/jni/android_util_PathParser.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+
+#include <PathParser.h>
+#include <SkPath.h>
+
+#include <android/log.h>
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring inputPathStr,
+ jint strLength) {
+ const char* pathString = env->GetStringUTFChars(inputPathStr, NULL);
+ SkPath* skPath = reinterpret_cast<SkPath*>(skPathHandle);
+
+ android::uirenderer::PathParser::ParseResult result;
+ android::uirenderer::PathParser::parseStringForSkPath(skPath, &result, pathString, strLength);
+ env->ReleaseStringUTFChars(inputPathStr, pathString);
+ if (result.failureOccurred) {
+ ALOGE(result.failureMessage.c_str());
+ }
+ return !result.failureOccurred;
+}
+
+static const JNINativeMethod gMethods[] = {
+ {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath}
+};
+
+int register_android_util_PathParser(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/util/PathParser", gMethods, NELEM(gMethods));
+}
+};
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index f073c33..fc53a1a 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -24,18 +24,23 @@
android:background="@android:color/transparent"
android:layout_removeBorders="true">
- <ListView android:id="@android:id/list"
- style="?attr/preferenceFragmentListStyle"
+ <FrameLayout
+ android:id="@android:id/list_container"
android:layout_width="match_parent"
android:layout_height="0px"
- android:layout_weight="1"
- android:paddingTop="0dip"
- android:paddingBottom="@dimen/preference_fragment_padding_bottom"
- android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
- android:clipToPadding="false"
- android:drawSelectorOnTop="false"
- android:cacheColorHint="@android:color/transparent"
- android:scrollbarAlwaysDrawVerticalTrack="true" />
+ android:layout_weight="1">
+ <ListView android:id="@android:id/list"
+ style="?attr/preferenceFragmentListStyle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="0dip"
+ android:paddingBottom="@dimen/preference_fragment_padding_bottom"
+ android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
+ android:clipToPadding="false"
+ android:drawSelectorOnTop="false"
+ android:cacheColorHint="@android:color/transparent"
+ android:scrollbarAlwaysDrawVerticalTrack="true" />
+ </FrameLayout>
<TextView android:id="@android:id/empty"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml
index 62bdffd..e411c0e 100644
--- a/core/res/res/layout/preference_list_fragment_material.xml
+++ b/core/res/res/layout/preference_list_fragment_material.xml
@@ -24,18 +24,23 @@
android:background="@android:color/transparent"
android:layout_removeBorders="true">
- <ListView android:id="@android:id/list"
- style="?attr/preferenceFragmentListStyle"
+ <FrameLayout
+ android:id="@android:id/list_container"
android:layout_width="match_parent"
android:layout_height="0px"
- android:layout_weight="1"
- android:paddingTop="0dip"
- android:paddingBottom="@dimen/preference_fragment_padding_bottom"
- android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
- android:clipToPadding="false"
- android:drawSelectorOnTop="false"
- android:cacheColorHint="@android:color/transparent"
- android:scrollbarAlwaysDrawVerticalTrack="true" />
+ android:layout_weight="1">
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="0dip"
+ android:paddingBottom="@dimen/preference_fragment_padding_bottom"
+ style="?attr/preferenceFragmentListStyle"
+ android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
+ android:clipToPadding="false"
+ android:drawSelectorOnTop="false"
+ android:cacheColorHint="@android:color/transparent"
+ android:scrollbarAlwaysDrawVerticalTrack="true" />
+ </FrameLayout>
<TextView android:id="@android:id/empty"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/preference_widget_switch.xml b/core/res/res/layout/preference_widget_switch.xml
index 25e8aa6..80c572b 100644
--- a/core/res/res/layout/preference_widget_switch.xml
+++ b/core/res/res/layout/preference_widget_switch.xml
@@ -17,7 +17,7 @@
<!-- Layout used by SwitchPreference for the switch widget style. This is inflated
inside android.R.layout.preference. -->
<Switch xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+android:id/switchWidget"
+ android:id="@+android:id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index f900301..6f6d1b0 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -420,7 +420,7 @@
<string name="permdesc_useFingerprint" msgid="9165097460730684114">"指紋ハードウェアを認証に使用することをアプリに許可します"</string>
<string name="fingerprint_acquired_partial" msgid="735082772341716043">"指紋を一部しか検出できませんでした。もう一度お試しください。"</string>
<string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"指紋を処理できませんでした。もう一度お試しください。"</string>
- <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"指紋センサーに汚れがあります。汚れを落としてもう一度お試しください。"</string>
+ <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"指紋認証センサーに汚れがあります。汚れを落としてもう一度お試しください。"</string>
<string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"指の動きが速すぎました。もう一度お試しください。"</string>
<string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"指の動きが遅すぎました。もう一度お試しください。"</string>
<string-array name="fingerprint_acquired_vendor">
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d7dd3ec..50cf302 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7714,6 +7714,7 @@
<declare-styleable name="PreferenceFragment">
<!-- The layout for the PreferenceFragment. This should rarely need to be changed. -->
<attr name="layout" />
+ <attr name="divider" />
</declare-styleable>
<!-- Base attributes available to PreferenceActivity. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 76f7062..b87d9e2 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -411,7 +411,7 @@
<bool translatable="false" name="config_wifi_revert_country_code_on_cellular_loss">false</bool>
<!-- Boolean indicating whether or not wifi firmware debugging is enabled -->
- <bool translatable="false" name="config_wifi_enable_wifi_firmware_debugging">false</bool>
+ <bool translatable="false" name="config_wifi_enable_wifi_firmware_debugging">true</bool>
<!-- Integer specifying the basic autojoin parameters -->
<integer translatable="false" name="config_wifi_framework_5GHz_preference_boost_threshold">-65</integer>
@@ -1530,8 +1530,8 @@
<!-- If the time difference is greater than this threshold in milliseconds,
then update the time. -->
<integer name="config_ntpThreshold">5000</integer>
- <!-- Timeout to wait for NTP server response. -->
- <integer name="config_ntpTimeout">20000</integer>
+ <!-- Timeout to wait for NTP server response in milliseconds. -->
+ <integer name="config_ntpTimeout">5000</integer>
<!-- Default network policy warning threshold, in megabytes. -->
<integer name="config_networkPolicyDefaultWarning">2048</integer>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 695dafa..9d5e5ac 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -23,12 +23,14 @@
<item type="id" name="empty" />
<item type="id" name="hint" />
<item type="id" name="icon" />
+ <item type="id" name="icon_frame" />
<item type="id" name="icon_badge" />
<item type="id" name="icon1" />
<item type="id" name="icon2" />
<item type="id" name="input" />
<item type="id" name="left_icon" />
<item type="id" name="list" />
+ <item type="id" name="list_container" />
<item type="id" name="menu" />
<item type="id" name="message" />
<item type="id" name="primary" />
@@ -48,6 +50,7 @@
<item type="id" name="lock_screen" />
<item type="id" name="edit" />
<item type="id" name="widget_frame" />
+ <item type="id" name="switch_widget" />
<item type="id" name="button1" />
<item type="id" name="button2" />
<item type="id" name="button3" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 43a6acd..54e43c8 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2681,6 +2681,7 @@
<public type="attr" name="windowBackgroundFallback" />
<public type="attr" name="forceDeviceEncrypted" />
<public type="attr" name="encryptionAware" />
+ <public type="attr" name="preferenceFragmentStyle" />
<public type="style" name="Theme.Material.DayNight" />
<public type="style" name="Theme.Material.DayNight.DarkActionBar" />
@@ -2701,5 +2702,8 @@
<public type="style" name="Theme.Material.DayNight.DialogWhenLarge.DarkActionBar" />
<public type="id" name="accessibilityActionSetProgress" />
+ <public type="id" name="icon_frame" />
+ <public type="id" name="list_container" />
+ <public type="id" name="switch_widget" />
</resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 58640eb..4b2a451 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -40,6 +40,7 @@
<item name="layout">@layout/preference_list_fragment_material</item>
<item name="paddingStart">@dimen/preference_fragment_padding_side_material</item>
<item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item>
+ <item name="divider">?attr/listDivider</item>
</style>
<style name="PreferenceActivity.Material">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cda7faa..6820c25 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -162,7 +162,7 @@
<java-symbol type="id" name="submit_area" />
<java-symbol type="id" name="switch_new" />
<java-symbol type="id" name="switch_old" />
- <java-symbol type="id" name="switchWidget" />
+ <java-symbol type="id" name="switch_widget" />
<java-symbol type="id" name="text" />
<java-symbol type="id" name="time" />
<java-symbol type="id" name="time_current" />
diff --git a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
index 4c9b00a..dcf2c89 100644
--- a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
@@ -33,7 +33,7 @@
@Override
public void setUp() throws Exception {
super.setUp();
- mAppsQueryHelper = new AppsQueryHelperTestable(getContext());
+ mAppsQueryHelper = new AppsQueryHelperTestable();
}
public void testQueryAppsSystemAppsOnly() {
@@ -78,33 +78,20 @@
assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "sys_app3"), apps);
}
- public void testQueryAppsDefaultIme() {
- // Test query default system IMEs
- List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES,
+ public void testQueryAppsImes() {
+ // Test query system IMEs
+ List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_IMES,
true, UserHandle.of(UserHandle.myUserId()));
assertEqualsIgnoreOrder(Arrays.asList("sys_app1"), apps);
- // Test query default IMEs
- apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
+ // Test query IMEs
+ apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_IMES, false,
UserHandle.of(UserHandle.myUserId()));
assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "app4"), apps);
-
- // Test that GET_DEFAULT_IMES cannot be used with a user id different from current process
- try {
- mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
- UserHandle.of(UserHandle.USER_NULL));
- fail("queryApps must fail if wrong user was passed");
- } catch (IllegalArgumentException e) {
- // OK
- }
}
private class AppsQueryHelperTestable extends AppsQueryHelper {
- public AppsQueryHelperTestable(Context context) {
- super(context);
- }
-
@Override
protected List<ApplicationInfo> getAllApps(int userId) {
final ApplicationInfo ai1 = new ApplicationInfo();
@@ -145,18 +132,19 @@
}
@Override
- protected List<InputMethodInfo> getInputMethodList() {
+ protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int userId) {
final ResolveInfo sysApp1 = new ResolveInfo();
sysApp1.serviceInfo = new ServiceInfo();
sysApp1.serviceInfo.packageName = "sys_app1";
sysApp1.serviceInfo.name = "name";
- InputMethodInfo imi1 = new InputMethodInfo(sysApp1, false, null, null, 0, true);
+ sysApp1.serviceInfo.applicationInfo = new ApplicationInfo();
+ sysApp1.serviceInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
final ResolveInfo app4 = new ResolveInfo();
app4.serviceInfo = new ServiceInfo();
app4.serviceInfo.packageName = "app4";
app4.serviceInfo.name = "name";
- InputMethodInfo imi2 = new InputMethodInfo(app4, false, null, null, 0, true);
- return Arrays.asList(imi1, imi2);
+ app4.serviceInfo.applicationInfo = new ApplicationInfo();
+ return Arrays.asList(sysApp1, app4);
}
}
diff --git a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
index 87b3785..3ce45e9 100644
--- a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
+++ b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
@@ -61,11 +61,13 @@
final int expectedHighlightTextColor = tmpTp.getColor();
final float expectedHighlightTextSize = tmpTp.getTextSize();
- // Create and wait until SuggestionsPopupWindow is shown.
final EditText editText = (EditText) activity.findViewById(R.id.textview);
final Editor editor = editText.getEditorForTesting();
assertNotNull(editor);
- activity.runOnUiThread(new Runnable() {
+
+ // Request to show SuggestionsPopupWindow.
+ Runnable showSuggestionWindowRunner = new Runnable() {
+ @Override
public void run() {
SpannableStringBuilder ssb = new SpannableStringBuilder();
ssb.append(sampleText);
@@ -78,8 +80,7 @@
Selection.setSelection(editText.getText(), singleWordSpanStart, singleWordSpanEnd);
editText.onTextContextMenuItem(TextView.ID_REPLACE);
}
- });
- getInstrumentation().waitForIdleSync();
+ };
// In this test, the SuggestionsPopupWindow looks like
// abc def ghi
@@ -91,7 +92,8 @@
// | DELETE |
// -----------------
// *XX* means that XX is highlighted.
- activity.runOnUiThread(new Runnable() {
+ Runnable popupVaridator = new Runnable() {
+ @Override
public void run() {
Editor.SuggestionsPopupWindow popupWindow =
editor.getSuggestionsPopupWindowForTesting();
@@ -155,6 +157,25 @@
assertEquals(multiWordSpanEnd, spanned.getSpanEnd(taSpan[0]));
}
}
+ };
+
+ // Show the SuggestionWindow and verify the contents.
+ activity.runOnUiThread(showSuggestionWindowRunner);
+ getInstrumentation().waitForIdleSync();
+ activity.runOnUiThread(popupVaridator);
+
+ // Request to hide the SuggestionPopupWindow and wait until it is hidden.
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ editText.setText("");
+ }
});
+ getInstrumentation().waitForIdleSync();
+
+ // Show and verify the contents again.
+ activity.runOnUiThread(showSuggestionWindowRunner);
+ getInstrumentation().waitForIdleSync();
+ activity.runOnUiThread(popupVaridator);
}
}
diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd
index c1ccef0..21e3057 100644
--- a/docs/html/guide/topics/manifest/uses-feature-element.jd
+++ b/docs/html/guide/topics/manifest/uses-feature-element.jd
@@ -562,10 +562,11 @@
<tr>
<td rowspan="6">Camera</td>
<td><code>android.hardware.camera</code></td>
- <td>The application uses the device's camera. If the device supports
- multiple cameras, the application uses the camera that facing
- away from the screen.</td>
- <td></td>
+ <td>The application uses the device's back-facing (main) camera.</td>
+ <td>Devices with only a front-facing camera do not list this feature, so the
+ <code>android.hardware.camera.any</code> feature should be
+ used instead if a camera facing any direction is acceptable for the
+ application.</td>
</tr>
<tr>
<td><code>android.hardware.camera.autofocus</code></td>
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 39d13df..a50c945 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -913,16 +913,26 @@
protected void onBoundsChange(Rect bounds) {}
/**
- * Return the intrinsic width of the underlying drawable object. Returns
- * -1 if it has no intrinsic width, such as with a solid color.
+ * Returns the drawable's intrinsic width.
+ * <p>
+ * Intrinsic width is the width at which the drawable would like to be laid
+ * out, including any inherent padding. If the drawable has no intrinsic
+ * width, such as a solid color, this method returns -1.
+ *
+ * @return the intrinsic width, or -1 if no intrinsic width
*/
public int getIntrinsicWidth() {
return -1;
}
/**
- * Return the intrinsic height of the underlying drawable object. Returns
- * -1 if it has no intrinsic height, such as with a solid color.
+ * Returns the drawable's intrinsic height.
+ * <p>
+ * Intrinsic height is the height at which the drawable would like to be
+ * laid out, including any inherent padding. If the drawable has no
+ * intrinsic height, such as a solid color, this method returns -1.
+ *
+ * @return the intrinsic height, or -1 if no intrinsic height
*/
public int getIntrinsicHeight() {
return -1;
@@ -1230,30 +1240,54 @@
*/
public static abstract class ConstantState {
/**
- * Create a new drawable without supplying resources the caller
- * is running in. Note that using this means the density-dependent
- * drawables (like bitmaps) will not be able to update their target
- * density correctly. One should use {@link #newDrawable(Resources)}
- * instead to provide a resource.
+ * Creates a new Drawable instance from its constant state.
+ * <p>
+ * <strong>Note:</strong> Using this method means density-dependent
+ * properties, such as pixel dimensions or bitmap images, will not be
+ * updated to match the density of the target display. To ensure
+ * correct scaling, use {@link #newDrawable(Resources)} instead to
+ * provide an appropriate Resources object.
+ *
+ * @return a new drawable object based on this constant state
+ * @see {@link #newDrawable(Resources)}
*/
+ @NonNull
public abstract Drawable newDrawable();
/**
- * Create a new Drawable instance from its constant state. This
- * must be implemented for drawables that change based on the target
- * density of their caller (that is depending on whether it is
- * in compatibility mode).
+ * Creates a new Drawable instance from its constant state using the
+ * specified resources. This method should be implemented for drawables
+ * that have density-dependent properties.
+ * <p>
+ * The default implementation for this method calls through to
+ * {@link #newDrawable()}.
+ *
+ * @param res the resources of the context in which the drawable will
+ * be displayed
+ * @return a new drawable object based on this constant state
*/
- public Drawable newDrawable(Resources res) {
+ @NonNull
+ public Drawable newDrawable(@Nullable Resources res) {
return newDrawable();
}
/**
- * Create a new Drawable instance from its constant state. This must be
- * implemented for drawables that can have a theme applied.
+ * Creates a new Drawable instance from its constant state using the
+ * specified resources and theme. This method should be implemented for
+ * drawables that have theme-dependent properties.
+ * <p>
+ * The default implementation for this method calls through to
+ * {@link #newDrawable(Resources)}.
+ *
+ * @param res the resources of the context in which the drawable will
+ * be displayed
+ * @param theme the theme of the context in which the drawable will be
+ * displayed
+ * @return a new drawable object based on this constant state
*/
- public Drawable newDrawable(Resources res, Theme theme) {
- return newDrawable(null);
+ @NonNull
+ public Drawable newDrawable(@Nullable Resources res, @Nullable Theme theme) {
+ return newDrawable(res);
}
/**
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index 927b9c9..36d4272 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -222,12 +222,20 @@
@Override
public int getIntrinsicWidth() {
- return getDrawable().getIntrinsicWidth() + mState.mInsetLeft + mState.mInsetRight;
+ final int childWidth = getDrawable().getIntrinsicWidth();
+ if (childWidth < 0) {
+ return -1;
+ }
+ return childWidth + mState.mInsetLeft + mState.mInsetRight;
}
@Override
public int getIntrinsicHeight() {
- return getDrawable().getIntrinsicHeight() + mState.mInsetTop + mState.mInsetBottom;
+ final int childHeight = getDrawable().getIntrinsicHeight();
+ if (childHeight < 0) {
+ return -1;
+ }
+ return childHeight + mState.mInsetTop + mState.mInsetBottom;
}
@Override
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 5cdd723..4acad67 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -9,6 +9,7 @@
font/Font.cpp \
renderstate/Blend.cpp \
renderstate/MeshState.cpp \
+ renderstate/OffscreenBufferPool.cpp \
renderstate/PixelBufferState.cpp \
renderstate/RenderState.cpp \
renderstate/Scissor.cpp \
@@ -216,6 +217,7 @@
unit_tests/LayerUpdateQueueTests.cpp \
unit_tests/LinearAllocatorTests.cpp \
unit_tests/PathParserTests.cpp \
+ unit_tests/OffscreenBufferPoolTests.cpp \
unit_tests/StringUtilsTests.cpp
ifeq (true, $(HWUI_NEW_OPS))
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 2fca5ea..1aa291f 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -19,104 +19,29 @@
#include "Caches.h"
#include "Glop.h"
#include "GlopBuilder.h"
+#include "renderstate/OffscreenBufferPool.h"
#include "renderstate/RenderState.h"
-#include "utils/FatVector.h"
#include "utils/GLUtils.h"
+#include "VertexBuffer.h"
namespace android {
namespace uirenderer {
////////////////////////////////////////////////////////////////////////////////
-// OffscreenBuffer
-////////////////////////////////////////////////////////////////////////////////
-
-OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
- uint32_t textureWidth, uint32_t textureHeight,
- uint32_t viewportWidth, uint32_t viewportHeight)
- : renderState(renderState)
- , viewportWidth(viewportWidth)
- , viewportHeight(viewportHeight)
- , texture(caches) {
- texture.width = textureWidth;
- texture.height = textureHeight;
-
- caches.textureState().activateTexture(0);
- glGenTextures(1, &texture.id);
- caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
-
- texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
- // not setting filter on texture, since it's set when drawing, based on transform
-
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
-}
-
-void OffscreenBuffer::updateMeshFromRegion() {
- // avoid T-junctions as they cause artifacts in between the resultant
- // geometry when complex transforms occur.
- // TODO: generate the safeRegion only if necessary based on drawing transform
- Region safeRegion = Region::createTJunctionFreeRegion(region);
-
- size_t count;
- const android::Rect* rects = safeRegion.getArray(&count);
-
- const float texX = 1.0f / float(viewportWidth);
- const float texY = 1.0f / float(viewportHeight);
-
- FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
- TextureVertex* mesh = &meshVector[0];
- for (size_t i = 0; i < count; i++) {
- const android::Rect* r = &rects[i];
-
- const float u1 = r->left * texX;
- const float v1 = (viewportHeight - r->top) * texY;
- const float u2 = r->right * texX;
- const float v2 = (viewportHeight - r->bottom) * texY;
-
- TextureVertex::set(mesh++, r->left, r->top, u1, v1);
- TextureVertex::set(mesh++, r->right, r->top, u2, v1);
- TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
- TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
- }
- elementCount = count * 6;
- renderState.meshState().genOrUpdateMeshBuffer(&vbo,
- sizeof(TextureVertex) * count * 4,
- &meshVector[0],
- GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
-}
-
-OffscreenBuffer::~OffscreenBuffer() {
- texture.deleteTexture();
- renderState.meshState().deleteMeshBuffer(vbo);
- elementCount = 0;
- vbo = 0;
-}
-
-////////////////////////////////////////////////////////////////////////////////
// BakedOpRenderer
////////////////////////////////////////////////////////////////////////////////
-OffscreenBuffer* BakedOpRenderer::createOffscreenBuffer(RenderState& renderState,
- uint32_t width, uint32_t height) {
- // TODO: get from cache!
- return new OffscreenBuffer(renderState, Caches::getInstance(), width, height, width, height);
-}
-
-void BakedOpRenderer::destroyOffscreenBuffer(OffscreenBuffer* offscreenBuffer) {
- // TODO: return texture/offscreenbuffer to cache!
- delete offscreenBuffer;
-}
-
-OffscreenBuffer* BakedOpRenderer::createLayer(uint32_t width, uint32_t height) {
+OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
- OffscreenBuffer* buffer = createOffscreenBuffer(mRenderState, width, height);
- startLayer(buffer);
+ OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
+ startRepaintLayer(buffer);
return buffer;
}
-void BakedOpRenderer::startLayer(OffscreenBuffer* offscreenBuffer) {
+void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) {
+ LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
+
mRenderTarget.offscreenBuffer = offscreenBuffer;
// create and bind framebuffer
@@ -261,6 +186,71 @@
renderer.renderGlop(state, glop);
}
+namespace VertexBufferRenderFlags {
+ enum {
+ Offset = 0x1,
+ ShadowInterp = 0x2,
+ };
+}
+
+static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
+ const VertexBuffer& vertexBuffer, float translateX, float translateY,
+ SkPaint& paint, int vertexBufferRenderFlags) {
+ if (CC_LIKELY(vertexBuffer.getVertexCount())) {
+ bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
+ const int transformFlags = TransformFlags::OffsetByFudgeFactor;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshVertexBuffer(vertexBuffer, shadowInterp)
+ .setFillPaint(paint, state.alpha)
+ .setTransform(state.computedState.transform, transformFlags)
+ .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
+ .build();
+ renderer.renderGlop(state, glop);
+ }
+}
+
+static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
+ const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
+ SkPaint paint;
+ paint.setAntiAlias(true); // want to use AlphaVertex
+
+ // The caller has made sure casterAlpha > 0.
+ uint8_t ambientShadowAlpha = 128u; //TODO: mAmbientShadowAlpha;
+ if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
+ ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
+ }
+ if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
+ paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
+ renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0,
+ paint, VertexBufferRenderFlags::ShadowInterp);
+ }
+
+ uint8_t spotShadowAlpha = 128u; //TODO: mSpotShadowAlpha;
+ if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
+ spotShadowAlpha = Properties::overrideSpotShadowStrength;
+ }
+ if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
+ paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
+ renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0,
+ paint, VertexBufferRenderFlags::ShadowInterp);
+ }
+}
+
+void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
+ TessellationCache::vertexBuffer_pair_t buffers;
+ Vector3 lightCenter = { 300, 300, 300 }; // TODO!
+ float lightRadius = 150; // TODO!
+
+ renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
+ op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
+ &op.shadowMatrixXY, &op.shadowMatrixZ, lightCenter, lightRadius,
+ buffers);
+
+ renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
+}
+
void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
@@ -291,7 +281,7 @@
renderer.renderGlop(state, glop);
if (op.destroy) {
- BakedOpRenderer::destroyOffscreenBuffer(buffer);
+ renderer.renderState().layerPool().putOrDelete(buffer);
}
}
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index aa1e67d..d6d9cb1 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -29,33 +29,6 @@
class RenderState;
/**
- * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
- * encompasses enough information to draw it back on screen (minus paint properties, which are held
- * by LayerOp).
- */
-class OffscreenBuffer {
-public:
- OffscreenBuffer(RenderState& renderState, Caches& caches,
- uint32_t textureWidth, uint32_t textureHeight,
- uint32_t viewportWidth, uint32_t viewportHeight);
- ~OffscreenBuffer();
-
- // must be called prior to rendering, to construct/update vertex buffer
- void updateMeshFromRegion();
-
- RenderState& renderState;
- uint32_t viewportWidth;
- uint32_t viewportHeight;
- Texture texture;
-
- // Portion of offscreen buffer that has been drawn to. Used to minimize drawing area when
- // drawing back to screen / parent FBO.
- Region region;
- GLsizei elementCount = 0;
- GLuint vbo = 0;
-};
-
-/**
* Main rendering manager for a collection of work - one frame + any contained FBOs.
*
* Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only
@@ -72,17 +45,13 @@
, mOpaque(opaque) {
}
- static OffscreenBuffer* createOffscreenBuffer(RenderState& renderState,
- uint32_t width, uint32_t height);
- static void destroyOffscreenBuffer(OffscreenBuffer*);
-
RenderState& renderState() { return mRenderState; }
Caches& caches() { return mCaches; }
void startFrame(uint32_t width, uint32_t height);
void endFrame();
- OffscreenBuffer* createLayer(uint32_t width, uint32_t height);
- void startLayer(OffscreenBuffer* offscreenBuffer);
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer);
void endLayer();
Texture* getTexture(const SkBitmap* bitmap);
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index ddb8c84..9a40c3b 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -89,6 +89,21 @@
* and early return null in one place.
*/
}
+
+ /**
+ * Constructor for unbounded ops without transform/clip (namely shadows)
+ *
+ * Since the op doesn't have known bounds, we conservatively set the mapped bounds
+ * to the current clipRect, and clipSideFlags to Full.
+ */
+ ResolvedRenderState(const Snapshot& snapshot) {
+ transform = *snapshot.transform;
+ clipRect = snapshot.getRenderTargetClip();
+ clippedBounds = clipRect;
+ transform.mapRect(clippedBounds);
+ clipSideFlags = OpClipSideFlags::Full;
+ }
+
Matrix4 transform;
Rect clipRect;
int clipSideFlags = 0;
@@ -104,8 +119,7 @@
public:
static BakedOpState* tryConstruct(LinearAllocator& allocator,
const Snapshot& snapshot, const RecordedOp& recordedOp) {
- BakedOpState* bakedOp = new (allocator) BakedOpState(
- snapshot, recordedOp);
+ BakedOpState* bakedOp = new (allocator) BakedOpState(snapshot, recordedOp);
if (bakedOp->computedState.clippedBounds.isEmpty()) {
// bounds are empty, so op is rejected
allocator.rewindIfLastAlloc(bakedOp);
@@ -114,6 +128,14 @@
return bakedOp;
}
+ static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
+ const Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+ if (snapshot.getRenderTargetClip().isEmpty()) return nullptr;
+
+ // clip isn't empty, so construct the op
+ return new (allocator) BakedOpState(snapshot, shadowOpPtr);
+ }
+
static void* operator new(size_t size, LinearAllocator& allocator) {
return allocator.alloc(size);
}
@@ -134,6 +156,13 @@
, roundRectClipState(snapshot.roundRectClipState)
, projectionPathMask(snapshot.projectionPathMask)
, op(&recordedOp) {}
+
+ BakedOpState(const Snapshot& snapshot, const ShadowOp* shadowOpPtr)
+ : computedState(snapshot)
+ , alpha(snapshot.alpha)
+ , roundRectClipState(snapshot.roundRectClipState)
+ , projectionPathMask(snapshot.projectionPathMask)
+ , op(shadowOpPtr) {}
};
}; // namespace uirenderer
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 03b1706..39b7ecb 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -40,7 +40,6 @@
}
void DeviceInfo::load() {
- mExtensions.load();
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
}
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index fc08504..609103b 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -17,6 +17,14 @@
#ifndef ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
#define ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
+#include "Canvas.h"
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "RenderNode.h"
+#include "ResourceCache.h"
+#include "SkiaCanvasProxy.h"
+#include "utils/Macros.h"
+
#include <SkDrawFilter.h>
#include <SkMatrix.h>
#include <SkPaint.h>
@@ -25,13 +33,6 @@
#include <SkTLazy.h>
#include <cutils/compiler.h>
-#include "Canvas.h"
-#include "CanvasState.h"
-#include "DisplayList.h"
-#include "SkiaCanvasProxy.h"
-#include "RenderNode.h"
-#include "ResourceCache.h"
-
namespace android {
namespace uirenderer {
@@ -66,7 +67,7 @@
virtual ~DisplayListCanvas();
void reset(int width, int height);
- __attribute__((warn_unused_result)) DisplayList* finishRecording();
+ WARN_UNUSED_RESULT DisplayList* finishRecording();
// ----------------------------------------------------------------------------
// HWUI Canvas state operations
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index e257715..02caaa4 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -35,7 +35,7 @@
#endif
-void Extensions::load() {
+Extensions::Extensions() {
auto extensions = StringUtils::split((const char*) glGetString(GL_EXTENSIONS));
mHasNPot = extensions.has("GL_OES_texture_npot");
mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch");
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 8ccfabd..67cc747 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -31,7 +31,7 @@
class Extensions {
public:
- void load();
+ Extensions();
inline bool hasNPot() const { return mHasNPot; }
inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 39cadd1..b117754 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-#include <GLES2/gl2.h>
+#include "LayerCache.h"
+
+#include "Caches.h"
+#include "Properties.h"
#include <utils/Log.h>
-#include "Caches.h"
-#include "LayerCache.h"
-#include "Properties.h"
+#include <GLES2/gl2.h>
namespace android {
namespace uirenderer {
@@ -29,15 +30,9 @@
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-LayerCache::LayerCache(): mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting layer cache size to %sMB", property);
- setMaxSize(MB(atof(property)));
- } else {
- INIT_LOGD(" Using default layer cache size of %.2fMB", DEFAULT_LAYER_CACHE_SIZE);
- }
-}
+LayerCache::LayerCache()
+ : mSize(0)
+ , mMaxSize(Properties::layerPoolSize) {}
LayerCache::~LayerCache() {
clear();
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 68f80ea..80efaed 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -22,6 +22,7 @@
#include "utils/PaintUtils.h"
#include <SkCanvas.h>
+#include <SkPathOps.h>
#include <utils/Trace.h>
#include <utils/TypeHelpers.h>
@@ -312,7 +313,7 @@
: mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
- mLayerStack.push_back(0);
+ mLayerStack.push_back(0);
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
@@ -347,7 +348,6 @@
OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
: mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
-
mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
mLayerStack.push_back(0);
@@ -462,8 +462,60 @@
}
void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
- // TODO
+ auto& node = *casterNodeOp.renderNode;
+ auto& properties = node.properties();
+
+ if (properties.getAlpha() <= 0.0f
+ || properties.getOutline().getAlpha() <= 0.0f
+ || !properties.getOutline().getPath()
+ || properties.getScaleX() == 0
+ || properties.getScaleY() == 0) {
+ // no shadow to draw
+ return;
+ }
+
+ const SkPath* casterOutlinePath = properties.getOutline().getPath();
+ const SkPath* revealClipPath = properties.getRevealClip().getPath();
+ if (revealClipPath && revealClipPath->isEmpty()) return;
+
+ float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha();
+
+ // holds temporary SkPath to store the result of intersections
+ SkPath* frameAllocatedPath = nullptr;
+ const SkPath* casterPath = casterOutlinePath;
+
+ // intersect the shadow-casting path with the reveal, if present
+ if (revealClipPath) {
+ frameAllocatedPath = createFrameAllocatedPath();
+
+ Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
+ casterPath = frameAllocatedPath;
+ }
+
+ // intersect the shadow-casting path with the clipBounds, if present
+ if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
+ if (!frameAllocatedPath) {
+ frameAllocatedPath = createFrameAllocatedPath();
+ }
+ Rect clipBounds;
+ properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
+ SkPath clipBoundsPath;
+ clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
+ clipBounds.right, clipBounds.bottom);
+
+ Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
+ casterPath = frameAllocatedPath;
+ }
+
+ ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
+ mCanvasState.getLocalClipBounds());
+ BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
+ mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
+ if (CC_LIKELY(bakedOpState)) {
+ currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
+ }
}
+
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
@@ -593,5 +645,9 @@
LOG_ALWAYS_FATAL("unsupported");
}
+void OpReorderer::onShadowOp(const ShadowOp& op) {
+ LOG_ALWAYS_FATAL("unsupported");
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 936b6ed..2c30f0d 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -51,6 +51,7 @@
AlphaMaskTexture,
Text,
ColorText,
+ Shadow,
Count // must be last
};
@@ -152,11 +153,11 @@
LayerReorderer& layer = mLayerReorderers[i];
if (layer.renderNode) {
// cached HW layer - can't skip layer if empty
- renderer.startLayer(layer.offscreenBuffer);
+ renderer.startRepaintLayer(layer.offscreenBuffer);
layer.replayBakedOpsImpl((void*)&renderer, receivers);
renderer.endLayer();
} else if (!layer.empty()) { // save layer - skip entire layer if empty
- layer.offscreenBuffer = renderer.createLayer(layer.width, layer.height);
+ layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height);
layer.replayBakedOpsImpl((void*)&renderer, receivers);
renderer.endLayer();
}
@@ -210,6 +211,10 @@
void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
+ SkPath* createFrameAllocatedPath() {
+ mFrameAllocatedPaths.emplace_back(new SkPath);
+ return mFrameAllocatedPaths.back().get();
+ }
/**
* Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
*
@@ -220,6 +225,8 @@
void on##Type(const Type& op);
MAP_OPS(INTERNAL_OP_HANDLER)
+ 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/PathParser.cpp b/libs/hwui/PathParser.cpp
index e8ed8a1..35230ff 100644
--- a/libs/hwui/PathParser.cpp
+++ b/libs/hwui/PathParser.cpp
@@ -18,6 +18,7 @@
#include "jni.h"
+#include <errno.h>
#include <utils/Log.h>
#include <sstream>
#include <stdlib.h>
@@ -97,14 +98,31 @@
*outEndPosition = currentIndex;
}
+static float parseFloat(PathParser::ParseResult* result, const char* startPtr, size_t expectedLength) {
+ char* endPtr = NULL;
+ float currentValue = strtof(startPtr, &endPtr);
+ if ((currentValue == HUGE_VALF || currentValue == -HUGE_VALF) && errno == ERANGE) {
+ result->failureOccurred = true;
+ result->failureMessage = "Float out of range: ";
+ result->failureMessage.append(startPtr, expectedLength);
+ }
+ if (currentValue == 0 && endPtr == startPtr) {
+ // No conversion is done.
+ result->failureOccurred = true;
+ result->failureMessage = "Float format error when parsing: ";
+ result->failureMessage.append(startPtr, expectedLength);
+ }
+ return currentValue;
+}
+
/**
-* Parse the floats in the string.
-* This is an optimized version of parseFloat(s.split(",|\\s"));
-*
-* @param s the string containing a command and list of floats
-* @return array of floats
-*/
-static void getFloats(std::vector<float>* outPoints, const char* pathStr, int start, int end) {
+ * Parse the floats in the string.
+ *
+ * @param s the string containing a command and list of floats
+ * @return true on success
+ */
+static void getFloats(std::vector<float>* outPoints, PathParser::ParseResult* result,
+ const char* pathStr, int start, int end) {
if (pathStr[start] == 'z' || pathStr[start] == 'Z') {
return;
@@ -120,7 +138,12 @@
extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end);
if (startPosition < endPosition) {
- outPoints->push_back(strtof(&pathStr[startPosition], NULL));
+ float currentValue = parseFloat(result, &pathStr[startPosition],
+ end - startPosition);
+ if (result->failureOccurred) {
+ return;
+ }
+ outPoints->push_back(currentValue);
}
if (endWithNegOrDot) {
@@ -130,10 +153,14 @@
startPosition = endPosition + 1;
}
}
+ return;
}
-void PathParser::getPathDataFromString(PathData* data, const char* pathStr, size_t strLen) {
+void PathParser::getPathDataFromString(PathData* data, ParseResult* result,
+ const char* pathStr, size_t strLen) {
if (pathStr == NULL) {
+ result->failureOccurred = true;
+ result->failureMessage = "Path string cannot be NULL.";
return;
}
@@ -143,7 +170,10 @@
while (end < strLen) {
end = nextStart(pathStr, strLen, end);
std::vector<float> points;
- getFloats(&points, pathStr, start, end);
+ getFloats(&points, result, pathStr, start, end);
+ if (result->failureOccurred) {
+ return;
+ }
data->verbs.push_back(pathStr[start]);
data->verbSizes.push_back(points.size());
data->points.insert(data->points.end(), points.begin(), points.end());
@@ -151,16 +181,11 @@
end++;
}
- if ((end - start) == 1 && pathStr[start] != '\0') {
+ if ((end - start) == 1 && start < strLen) {
data->verbs.push_back(pathStr[start]);
data->verbSizes.push_back(0);
}
-
- int i = 0;
- while(pathStr[i] != '\0') {
- i++;
- }
-
+ return;
}
void PathParser::dump(const PathData& data) {
@@ -169,6 +194,7 @@
for (size_t i = 0; i < data.verbs.size(); i++) {
std::ostringstream os;
os << data.verbs[i];
+ os << ", verb size: " << data.verbSizes[i];
for (size_t j = 0; j < data.verbSizes[i]; j++) {
os << " " << data.points[start + j];
}
@@ -183,10 +209,20 @@
ALOGD("points are : %s", os.str().c_str());
}
-void PathParser::parseStringForSkPath(SkPath* skPath, const char* pathStr, size_t strLen) {
+void PathParser::parseStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, size_t strLen) {
PathData pathData;
- getPathDataFromString(&pathData, pathStr, strLen);
+ getPathDataFromString(&pathData, result, pathStr, strLen);
+ if (result->failureOccurred) {
+ return;
+ }
+ // Check if there is valid data coming out of parsing the string.
+ if (pathData.verbs.size() == 0) {
+ result->failureOccurred = true;
+ result->failureMessage = "No verbs found in the string for pathData";
+ return;
+ }
VectorDrawablePath::verbsToPath(skPath, &pathData);
+ return;
}
}; // namespace uirenderer
diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h
index 6dc7ee1..a9c1e60 100644
--- a/libs/hwui/PathParser.h
+++ b/libs/hwui/PathParser.h
@@ -21,14 +21,27 @@
#include <jni.h>
#include <android/log.h>
+#include <cutils/compiler.h>
+
+#include <string>
namespace android {
namespace uirenderer {
+
class PathParser {
public:
- static void parseStringForSkPath(SkPath* outPath, const char* pathStr, size_t strLength);
- static void getPathDataFromString(PathData* outData, const char* pathStr, size_t strLength);
+ struct ANDROID_API ParseResult {
+ bool failureOccurred = false;
+ std::string failureMessage;
+ };
+ /**
+ * Parse the string literal and create a Skia Path. Return true on success.
+ */
+ ANDROID_API static void parseStringForSkPath(SkPath* outPath, ParseResult* result,
+ const char* pathStr, size_t strLength);
+ static void getPathDataFromString(PathData* outData, ParseResult* result,
+ const char* pathStr, size_t strLength);
static void dump(const PathData& data);
};
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e818186..0669596 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -37,6 +37,7 @@
bool Properties::enablePartialUpdates = true;
float Properties::textGamma = DEFAULT_TEXT_GAMMA;
+int Properties::layerPoolSize = DEFAULT_LAYER_CACHE_SIZE;
DebugLevel Properties::debugLevel = kDebugDisabled;
OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -52,10 +53,19 @@
ProfileType Properties::sProfileType = ProfileType::None;
bool Properties::sDisableProfileBars = false;
+static int property_get_int(const char* key, int defaultValue) {
+ char buf[PROPERTY_VALUE_MAX] = {'\0',};
+
+ if (property_get(key, buf, "") > 0) {
+ return atoi(buf);
+ }
+ return defaultValue;
+}
+
static float property_get_float(const char* key, float defaultValue) {
char buf[PROPERTY_VALUE_MAX] = {'\0',};
- if (property_get(PROPERTY_PROFILE, buf, "") > 0) {
+ if (property_get(key, buf, "") > 0) {
return atof(buf);
}
return defaultValue;
@@ -114,16 +124,14 @@
showDirtyRegions = property_get_bool(PROPERTY_DEBUG_SHOW_DIRTY_REGIONS, false);
- debugLevel = kDebugDisabled;
- if (property_get(PROPERTY_DEBUG, property, nullptr) > 0) {
- debugLevel = (DebugLevel) atoi(property);
- }
+ debugLevel = (DebugLevel) property_get_int(PROPERTY_DEBUG, kDebugDisabled);
skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
textGamma = property_get_float(PROPERTY_TEXT_GAMMA, DEFAULT_TEXT_GAMMA);
+ layerPoolSize = MB(property_get_float(PROPERTY_LAYER_CACHE_SIZE, DEFAULT_LAYER_CACHE_SIZE));
return (prevDebugLayersUpdates != debugLayersUpdates)
|| (prevDebugOverdraw != debugOverdraw)
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1293c78..1dde7e0 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -267,6 +267,8 @@
static float textGamma;
+ static int layerPoolSize;
+
static DebugLevel debugLevel;
static OverdrawColorSet overdrawColorSet;
static StencilClipDebug debugStencilClip;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 04af8e3..bb7a0a7c 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -43,6 +43,7 @@
OP_FN(BitmapOp) \
OP_FN(RectOp) \
OP_FN(RenderNodeOp) \
+ OP_FN(ShadowOp) \
OP_FN(SimpleRectsOp) \
OP_FN(BeginLayerOp) \
OP_FN(EndLayerOp) \
@@ -109,6 +110,31 @@
: SUPER(RectOp) {}
};
+/**
+ * Real-time, dynamic-lit shadow.
+ *
+ * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time,
+ * and are resolved dynamically, and transform isn't needed.
+ *
+ * State construction handles these properties specially.
+ */
+struct ShadowOp : RecordedOp {
+ ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, const Rect& clipRect)
+ : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
+ , shadowMatrixXY(casterOp.localMatrix)
+ , shadowMatrixZ(casterOp.localMatrix)
+ , casterAlpha(casterAlpha)
+ , casterPath(casterPath) {
+ const RenderNode& node = *casterOp.renderNode;
+ node.applyViewPropertyTransforms(shadowMatrixXY, false);
+ node.applyViewPropertyTransforms(shadowMatrixZ, true);
+ };
+ Matrix4 shadowMatrixXY;
+ Matrix4 shadowMatrixZ;
+ const float casterAlpha;
+ const SkPath* casterPath;
+};
+
struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount)
: SUPER(SimpleRectsOp)
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index fc84c98..f26b0c8 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -20,11 +20,12 @@
#include "Canvas.h"
#include "CanvasState.h"
#include "DisplayList.h"
-#include "utils/LinearAllocator.h"
-#include "utils/NinePatch.h"
#include "ResourceCache.h"
#include "SkiaCanvasProxy.h"
#include "Snapshot.h"
+#include "utils/LinearAllocator.h"
+#include "utils/Macros.h"
+#include "utils/NinePatch.h"
#include <SkDrawFilter.h>
#include <SkPaint.h>
@@ -49,7 +50,7 @@
virtual ~RecordingCanvas();
void reset(int width, int height);
- __attribute__((warn_unused_result)) DisplayList* finishRecording();
+ WARN_UNUSED_RESULT DisplayList* finishRecording();
// ----------------------------------------------------------------------------
// MISC HWUI OPERATIONS - TODO: CATEGORIZE
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 15ca718..e177f9a 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -248,22 +248,31 @@
}
}
-layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+static layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
#if HWUI_NEW_OPS
- return BakedOpRenderer::createOffscreenBuffer(renderState, width, height);
+ return renderState.layerPool().get(renderState, width, height);
#else
return LayerRenderer::createRenderLayer(renderState, width, height);
#endif
}
-void destroyLayer(layer_t* layer) {
+static void destroyLayer(layer_t* layer) {
#if HWUI_NEW_OPS
- BakedOpRenderer::destroyOffscreenBuffer(layer);
+ RenderState& renderState = layer->renderState;
+ renderState.layerPool().putOrDelete(layer);
#else
LayerRenderer::destroyLayer(layer);
#endif
}
+static bool layerMatchesWidthAndHeight(layer_t* layer, int width, int height) {
+#if HWUI_NEW_OPS
+ return layer->viewportWidth == (uint32_t) width && layer->viewportHeight == (uint32_t)height;
+#else
+ return layer->layer.getWidth() == width && layer->layer.getHeight() == height;
+#endif
+}
+
void RenderNode::pushLayerUpdate(TreeInfo& info) {
LayerType layerType = properties().effectiveLayerType();
// If we are not a layer OR we cannot be rendered (eg, view was detached)
@@ -278,17 +287,16 @@
bool transformUpdateNeeded = false;
if (!mLayer) {
- mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
- damageSelf(info);
- transformUpdateNeeded = true;
+ mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+ damageSelf(info);
+ transformUpdateNeeded = true;
+ } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) {
#if HWUI_NEW_OPS
- } else if (mLayer->viewportWidth != (uint32_t) getWidth()
- || mLayer->viewportHeight != (uint32_t)getHeight()) {
- // TODO: allow node's layer to grow larger
- if ((uint32_t)getWidth() > mLayer->texture.width
- || (uint32_t)getHeight() > mLayer->texture.height) {
+ RenderState& renderState = mLayer->renderState;
+ if (properties().fitsOnLayer()) {
+ mLayer = renderState.layerPool().resize(mLayer, getWidth(), getHeight());
+ } else {
#else
- } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
#endif
destroyLayer(mLayer);
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index ca7789e..0bd5b65 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -608,12 +608,16 @@
&& getOutline().getAlpha() != 0.0f;
}
- bool promotedToLayer() const {
+ bool fitsOnLayer() const {
const DeviceInfo* deviceInfo = DeviceInfo::get();
LOG_ALWAYS_FATAL_IF(!deviceInfo, "DeviceInfo uninitialized");
+ return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
+ && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
+ }
+
+ bool promotedToLayer() const {
return mLayerProperties.mType == LayerType::None
- && mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
- && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize()
+ && fitsOnLayer()
&& (mComputedFields.mNeedLayerForFunctors
|| (!MathUtils::isZero(mPrimitiveFields.mAlpha)
&& mPrimitiveFields.mAlpha < 1
diff --git a/libs/hwui/VectorDrawablePath.cpp b/libs/hwui/VectorDrawablePath.cpp
index 115435c..05ea2da 100644
--- a/libs/hwui/VectorDrawablePath.cpp
+++ b/libs/hwui/VectorDrawablePath.cpp
@@ -37,8 +37,11 @@
};
VectorDrawablePath::VectorDrawablePath(const char* pathStr, size_t strLength) {
- PathParser::getPathDataFromString(&mData, pathStr, strLength);
- verbsToPath(&mSkPath, &mData);
+ PathParser::ParseResult result;
+ PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
+ if (!result.failureOccurred) {
+ verbsToPath(&mSkPath, &mData);
+ }
}
VectorDrawablePath::VectorDrawablePath(const PathData& data) {
@@ -80,7 +83,7 @@
for (unsigned int i = 0; i < data->verbs.size(); i++) {
size_t verbSize = data->verbSizes[i];
resolver.addCommand(outPath, previousCommand, data->verbs[i], &data->points, start,
- start + verbSize - 1u);
+ start + verbSize);
previousCommand = data->verbs[i];
start += verbSize;
}
@@ -254,7 +257,7 @@
arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
}
-
+// Use the given verb, and points in the range [start, end) to insert a command into the SkPath.
void PathResolver::addCommand(SkPath* outPath, char previousCmd,
char cmd, const std::vector<float>* points, size_t start, size_t end) {
@@ -305,7 +308,7 @@
break;
}
- for (unsigned int k = start; k <= end; k += incr) {
+ for (unsigned int k = start; k < end; k += incr) {
switch (cmd) {
case 'm': // moveto - Start a new sub-path (relative)
currentX += points->at(k + 0);
diff --git a/libs/hwui/microbench/PathParserBench.cpp b/libs/hwui/microbench/PathParserBench.cpp
index 198035e..171078d 100644
--- a/libs/hwui/microbench/PathParserBench.cpp
+++ b/libs/hwui/microbench/PathParserBench.cpp
@@ -28,9 +28,10 @@
const char* pathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10";
SkPath skPath;
size_t length = strlen(pathString);
+ PathParser::ParseResult result;
StartBenchmarkTiming();
for (int i = 0; i < iter; i++) {
- PathParser::parseStringForSkPath(&skPath, pathString, length);
+ PathParser::parseStringForSkPath(&skPath, &result, pathString, length);
}
StopBenchmarkTiming();
}
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
new file mode 100644
index 0000000..6b44557
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -0,0 +1,196 @@
+/*
+ * 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.
+ */
+
+#include "OffscreenBufferPool.h"
+
+#include "Caches.h"
+#include "Properties.h"
+#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
+
+#include <utils/Log.h>
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace uirenderer {
+
+////////////////////////////////////////////////////////////////////////////////
+// OffscreenBuffer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t viewportWidth, uint32_t viewportHeight)
+ : renderState(renderState)
+ , viewportWidth(viewportWidth)
+ , viewportHeight(viewportHeight)
+ , texture(caches) {
+ texture.width = computeIdealDimension(viewportWidth);
+ texture.height = computeIdealDimension(viewportHeight);
+ texture.blend = true;
+
+ caches.textureState().activateTexture(0);
+ glGenTextures(1, &texture.id);
+ caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
+
+ texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
+ // not setting filter on texture, since it's set when drawing, based on transform
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+}
+
+void OffscreenBuffer::updateMeshFromRegion() {
+ // avoid T-junctions as they cause artifacts in between the resultant
+ // geometry when complex transforms occur.
+ // TODO: generate the safeRegion only if necessary based on drawing transform
+ Region safeRegion = Region::createTJunctionFreeRegion(region);
+
+ size_t count;
+ const android::Rect* rects = safeRegion.getArray(&count);
+
+ const float texX = 1.0f / float(texture.width);
+ const float texY = 1.0f / float(texture.height);
+
+ FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
+ TextureVertex* mesh = &meshVector[0];
+ for (size_t i = 0; i < count; i++) {
+ const android::Rect* r = &rects[i];
+
+ const float u1 = r->left * texX;
+ const float v1 = (viewportHeight - r->top) * texY;
+ const float u2 = r->right * texX;
+ const float v2 = (viewportHeight - r->bottom) * texY;
+
+ TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+ TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+ TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+ TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+ }
+ elementCount = count * 6;
+ renderState.meshState().genOrUpdateMeshBuffer(&vbo,
+ sizeof(TextureVertex) * count * 4,
+ &meshVector[0],
+ GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
+}
+
+uint32_t OffscreenBuffer::computeIdealDimension(uint32_t dimension) {
+ return uint32_t(ceilf(dimension / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+OffscreenBuffer::~OffscreenBuffer() {
+ texture.deleteTexture();
+ renderState.meshState().deleteMeshBuffer(vbo);
+ elementCount = 0;
+ vbo = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// OffscreenBufferPool
+///////////////////////////////////////////////////////////////////////////////
+
+OffscreenBufferPool::OffscreenBufferPool()
+ : mMaxSize(Properties::layerPoolSize) {
+}
+
+OffscreenBufferPool::~OffscreenBufferPool() {
+ clear(); // TODO: unique_ptr?
+}
+
+int OffscreenBufferPool::Entry::compare(const Entry& lhs, const Entry& rhs) {
+ int deltaInt = int(lhs.width) - int(rhs.width);
+ if (deltaInt != 0) return deltaInt;
+
+ return int(lhs.height) - int(rhs.height);
+}
+
+void OffscreenBufferPool::clear() {
+ for (auto entry : mPool) {
+ delete entry.layer;
+ }
+ mPool.clear();
+ mSize = 0;
+}
+
+OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState,
+ const uint32_t width, const uint32_t height) {
+ OffscreenBuffer* layer = nullptr;
+
+ Entry entry(width, height);
+ auto iter = mPool.find(entry);
+
+ if (iter != mPool.end()) {
+ entry = *iter;
+ mPool.erase(iter);
+
+ layer = entry.layer;
+ layer->viewportWidth = width;
+ layer->viewportHeight = height;
+ mSize -= layer->getSizeInBytes();
+ } else {
+ layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height);
+ }
+
+ return layer;
+}
+
+OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer,
+ const uint32_t width, const uint32_t height) {
+ RenderState& renderState = layer->renderState;
+ if (layer->texture.width == OffscreenBuffer::computeIdealDimension(width)
+ && layer->texture.height == OffscreenBuffer::computeIdealDimension(height)) {
+ // resize in place
+ layer->viewportWidth = width;
+ layer->viewportHeight = height;
+ return layer;
+ }
+ putOrDelete(layer);
+ return get(renderState, width, height);
+}
+
+void OffscreenBufferPool::dump() {
+ for (auto entry : mPool) {
+ ALOGD(" Layer size %dx%d", entry.width, entry.height);
+ }
+}
+
+void OffscreenBufferPool::putOrDelete(OffscreenBuffer* layer) {
+ const uint32_t size = layer->getSizeInBytes();
+ // Don't even try to cache a layer that's bigger than the cache
+ if (size < mMaxSize) {
+ // TODO: Use an LRU
+ while (mSize + size > mMaxSize) {
+ OffscreenBuffer* victim = mPool.begin()->layer;
+ mSize -= victim->getSizeInBytes();
+ delete victim;
+ mPool.erase(mPool.begin());
+ }
+
+ // clear region, since it's no longer valid
+ layer->region.clear();
+
+ Entry entry(layer);
+
+ mPool.insert(entry);
+ mSize += size;
+ } else {
+ delete layer;
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
new file mode 100644
index 0000000..f0fd82d
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+#define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+
+#include "Caches.h"
+#include "Texture.h"
+#include "utils/Macros.h"
+
+#include <ui/Region.h>
+
+#include <set>
+
+namespace android {
+namespace uirenderer {
+
+class RenderState;
+
+/**
+ * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
+ * encompasses enough information to draw it back on screen (minus paint properties, which are held
+ * by LayerOp).
+ */
+class OffscreenBuffer {
+public:
+ OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t viewportWidth, uint32_t viewportHeight);
+ ~OffscreenBuffer();
+
+ // must be called prior to rendering, to construct/update vertex buffer
+ void updateMeshFromRegion();
+
+ static uint32_t computeIdealDimension(uint32_t dimension);
+
+ uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
+
+ RenderState& renderState;
+ uint32_t viewportWidth;
+ uint32_t viewportHeight;
+ Texture texture;
+
+ // Portion of layer that has been drawn to. Used to minimize drawing area when
+ // drawing back to screen / parent FBO.
+ Region region;
+ GLsizei elementCount = 0;
+ GLuint vbo = 0;
+};
+
+/**
+ * Pool of OffscreenBuffers allocated, but not currently in use.
+ */
+class OffscreenBufferPool {
+public:
+ OffscreenBufferPool();
+ ~OffscreenBufferPool();
+
+ WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState,
+ const uint32_t width, const uint32_t height);
+
+ WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer,
+ const uint32_t width, const uint32_t height);
+
+ void putOrDelete(OffscreenBuffer* layer);
+
+ /**
+ * Clears the pool. This causes all layers to be deleted.
+ */
+ void clear();
+
+ /**
+ * Returns the maximum size of the pool in bytes.
+ */
+ uint32_t getMaxSize() { return mMaxSize; }
+
+ /**
+ * Returns the current size of the pool in bytes.
+ */
+ uint32_t getSize() { return mSize; }
+
+ size_t getCount() { return mPool.size(); }
+
+ /**
+ * Prints out the content of the pool.
+ */
+ void dump();
+private:
+ struct Entry {
+ Entry() {}
+
+ Entry(const uint32_t layerWidth, const uint32_t layerHeight)
+ : width(OffscreenBuffer::computeIdealDimension(layerWidth))
+ , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {}
+
+ Entry(OffscreenBuffer* layer)
+ : layer(layer)
+ , width(layer->texture.width)
+ , height(layer->texture.height) {
+ }
+
+ static int compare(const Entry& lhs, const Entry& rhs);
+
+ bool operator==(const Entry& other) const {
+ return compare(*this, other) == 0;
+ }
+
+ bool operator!=(const Entry& other) const {
+ return compare(*this, other) != 0;
+ }
+
+ bool operator<(const Entry& other) const {
+ return Entry::compare(*this, other) < 0;
+ }
+
+ OffscreenBuffer* layer = nullptr;
+ uint32_t width = 0;
+ uint32_t height = 0;
+ }; // struct Entry
+
+ std::multiset<Entry> mPool;
+
+ uint32_t mSize = 0;
+ uint32_t mMaxSize;
+}; // class OffscreenBufferCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 9637117..4fa8200 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -90,6 +90,8 @@
}
*/
+ mLayerPool.clear();
+
// TODO: reset all cached state in state objects
std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
mAssetAtlas.terminate();
@@ -106,6 +108,19 @@
mStencil = nullptr;
}
+void RenderState::flush(Caches::FlushMode mode) {
+ switch (mode) {
+ case Caches::FlushMode::Full:
+ // fall through
+ case Caches::FlushMode::Moderate:
+ // fall through
+ case Caches::FlushMode::Layers:
+ mLayerPool.clear();
+ break;
+ }
+ mCaches->flush(mode);
+}
+
void RenderState::setViewport(GLsizei width, GLsizei height) {
mViewportWidth = width;
mViewportHeight = height;
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 3cda170..dcd5ea6 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -21,6 +21,7 @@
#include "Glop.h"
#include "renderstate/Blend.h"
#include "renderstate/MeshState.h"
+#include "renderstate/OffscreenBufferPool.h"
#include "renderstate/PixelBufferState.h"
#include "renderstate/Scissor.h"
#include "renderstate/Stencil.h"
@@ -56,6 +57,8 @@
void onGLContextCreated();
void onGLContextDestroyed();
+ void flush(Caches::FlushMode flushMode);
+
void setViewport(GLsizei width, GLsizei height);
void getViewport(GLsizei* outWidth, GLsizei* outHeight);
@@ -97,6 +100,8 @@
Scissor& scissor() { return *mScissor; }
Stencil& stencil() { return *mStencil; }
+ OffscreenBufferPool& layerPool() { return mLayerPool; }
+
void dump();
private:
@@ -116,6 +121,8 @@
Scissor* mScissor = nullptr;
Stencil* mStencil = nullptr;
+ OffscreenBufferPool mLayerPool;
+
AssetAtlas mAssetAtlas;
std::set<Layer*> mActiveLayers;
std::set<renderthread::CanvasContext*> mRegisteredContexts;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1b89960..f094b2d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -599,7 +599,7 @@
// Make sure to release all the textures we were owning as there won't
// be another draw
caches.textureCache.resetMarkInUse(this);
- caches.flush(Caches::FlushMode::Layers);
+ mRenderThread.renderState().flush(Caches::FlushMode::Layers);
}
}
@@ -609,10 +609,10 @@
ATRACE_CALL();
if (level >= TRIM_MEMORY_COMPLETE) {
- Caches::getInstance().flush(Caches::FlushMode::Full);
+ thread.renderState().flush(Caches::FlushMode::Full);
thread.eglManager().destroy();
} else if (level >= TRIM_MEMORY_UI_HIDDEN) {
- Caches::getInstance().flush(Caches::FlushMode::Moderate);
+ thread.renderState().flush(Caches::FlushMode::Moderate);
}
}
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index 29d9803..81bf9ed 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -408,7 +408,7 @@
public:
sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
- }, true);
+ }, TestUtils::getHwLayerSetupCallback());
void createContent(int width, int height, TestCanvas* canvas) override {
canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
canvas->drawRenderNode(card.get());
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index bc1b69f..4e00fb3 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -60,24 +60,21 @@
TEST(BakedOpState, constructAndReject) {
LinearAllocator allocator;
- Matrix4 identity;
- identity.loadIdentity();
-
Matrix4 translate100x0;
translate100x0.loadTranslate(100, 0, 0);
SkPaint paint;
{
RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(0, 0, 100, 200), &paint);
- auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200));
BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
}
{
- RectOp successOp(Rect(30, 40, 100, 200), identity, Rect(0, 0, 100, 200), &paint);
- auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+ RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), Rect(0, 0, 100, 200), &paint);
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200));
BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
@@ -85,5 +82,24 @@
}
}
+TEST(BakedOpState, oplessConstructAndReject) {
+ LinearAllocator allocator;
+ {
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 0, 0)); // empty
+ BakedOpState* bakedOp = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
+
+ EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
+ EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
+ }
+ {
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200));
+ BakedOpState* bakedOp = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
+
+ EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
+ EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op
+ EXPECT_EQ((ShadowOp*)0x1234, bakedOp->op);
+ }
+}
+
}
}
diff --git a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
new file mode 100644
index 0000000..ba92157
--- /dev/null
+++ b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+#include <renderstate/OffscreenBufferPool.h>
+
+#include <unit_tests/TestUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(OffscreenBuffer, computeIdealDimension) {
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(1));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(31));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(33));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(64));
+ EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000));
+}
+
+TEST(OffscreenBuffer, construct) {
+ TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+ OffscreenBuffer layer(thread.renderState(), Caches::getInstance(), 49u, 149u);
+ EXPECT_EQ(49u, layer.viewportWidth);
+ EXPECT_EQ(149u, layer.viewportHeight);
+
+ EXPECT_EQ(64u, layer.texture.width);
+ EXPECT_EQ(192u, layer.texture.height);
+
+ EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
+ });
+}
+
+TEST(OffscreenBufferPool, construct) {
+ TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+ OffscreenBufferPool pool;
+ EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty";
+ EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty";
+ EXPECT_EQ((uint32_t) Properties::layerPoolSize, pool.getMaxSize())
+ << "pool must read size from Properties";
+ });
+
+}
+
+TEST(OffscreenBufferPool, getPutClear) {
+ TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+ OffscreenBufferPool pool;
+
+ auto layer = pool.get(thread.renderState(), 100u, 200u);
+ EXPECT_EQ(100u, layer->viewportWidth);
+ EXPECT_EQ(200u, layer->viewportHeight);
+
+ ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
+
+ pool.putOrDelete(layer);
+ ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
+
+ auto layer2 = pool.get(thread.renderState(), 102u, 202u);
+ EXPECT_EQ(layer, layer2) << "layer should be recycled";
+ ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
+
+ pool.putOrDelete(layer);
+ EXPECT_EQ(1u, pool.getCount());
+ pool.clear();
+ EXPECT_EQ(0u, pool.getSize());
+ EXPECT_EQ(0u, pool.getCount());
+ });
+}
+
+TEST(OffscreenBufferPool, resize) {
+ TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+ OffscreenBufferPool pool;
+
+ auto layer = pool.get(thread.renderState(), 64u, 64u);
+
+ // resize in place
+ ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
+ EXPECT_EQ(60u, layer->viewportWidth);
+ EXPECT_EQ(55u, layer->viewportHeight);
+ EXPECT_EQ(64u, layer->texture.width);
+ EXPECT_EQ(64u, layer->texture.height);
+
+ // resized to use different object in pool
+ auto layer2 = pool.get(thread.renderState(), 128u, 128u);
+ pool.putOrDelete(layer2);
+ ASSERT_EQ(1u, pool.getCount());
+ ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
+ EXPECT_EQ(120u, layer2->viewportWidth);
+ EXPECT_EQ(125u, layer2->viewportHeight);
+ EXPECT_EQ(128u, layer2->texture.width);
+ EXPECT_EQ(128u, layer2->texture.height);
+
+ // original allocation now only thing in pool
+ EXPECT_EQ(1u, pool.getCount());
+ EXPECT_EQ(layer->getSizeInBytes(), pool.getSize());
+ });
+}
+
+TEST(OffscreenBufferPool, putAndDestroy) {
+ TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+ OffscreenBufferPool pool;
+ // layer too big to return to the pool
+ // Note: this relies on the fact that the pool won't reject based on max texture size
+ auto hugeLayer = pool.get(thread.renderState(), pool.getMaxSize() / 64, 64);
+ EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize());
+ pool.putOrDelete(hugeLayer);
+ EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead)
+ });
+}
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index f67c24a..07080a2 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -38,17 +38,17 @@
* and allows Renderer vs Dispatching behavior to be merged.
*
* onXXXOp methods fail by default - tests should override ops they expect
- * startLayer fails by default - tests should override if expected
+ * startRepaintLayer fails by default - tests should override if expected
* startFrame/endFrame do nothing by default - tests should override to intercept
*/
class TestRendererBase {
public:
virtual ~TestRendererBase() {}
- virtual OffscreenBuffer* createLayer(uint32_t, uint32_t) {
+ virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) {
ADD_FAILURE() << "Layer creation not expected in this test";
return nullptr;
}
- virtual void startLayer(OffscreenBuffer*) {
+ virtual void startRepaintLayer(OffscreenBuffer*) {
ADD_FAILURE() << "Layer repaint not expected in this test";
}
virtual void endLayer() {
@@ -82,27 +82,27 @@
MAP_OPS(DISPATCHER_METHOD);
};
-
class FailRenderer : public TestRendererBase {};
-class SimpleTestRenderer : public TestRendererBase {
-public:
- void startFrame(uint32_t width, uint32_t height) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(100u, width);
- EXPECT_EQ(200u, height);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- }
- void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_EQ(2, mIndex++);
- }
- void endFrame() override {
- EXPECT_EQ(3, mIndex++);
- }
-};
TEST(OpReorderer, simple) {
+ class SimpleTestRenderer : public TestRendererBase {
+ public:
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(100u, width);
+ EXPECT_EQ(200u, height);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ }
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void endFrame() override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ };
+
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(25, 25);
canvas.drawRect(0, 0, 100, 200, SkPaint());
@@ -115,7 +115,6 @@
EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
}
-
TEST(OpReorderer, simpleRejection) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -129,18 +128,18 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-
-static int SIMPLE_BATCHING_LOOPS = 5;
-class SimpleBatchingTestRenderer : public TestRendererBase {
-public:
- void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
- }
-};
TEST(OpReorderer, simpleBatching) {
+ static int SIMPLE_BATCHING_LOOPS = 5;
+ class SimpleBatchingTestRenderer : public TestRendererBase {
+ public:
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
+ }
+ };
+
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
@@ -162,24 +161,25 @@
EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging)
}
-class RenderNodeTestRenderer : public TestRendererBase {
-public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- switch(mIndex++) {
- case 0:
- EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
- EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
- break;
- case 1:
- EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
- EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
- break;
- default:
- ADD_FAILURE();
- }
- }
-};
TEST(OpReorderer, renderNode) {
+ class RenderNodeTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ switch(mIndex++) {
+ case 0:
+ EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ break;
+ case 1:
+ EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ }
+ };
+
sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
@@ -210,16 +210,17 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-class ClippedTestRenderer : public TestRendererBase {
-public:
- void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
-};
TEST(OpReorderer, clipped) {
+ class ClippedTestRenderer : public TestRendererBase {
+ public:
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ };
+
sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
@@ -236,36 +237,36 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-
-class SaveLayerSimpleTestRenderer : public TestRendererBase {
-public:
- OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(180u, width);
- EXPECT_EQ(180u, height);
- return nullptr;
- }
- void endLayer() override {
- EXPECT_EQ(2, mIndex++);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
- EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
-
- Matrix4 expectedTransform;
- expectedTransform.loadTranslate(-10, -10, 0);
- EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(3, mIndex++);
- EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
-};
TEST(OpReorderer, saveLayerSimple) {
+ class SaveLayerSimpleTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(180u, width);
+ EXPECT_EQ(180u, height);
+ return nullptr;
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
+ EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
+
+ Matrix4 expectedTransform;
+ expectedTransform.loadTranslate(-10, -10, 0);
+ EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ };
+
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(10, 10, 190, 190, SkPaint());
@@ -279,57 +280,57 @@
EXPECT_EQ(4, renderer.getIndex());
}
-
-/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
- * - createLayer2, rect2 endLayer2
- * - createLayer1, rect1, drawLayer2, endLayer1
- * - startFrame, layerOp1, endFrame
- */
-class SaveLayerNestedTestRenderer : public TestRendererBase {
-public:
- OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
- const int index = mIndex++;
- if (index == 0) {
- EXPECT_EQ(400u, width);
- EXPECT_EQ(400u, height);
- return (OffscreenBuffer*) 0x400;
- } else if (index == 3) {
- EXPECT_EQ(800u, width);
- EXPECT_EQ(800u, height);
- return (OffscreenBuffer*) 0x800;
- } else { ADD_FAILURE(); }
- return (OffscreenBuffer*) nullptr;
- }
- void endLayer() override {
- int index = mIndex++;
- EXPECT_TRUE(index == 2 || index == 6);
- }
- void startFrame(uint32_t width, uint32_t height) override {
- EXPECT_EQ(7, mIndex++);
- }
- void endFrame() override {
- EXPECT_EQ(9, mIndex++);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- const int index = mIndex++;
- if (index == 1) {
- EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
- } else if (index == 4) {
- EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
- } else { ADD_FAILURE(); }
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- const int index = mIndex++;
- if (index == 5) {
- EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
- EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
- } else if (index == 8) {
- EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
- EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
- } else { ADD_FAILURE(); }
- }
-};
TEST(OpReorderer, saveLayerNested) {
+ /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
+ * - startTemporaryLayer2, rect2 endLayer2
+ * - startTemporaryLayer1, rect1, drawLayer2, endLayer1
+ * - startFrame, layerOp1, endFrame
+ */
+ class SaveLayerNestedTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+ const int index = mIndex++;
+ if (index == 0) {
+ EXPECT_EQ(400u, width);
+ EXPECT_EQ(400u, height);
+ return (OffscreenBuffer*) 0x400;
+ } else if (index == 3) {
+ EXPECT_EQ(800u, width);
+ EXPECT_EQ(800u, height);
+ return (OffscreenBuffer*) 0x800;
+ } else { ADD_FAILURE(); }
+ return (OffscreenBuffer*) nullptr;
+ }
+ void endLayer() override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 6);
+ }
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(7, mIndex++);
+ }
+ void endFrame() override {
+ EXPECT_EQ(9, mIndex++);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+ if (index == 1) {
+ EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
+ } else if (index == 4) {
+ EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
+ } else { ADD_FAILURE(); }
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+ if (index == 5) {
+ EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
+ EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+ } else if (index == 8) {
+ EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
+ EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
+ } else { ADD_FAILURE(); }
+ }
+ };
+
auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
{
@@ -369,42 +370,41 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-class HwLayerSimpleTestRenderer : public TestRendererBase {
-public:
- void startLayer(OffscreenBuffer* offscreenBuffer) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
-
- EXPECT_TRUE(state.computedState.transform.isIdentity())
- << "Transform should be reset within layer";
-
- EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
- << "Damage rect should be used to clip layer content";
- }
- void endLayer() override {
- EXPECT_EQ(2, mIndex++);
- }
- void startFrame(uint32_t width, uint32_t height) override {
- EXPECT_EQ(3, mIndex++);
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(4, mIndex++);
- }
- void endFrame() override {
- EXPECT_EQ(5, mIndex++);
- }
-};
TEST(OpReorderer, hwLayerSimple) {
+ class HwLayerSimpleTestRenderer : public TestRendererBase {
+ public:
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+
+ EXPECT_TRUE(state.computedState.transform.isIdentity())
+ << "Transform should be reset within layer";
+
+ EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
+ << "Damage rect should be used to clip layer content";
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ void endFrame() override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ };
+
sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
- });
- node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
- node->setPropertyFieldsDirty(RenderNode::GENERIC);
+ }, TestUtils::getHwLayerSetupCallback());
OffscreenBuffer** bufferHandle = node->getLayerHandle();
*bufferHandle = (OffscreenBuffer*) 0x0124;
@@ -427,69 +427,67 @@
*bufferHandle = nullptr;
}
-
-/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
- * - startLayer(child), rect(grey), endLayer
- * - createLayer, drawLayer(child), endLayer
- * - startLayer(parent), rect(white), drawLayer(saveLayer), endLayer
- * - startFrame, drawLayer(parent), endLayerb
- */
-class HwLayerComplexTestRenderer : public TestRendererBase {
-public:
- OffscreenBuffer* createLayer(uint32_t width, uint32_t height) {
- EXPECT_EQ(3, mIndex++); // savelayer first
- return (OffscreenBuffer*)0xabcd;
- }
- void startLayer(OffscreenBuffer* offscreenBuffer) override {
- int index = mIndex++;
- if (index == 0) {
- // starting inner layer
- EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
- } else if (index == 6) {
- // starting outer layer
- EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
- } else { ADD_FAILURE(); }
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- if (index == 1) {
- // inner layer's rect (white)
- EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
- } else if (index == 7) {
- // outer layer's rect (grey)
- EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
- } else { ADD_FAILURE(); }
- }
- void endLayer() override {
- int index = mIndex++;
- EXPECT_TRUE(index == 2 || index == 5 || index == 9);
- }
- void startFrame(uint32_t width, uint32_t height) override {
- EXPECT_EQ(10, mIndex++);
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- if (index == 4) {
- EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
- } else if (index == 8) {
- EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
- } else if (index == 11) {
- EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
- } else { ADD_FAILURE(); }
- }
- void endFrame() override {
- EXPECT_EQ(12, mIndex++);
- }
-};
TEST(OpReorderer, hwLayerComplex) {
+ /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
+ * - startRepaintLayer(child), rect(grey), endLayer
+ * - startTemporaryLayer, drawLayer(child), endLayer
+ * - startRepaintLayer(parent), rect(white), drawLayer(saveLayer), endLayer
+ * - startFrame, drawLayer(parent), endLayerb
+ */
+ class HwLayerComplexTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
+ EXPECT_EQ(3, mIndex++); // savelayer first
+ return (OffscreenBuffer*)0xabcd;
+ }
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+ int index = mIndex++;
+ if (index == 0) {
+ // starting inner layer
+ EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
+ } else if (index == 6) {
+ // starting outer layer
+ EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
+ } else { ADD_FAILURE(); }
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ if (index == 1) {
+ // inner layer's rect (white)
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ } else if (index == 7) {
+ // outer layer's rect (grey)
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ } else { ADD_FAILURE(); }
+ }
+ void endLayer() override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 5 || index == 9);
+ }
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(10, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ if (index == 4) {
+ EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
+ } else if (index == 8) {
+ EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
+ } else if (index == 11) {
+ EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
+ } else { ADD_FAILURE(); }
+ }
+ void endFrame() override {
+ EXPECT_EQ(12, mIndex++);
+ }
+ };
+
auto child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150,
[](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
- });
- child->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
- child->setPropertyFieldsDirty(RenderNode::GENERIC);
+ }, TestUtils::getHwLayerSetupCallback());
*(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
RenderNode* childPtr = child.get();
@@ -502,9 +500,7 @@
canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRenderNode(childPtr);
canvas.restore();
- });
- parent->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
- parent->setPropertyFieldsDirty(RenderNode::GENERIC);
+ }, TestUtils::getHwLayerSetupCallback());
*(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
@@ -527,14 +523,6 @@
*(parent->getLayerHandle()) = nullptr;
}
-
-class ZReorderTestRenderer : public TestRendererBase {
-public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
- EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
- }
-};
static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
SkPaint paint;
paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
@@ -550,6 +538,14 @@
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
TEST(OpReorderer, zReorder) {
+ class ZReorderTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+ EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+ }
+ };
+
auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
@@ -576,27 +572,64 @@
EXPECT_EQ(10, renderer.getIndex());
};
+TEST(OpReorderer, shadow) {
+ class ShadowTestRenderer : public TestRendererBase {
+ public:
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ }
+ };
-class PropertyTestRenderer : public TestRendererBase {
-public:
- PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
- : mCallback(callback) {}
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(mIndex++, 0);
- mCallback(op, state);
- }
- std::function<void(const RectOp&, const BakedOpState&)> mCallback;
-};
+ sp<RenderNode> caster = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ }, [] (RenderProperties& properties) {
+ properties.setTranslationZ(5.0f);
+ properties.mutableOutline().setRoundRect(0, 0, 100, 100, 5, 1.0f);
+ return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
+ });
+ sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+ [&caster] (RecordingCanvas& canvas) {
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(caster.get());
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ std::vector< sp<RenderNode> > nodes;
+ nodes.push_back(parent.get());
+
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+ ShadowTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
static void testProperty(
- std::function<int(RenderProperties&)> propSetupCallback,
+ TestUtils::PropSetupCallback propSetupCallback,
std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
+ class PropertyTestRenderer : public TestRendererBase {
+ public:
+ PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
+ : mCallback(callback) {}
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(mIndex++, 0);
+ mCallback(op, state);
+ }
+ std::function<void(const RectOp&, const BakedOpState&)> mCallback;
+ };
+
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
- });
- node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
+ }, propSetupCallback);
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
std::vector< sp<RenderNode> > nodes;
diff --git a/libs/hwui/unit_tests/PathParserTests.cpp b/libs/hwui/unit_tests/PathParserTests.cpp
index 244bd6c..c99d7b0 100644
--- a/libs/hwui/unit_tests/PathParserTests.cpp
+++ b/libs/hwui/unit_tests/PathParserTests.cpp
@@ -30,142 +30,204 @@
const std::function<void(SkPath*)> skPathLamda;
};
-static TestData testData1 {
- // Path
- "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z",
+const static TestData sTestDataSet[] = {
+ // TestData with scientific notation -2e3 etc.
{
- // Verbs
- {'M', 'l', 'z'},
- // Verb sizes
- {2, 4, 0},
- // Points
- {2, 22, 20, 0, 1, -2000},
+ // Path
+ "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z",
+ {
+ // Verbs
+ {'M', 'l', 'z'},
+ // Verb sizes
+ {2, 4, 0},
+ // Points
+ {2, 22, 20, 0, 1, -2000},
+ },
+ [](SkPath* outPath) {
+ outPath->moveTo(2, 22);
+ outPath->rLineTo(20, 0);
+ outPath->rLineTo(1, -2000);
+ outPath->close();
+ outPath->moveTo(2, 22);
+ }
},
- [](SkPath* outPath) {
- outPath->moveTo(2, 22);
- outPath->rLineTo(20, 0);
- outPath->rLineTo(1, -2000);
- outPath->close();
- outPath->moveTo(2, 22);
+
+ // Comprehensive data, containing all the verbs possible.
+ {
+ // Path
+ "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10",
+ {
+ // Verbs
+ {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A', 'a'},
+ // VerbSizes
+ {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7},
+ // Points
+ {1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, }
+ },
+ [](SkPath* outPath) {
+ outPath->moveTo(1.0, 1.0);
+ outPath->rMoveTo(2.0, 2.0);
+ outPath->rLineTo(3.0, 3.0);
+ outPath->lineTo(3.0, 3.0);
+ outPath->lineTo(4.0, 3.0);
+ outPath->rLineTo(4.0, 0);
+ outPath->lineTo(8.0, 5.0);
+ outPath->rLineTo(0, 5.0);
+ outPath->quadTo(6.0, 6.0, 6.0, 6.0);
+ outPath->rQuadTo(6.0, 6.0, 6.0, 6.0);
+ outPath->rQuadTo(0.0, 0.0, 7.0, 7.0);
+ outPath->quadTo(26.0, 26.0, 7.0, 7.0);
+ outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
+ outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
+ outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0);
+ outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0);
+ outPath->cubicTo(18.447775037328352, 20.404243860300607, 17.998389141249767, 22.8911717921705, 16.737515350332117, 24.986664170401575);
+ outPath->cubicTo(15.476641559414468, 27.08215654863265, 13.489843598291483, 28.644011882390082, 11.155893964798905, 29.37447073281729);
+ outPath->cubicTo(8.821944331306327, 30.1049295832445, 6.299226382436471, 29.954422532383525, 4.0686829203897235, 28.951642951534332);
+ outPath->cubicTo(1.838139458342976, 27.94886337068514, 0.05113662931485696, 26.161860541657013, -0.9516429515343354, 23.931317079610267);
+ outPath->cubicTo(-1.9544225323835278, 21.70077361756352, -2.1049295832444987, 19.178055668693663, -1.37447073281729, 16.844106035201087);
+ outPath->cubicTo(-0.6440118823900814, 14.51015640170851, 0.9178434513673546, 12.523358440585524, 3.0133358295984305, 11.262484649667876);
+ outPath->cubicTo(5.108828207829506, 10.001610858750228, 7.5957561396993984, 9.552224962671648, 10.000000000000005, 10.0);
+ outPath->cubicTo(10.0, 7.348852265086975, 11.054287646850167, 4.803576729418881, 12.928932188134523, 2.9289321881345254);
+ outPath->cubicTo(14.803576729418879, 1.0542876468501696, 17.348852265086972, 4.870079381441987E-16, 19.999999999999996, 0.0);
+ outPath->cubicTo(22.65114773491302, -4.870079381441987E-16, 25.19642327058112, 1.0542876468501678, 27.071067811865476, 2.9289321881345227);
+ outPath->cubicTo(28.94571235314983, 4.803576729418878, 30.0, 7.348852265086974, 30.0, 9.999999999999998);
+ outPath->cubicTo(30.0, 12.651147734913023, 28.94571235314983, 15.19642327058112, 27.071067811865476, 17.071067811865476);
+ outPath->cubicTo(25.19642327058112, 18.94571235314983, 22.651147734913028, 20.0, 20.000000000000004, 20.0);
+ }
+ },
+
+ // Random long data
+ {
+ // Path
+ "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z",
+ {
+ // Verbs
+ {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'},
+ // Verb sizes
+ {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
+ // Points
+ {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1, 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1, 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1, -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0, -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2},
+ },
+ [](SkPath* outPath) {
+ outPath->moveTo(5.3, 13.2);
+ outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1);
+ outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0);
+ outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5);
+ outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0);
+ outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4);
+ outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0);
+ outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2);
+ outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0);
+ outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0);
+ outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0);
+ outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2);
+ outPath->close();
+ outPath->moveTo(5.3, 13.2);
+ }
+ },
+
+ // Extreme case with numbers and decimal points crunched together
+ {
+ // Path
+ "l0.0.0.5.0.0.5-0.5.0.0-.5z",
+ {
+ // Verbs
+ {'l', 'z'},
+ // Verb sizes
+ {10, 0},
+ // Points
+ {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5},
+ },
+ [](SkPath* outPath) {
+ outPath->rLineTo(0.0, 0.0);
+ outPath->rLineTo(0.5, 0.0);
+ outPath->rLineTo(0.0, 0.5);
+ outPath->rLineTo(-0.5, 0.0);
+ outPath->rLineTo(0.0, -0.5);
+ outPath->close();
+ outPath->moveTo(0.0, 0.0);
+ }
+ },
+
+ // Empty test data
+ {
+ "",
+ {
+ // Verbs
+ {},
+ {},
+ {},
+ },
+ [](SkPath* outPath) {}
}
+
};
-static TestData testData2 {
- // Path
- "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10",
- {
- // Verbs
- {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A', 'a'},
- // VerbSizes
- {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7},
- // Points
- {1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, }
- },
- [](SkPath* outPath) {
- outPath->moveTo(1.0, 1.0);
- outPath->rMoveTo(2.0, 2.0);
- outPath->rLineTo(3.0, 3.0);
- outPath->lineTo(3.0, 3.0);
- outPath->lineTo(4.0, 3.0);
- outPath->rLineTo(4.0, 0);
- outPath->lineTo(8.0, 5.0);
- outPath->rLineTo(0, 5.0);
- outPath->quadTo(6.0, 6.0, 6.0, 6.0);
- outPath->rQuadTo(6.0, 6.0, 6.0, 6.0);
- outPath->rQuadTo(0.0, 0.0, 7.0, 7.0);
- outPath->quadTo(26.0, 26.0, 7.0, 7.0);
- outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
- outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
- outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0);
- outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0);
- outPath->cubicTo(18.447775037328352, 20.404243860300607, 17.998389141249767, 22.8911717921705, 16.737515350332117, 24.986664170401575);
- outPath->cubicTo(15.476641559414468, 27.08215654863265, 13.489843598291483, 28.644011882390082, 11.155893964798905, 29.37447073281729);
- outPath->cubicTo(8.821944331306327, 30.1049295832445, 6.299226382436471, 29.954422532383525, 4.0686829203897235, 28.951642951534332);
- outPath->cubicTo(1.838139458342976, 27.94886337068514, 0.05113662931485696, 26.161860541657013, -0.9516429515343354, 23.931317079610267);
- outPath->cubicTo(-1.9544225323835278, 21.70077361756352, -2.1049295832444987, 19.178055668693663, -1.37447073281729, 16.844106035201087);
- outPath->cubicTo(-0.6440118823900814, 14.51015640170851, 0.9178434513673546, 12.523358440585524, 3.0133358295984305, 11.262484649667876);
- outPath->cubicTo(5.108828207829506, 10.001610858750228, 7.5957561396993984, 9.552224962671648, 10.000000000000005, 10.0);
- outPath->cubicTo(10.0, 7.348852265086975, 11.054287646850167, 4.803576729418881, 12.928932188134523, 2.9289321881345254);
- outPath->cubicTo(14.803576729418879, 1.0542876468501696, 17.348852265086972, 4.870079381441987E-16, 19.999999999999996, 0.0);
- outPath->cubicTo(22.65114773491302, -4.870079381441987E-16, 25.19642327058112, 1.0542876468501678, 27.071067811865476, 2.9289321881345227);
- outPath->cubicTo(28.94571235314983, 4.803576729418878, 30.0, 7.348852265086974, 30.0, 9.999999999999998);
- outPath->cubicTo(30.0, 12.651147734913023, 28.94571235314983, 15.19642327058112, 27.071067811865476, 17.071067811865476);
- outPath->cubicTo(25.19642327058112, 18.94571235314983, 22.651147734913028, 20.0, 20.000000000000004, 20.0);
- }
+struct StringPath {
+ const char* stringPath;
+ bool isValid;
};
-static TestData testData3 {
- // Path
- "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z",
- {
- // Verbs
- {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'},
- // Verb sizes
- {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
- // Points
- {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1, 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1, 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1, -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0, -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2},
- },
- [](SkPath* outPath) {
- outPath->moveTo(5.3, 13.2);
- outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1);
- outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0);
- outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5);
- outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0);
- outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4);
- outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0);
- outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2);
- outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0);
- outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0);
- outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0);
- outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2);
- outPath->close();
- outPath->moveTo(5.3, 13.2);
- }
+const StringPath sStringPaths[] = {
+ {"3e...3", false},
+ {"L.M.F.A.O", false},
+ {"m 1 1", true},
+ {"z", true},
+ {"1-2e34567", false}
};
-static TestData testData4 {
- // Path
- "l0.0.0.5.0.0.5-0.5.0.0-.5z",
- {
- // Verbs
- {'l', 'z'},
- // Verb sizes
- {10, 0},
- // Points
- {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5},
- },
- [](SkPath* outPath) {
- outPath->rLineTo(0.0, 0.0);
- outPath->rLineTo(0.5, 0.0);
- outPath->rLineTo(0.0, 0.5);
- outPath->rLineTo(-0.5, 0.0);
- outPath->rLineTo(0.0, -0.5);
- outPath->close();
- outPath->moveTo(0.0, 0.0);
- }
-};
-
-const static TestData testDataSet[] = {testData1, testData2, testData3, testData4};
-
-TEST(PathPaser, parseString) {
-
- for (int i = 0; i < 4; i++) {
+TEST(PathParser, parseStringForData) {
+ for (TestData testData: sTestDataSet) {
+ PathParser::ParseResult result;
// Test generated path data against the given data.
PathData pathData;
- TestData testData = testDataSet[i];
size_t length = strlen(testData.pathString);
- PathParser::getPathDataFromString(&pathData, testData.pathString, length);
- PathParser::dump(pathData);
+ PathParser::getPathDataFromString(&pathData, &result, testData.pathString, length);
EXPECT_EQ(testData.pathData, pathData);
+ }
- // Test SkPath generated
+ for (StringPath stringPath : sStringPaths) {
+ PathParser::ParseResult result;
+ PathData pathData;
+ SkPath skPath;
+ PathParser::getPathDataFromString(&pathData, &result,
+ stringPath.stringPath, strlen(stringPath.stringPath));
+ EXPECT_EQ(stringPath.isValid, !result.failureOccurred);
+ }
+}
+
+TEST(PathParser, createSkPathFromPathData) {
+ for (TestData testData: sTestDataSet) {
SkPath expectedPath;
testData.skPathLamda(&expectedPath);
SkPath actualPath;
- VectorDrawablePath::verbsToPath(&actualPath, &pathData);
+ VectorDrawablePath::verbsToPath(&actualPath, &testData.pathData);
+ EXPECT_EQ(expectedPath, actualPath);
+ }
+}
+
+TEST(PathParser, parseStringForSkPath) {
+ for (TestData testData: sTestDataSet) {
+ PathParser::ParseResult result;
+ size_t length = strlen(testData.pathString);
+ // Check the return value as well as the SkPath generated.
+ SkPath actualPath;
+ PathParser::parseStringForSkPath(&actualPath, &result, testData.pathString, length);
+ bool hasValidData = !result.failureOccurred;
+ EXPECT_EQ(hasValidData, testData.pathData.verbs.size() > 0);
+ SkPath expectedPath;
+ testData.skPathLamda(&expectedPath);
EXPECT_EQ(expectedPath, actualPath);
}
+ for (StringPath stringPath : sStringPaths) {
+ PathParser::ParseResult result;
+ SkPath skPath;
+ PathParser::parseStringForSkPath(&skPath, &result, stringPath.stringPath,
+ strlen(stringPath.stringPath));
+ EXPECT_EQ(stringPath.isValid, !result.failureOccurred);
+ }
}
}; // namespace uirenderer
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 28e0fd8..efa28ae 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -97,25 +97,38 @@
return std::unique_ptr<DisplayList>(canvas.finishRecording());
}
- static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) {
+ typedef std::function<int(RenderProperties&)> PropSetupCallback;
+
+ static PropSetupCallback getHwLayerSetupCallback() {
+ static PropSetupCallback sLayerSetupCallback = [] (RenderProperties& properties) {
+ properties.mutateLayerProperties().setType(LayerType::RenderLayer);
+ return RenderNode::GENERIC;
+ };
+ return sLayerSetupCallback;
+ }
+
+ static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+ PropSetupCallback propSetupCallback = nullptr) {
+#if HWUI_NULL_GPU
// if RenderNodes are being sync'd/used, device info will be needed, since
// DeviceInfo::maxTextureSize() affects layer property
DeviceInfo::initialize();
+#endif
sp<RenderNode> node = new RenderNode();
node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- if (onLayer) {
- node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
- node->setPropertyFieldsDirty(RenderNode::GENERIC);
+ if (propSetupCallback) {
+ node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
}
return node;
}
template<class CanvasType>
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(CanvasType& canvas)> canvasCallback, bool onLayer = false) {
- sp<RenderNode> node = createNode(left, top, right, bottom, onLayer);
+ std::function<void(CanvasType& canvas)> canvasCallback,
+ PropSetupCallback propSetupCallback = nullptr) {
+ sp<RenderNode> node = createNode(left, top, right, bottom, propSetupCallback);
auto&& props = node->stagingProperties(); // staging, since not sync'd yet
CanvasType canvas(props.getWidth(), props.getHeight());
diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h
index 5ca9083..ccf2287 100644
--- a/libs/hwui/utils/Macros.h
+++ b/libs/hwui/utils/Macros.h
@@ -35,4 +35,7 @@
static_assert(std::is_standard_layout<Type>::value, \
#Type " must have standard layout")
+#define WARN_UNUSED_RESULT \
+ __attribute__((warn_unused_result))
+
#endif /* MACROS_H */
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index e0a8026..41b8ab2 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -44,7 +44,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
-import java.util.Collections;
import java.util.List;
/**
@@ -519,13 +518,10 @@
return;
}
- List<MediaItem> data = list.getList();
+ List<MediaItem> data = list == null ? null : list.getList();
if (DBG) {
Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
}
- if (data == null) {
- data = Collections.emptyList();
- }
// Check that the subscription is still subscribed.
final Subscription subscription = mSubscriptions.get(parentId);
@@ -730,10 +726,9 @@
* Called when the list of children is loaded or updated.
*
* @param parentId The media id of the parent media item.
- * @param children The children which were loaded.
+ * @param children The children which were loaded, or null if the id is invalid.
*/
- public void onChildrenLoaded(@NonNull String parentId,
- @NonNull List<MediaItem> children) {
+ public void onChildrenLoaded(@NonNull String parentId, List<MediaItem> children) {
}
/**
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 1bb99ff..f2c6a6c 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -469,10 +469,6 @@
= new Result<List<MediaBrowser.MediaItem>>(parentId) {
@Override
void onResultSent(List<MediaBrowser.MediaItem> list) {
- if (list == null) {
- throw new IllegalStateException("onLoadChildren sent null list for id "
- + parentId);
- }
if (mConnections.get(connection.callbacks.asBinder()) != connection) {
if (DBG) {
Log.d(TAG, "Not sending onLoadChildren result for connection that has"
@@ -481,7 +477,8 @@
return;
}
- final ParceledListSlice<MediaBrowser.MediaItem> pls = new ParceledListSlice(list);
+ final ParceledListSlice<MediaBrowser.MediaItem> pls =
+ list == null ? null : new ParceledListSlice(list);
try {
connection.callbacks.onLoadChildren(parentId, pls);
} catch (RemoteException ex) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 49dae3d..1b5b60de 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -117,6 +117,8 @@
public MotionInputEvent(MotionEvent event, RecyclerView view) {
mEvent = event;
mView = view;
+
+ // Consider determining position lazily as an optimization.
View child = mView.findChildViewUnder(mEvent.getX(), mEvent.getY());
mPosition = (child != null)
? mView.getChildAdapterPosition(child)
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 9eafcc3..65e1a28 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -41,10 +41,9 @@
import android.view.MotionEvent;
import android.view.View;
-import com.android.documentsui.Events;
-import com.android.documentsui.R;
import com.android.documentsui.Events.InputEvent;
import com.android.documentsui.Events.MotionInputEvent;
+import com.android.documentsui.R;
import java.util.ArrayList;
import java.util.Collections;
@@ -88,9 +87,7 @@
* @param mode Selection mode
*/
public MultiSelectManager(final RecyclerView recyclerView, int mode) {
- this(recyclerView.getAdapter(), mode);
-
- mEnvironment = new RuntimeSelectionEnvironment(recyclerView);
+ this(recyclerView.getAdapter(), new RuntimeSelectionEnvironment(recyclerView), mode);
if (mode == MODE_MULTIPLE) {
mBandManager = new BandController();
@@ -137,16 +134,15 @@
/**
* Constructs a new instance with {@code adapter} and {@code helper}.
+ * @param runtimeSelectionEnvironment
* @hide
*/
@VisibleForTesting
- MultiSelectManager(Adapter<?> adapter, int mode) {
- checkNotNull(adapter, "'adapter' cannot be null.");
-
+ MultiSelectManager(Adapter<?> adapter, SelectionEnvironment environment, int mode) {
+ mAdapter = checkNotNull(adapter, "'adapter' cannot be null.");
+ mEnvironment = checkNotNull(environment, "'environment' cannot be null.");
mSingleSelect = mode == MODE_SINGLE;
- mAdapter = adapter;
-
mAdapter.registerAdapterDataObserver(
new AdapterDataObserver() {
@@ -880,7 +876,7 @@
void focusItem(int position);
}
- /** RvFacade implementation backed by good ol' RecyclerView. */
+ /** Recycler view facade implementation backed by good ol' RecyclerView. */
private static final class RuntimeSelectionEnvironment implements SelectionEnvironment {
private final RecyclerView mView;
@@ -1960,11 +1956,50 @@
return false;
}
- int target = RecyclerView.NO_POSITION;
+ // Here we unpack information from the event and pass it to an more
+ // easily tested method....basically eliminating the need to synthesize
+ // events and views and so on in our tests.
+ int position = findTargetPosition(view, keyCode);
+ if (position == RecyclerView.NO_POSITION) {
+ // If there is no valid navigation target, don't handle the keypress.
+ return false;
+ }
+
+ return attemptChangePosition(position, event.isShiftPressed());
+ }
+
+ @VisibleForTesting
+ boolean attemptChangePosition(int targetPosition, boolean isShiftPressed) {
+ // Focus the new file.
+ mEnvironment.focusItem(targetPosition);
+
+ if (isShiftPressed) {
+ if (!hasSelection()) {
+ // If there is no selection, start a selection when the user presses shift-arrow.
+ toggleSelection(targetPosition);
+ } else if (!mSingleSelect) {
+ mRanger.snapSelection(targetPosition);
+ notifySelectionChanged();
+ } else {
+ // We're in single select and have an existing selection.
+ // Our best guess as to what the user would expect is to advance the selection.
+ clearSelection();
+ toggleSelection(targetPosition);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the adapter position that the key combo is targeted at.
+ */
+ private int findTargetPosition(View view, int keyCode) {
+ int position = RecyclerView.NO_POSITION;
if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
- target = 0;
+ position = 0;
} else if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
- target = mAdapter.getItemCount() - 1;
+ position = mAdapter.getItemCount() - 1;
} else {
// Find a navigation target based on the arrow key that the user pressed. Ignore
// navigation targets that aren't items in the recycler view.
@@ -1988,30 +2023,10 @@
// TargetView can be null, for example, if the user pressed <down> at the bottom of
// the list.
if (targetView != null) {
- target = mEnvironment.getAdapterPositionForChildView(targetView);
+ position = mEnvironment.getAdapterPositionForChildView(targetView);
}
}
}
-
- if (target == RecyclerView.NO_POSITION) {
- // If there is no valid navigation target, don't handle the keypress.
- return false;
- }
-
- // Focus the new file.
- mEnvironment.focusItem(target);
-
- if (event.isShiftPressed()) {
- if (!hasSelection()) {
- // If there is no selection, start a selection when the user presses shift-arrow.
- toggleSelection(mEnvironment.getAdapterPositionForChildView(view));
- }
-
- mRanger.snapSelection(target);
- notifySelectionChanged();
- }
-
- return true;
+ return position;
}
-
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index 6f1a89b..369ab7d 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -32,6 +32,8 @@
import android.test.MoreAsserts;
import android.test.ServiceTestCase;
import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import com.android.documentsui.model.DocumentInfo;
@@ -52,6 +54,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+@MediumTest
public class CopyTest extends ServiceTestCase<CopyService> {
public CopyTest() {
@@ -89,9 +92,6 @@
super.tearDown();
}
- /**
- * Test copying a single file.
- */
public void testCopyFile() throws Exception {
String srcPath = "/test0.txt";
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
@@ -131,9 +131,6 @@
MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
}
- /**
- * Test copying multiple files.
- */
public void testCopyMultipleFiles() throws Exception {
String testContent[] = {
"The five boxing wizards jump quickly",
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 9060516..ba91c83 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -31,11 +31,13 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.MotionEvent;
import com.android.documentsui.model.RootInfo;
+@LargeTest
public class FilesActivityUiTest extends InstrumentationTestCase {
private static final int TIMEOUT = 5000;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
index 746e2117..b250e5d 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
@@ -25,6 +25,7 @@
import android.support.v7.widget.RecyclerView;
import android.test.AndroidTestCase;
import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.ViewGroup;
import com.android.documentsui.DirectoryResult;
@@ -34,6 +35,7 @@
import java.util.List;
+@SmallTest
public class DirectoryFragmentModelTest extends AndroidTestCase {
private static final int ITEM_COUNT = 5;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index 24f5c9e..b3d45ae 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -18,12 +18,12 @@
import android.support.v7.widget.RecyclerView;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.ViewGroup;
import com.android.documentsui.TestInputEvent;
-import com.android.documentsui.dirlist.MultiSelectManager;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import org.mockito.Mockito;
@@ -33,6 +33,7 @@
import java.util.List;
import java.util.Set;
+@SmallTest
public class MultiSelectManagerTest extends AndroidTestCase {
private static final List<String> items;
@@ -49,11 +50,13 @@
private MultiSelectManager mManager;
private TestAdapter mAdapter;
private TestCallback mCallback;
+ private TestSelectionEnvironment mEnv;
public void setUp() throws Exception {
mAdapter = new TestAdapter(items);
mCallback = new TestCallback();
- mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+ mEnv = new TestSelectionEnvironment();
+ mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_MULTIPLE);
mManager.addCallback(mCallback);
}
@@ -162,7 +165,6 @@
assertRangeSelection(14, 17);
}
-
public void testSingleTapUp_ShiftReversesSelectionDirection() {
longPress(7);
shiftTap(17);
@@ -171,7 +173,7 @@
}
public void testSingleSelectMode() {
- mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
+ mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
mManager.addCallback(mCallback);
longPress(20);
tap(13);
@@ -179,13 +181,21 @@
}
public void testSingleSelectMode_ShiftTap() {
- mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
+ mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
mManager.addCallback(mCallback);
longPress(13);
shiftTap(20);
assertSelection(20);
}
+ public void testSingleSelectMode_ShiftDoesNotExtendSelection() {
+ mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
+ mManager.addCallback(mCallback);
+ longPress(20);
+ keyToPosition(22, true);
+ assertSelection(22);
+ }
+
public void testProvisionalSelection() {
Selection s = mManager.getSelection();
assertSelection();
@@ -235,6 +245,10 @@
mManager.onSingleTapUp(TestInputEvent.shiftClick(position));
}
+ private void keyToPosition(int position, boolean shift) {
+ mManager.attemptChangePosition(position, shift);
+ }
+
private void assertSelected(int... expected) {
for (int i = 0; i < expected.length; i++) {
Selection selection = mManager.getSelection();
@@ -290,11 +304,8 @@
private static final class TestHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
- public View view;
- public String string;
public TestHolder(View view) {
super(view);
- this.view = view;
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index c4b6ce5..c856b22 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -20,11 +20,13 @@
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView.OnScrollListener;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.SparseBooleanArray;
import android.view.View;
import com.android.documentsui.dirlist.MultiSelectManager.GridModel;
+@SmallTest
public class MultiSelectManager_GridModelTest extends AndroidTestCase {
private static final int VIEW_PADDING_PX = 5;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
index 64da750..72fc108 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
@@ -17,10 +17,11 @@
package com.android.documentsui.dirlist;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
-
+@SmallTest
public class MultiSelectManager_SelectionTest extends AndroidTestCase{
private Selection selection;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
new file mode 100644
index 0000000..b4324a8
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
@@ -0,0 +1,108 @@
+/*
+ * 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.documentsui.dirlist;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.view.View;
+
+import com.android.documentsui.dirlist.MultiSelectManager.SelectionEnvironment;
+
+public class TestSelectionEnvironment implements SelectionEnvironment {
+
+ @Override
+ public void showBand(Rect rect) {
+ }
+
+ @Override
+ public void hideBand() {
+ }
+
+ @Override
+ public void addOnScrollListener(OnScrollListener listener) {
+ }
+
+ @Override
+ public void removeOnScrollListener(OnScrollListener listener) {
+ }
+
+ @Override
+ public void scrollBy(int dy) {
+ }
+
+ @Override
+ public int getHeight() {
+ return 0;
+ }
+
+ @Override
+ public void invalidateView() {
+ }
+
+ @Override
+ public void runAtNextFrame(Runnable r) {
+ }
+
+ @Override
+ public void removeCallback(Runnable r) {
+ }
+
+ @Override
+ public Point createAbsolutePoint(Point relativePoint) {
+ return null;
+ }
+
+ @Override
+ public Rect getAbsoluteRectForChildViewAt(int index) {
+ return null;
+ }
+
+ @Override
+ public int getAdapterPositionAt(int index) {
+ return 0;
+ }
+
+ @Override
+ public int getAdapterPositionForChildView(View view) {
+ return 0;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 0;
+ }
+
+ @Override
+ public int getRowCount() {
+ return 0;
+ }
+
+ @Override
+ public int getChildCount() {
+ return 0;
+ }
+
+ @Override
+ public int getVisibleChildCount() {
+ return 0;
+ }
+
+ @Override
+ public void focusItem(int position) {
+ }
+}
diff --git a/packages/Keyguard/res/values-ja/strings.xml b/packages/Keyguard/res/values-ja/strings.xml
index cea4319..3230beb 100644
--- a/packages/Keyguard/res/values-ja/strings.xml
+++ b/packages/Keyguard/res/values-ja/strings.xml
@@ -110,8 +110,8 @@
<string name="keyguard_carrier_default" msgid="8700650403054042153">"通信サービスはありません。"</string>
<string name="accessibility_ime_switch_button" msgid="5032926134740456424">"入力方法の切り替えボタン。"</string>
<string name="airplane_mode" msgid="3122107900897202805">"機内モード"</string>
- <string name="kg_prompt_reason_restart_pattern" msgid="489430505491862444">"端末を再起動するにはパターンが必要です。"</string>
- <string name="kg_prompt_reason_restart_pin" msgid="994878216570694974">"端末を再起動するにはPINが必要です。"</string>
+ <string name="kg_prompt_reason_restart_pattern" msgid="489430505491862444">"端末を再起動した時にはパターンが必要です。"</string>
+ <string name="kg_prompt_reason_restart_pin" msgid="994878216570694974">"端末を再起動した時にはPINが必要です。"</string>
<string name="kg_prompt_reason_restart_password" msgid="2375742919528461664">"端末を再起動した時にはパスワードが必要です。"</string>
<string name="kg_prompt_reason_timeout_pattern" msgid="8930047492617900785">"セキュリティを強化するため、パターンが必要です。"</string>
<string name="kg_prompt_reason_timeout_pin" msgid="7470468607947726377">"セキュリティを強化するため、PINが必要です。"</string>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index f95b0ae..9d1df26 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,7 +18,6 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -198,12 +197,18 @@
}
private String getOwnerInfo() {
- ContentResolver res = getContext().getContentResolver();
String info = null;
- final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
- KeyguardUpdateMonitor.getCurrentUser());
- if (ownerInfoEnabled) {
- info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+ if (mLockPatternUtils.isDeviceOwnerInfoEnabled()) {
+ // Use the device owner information set by device policy client via
+ // device policy manager.
+ info = mLockPatternUtils.getDeviceOwnerInfo();
+ } else {
+ // Use the current user owner information if enabled.
+ final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (ownerInfoEnabled) {
+ info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+ }
}
return info;
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 0f31e2c..3151ccb 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -16,22 +16,20 @@
package com.android.mtp;
+import static com.android.mtp.MtpDatabaseConstants.*;
+
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.Objects;
+import java.util.HashMap;
+import java.util.Map;
/**
* Database for MTP objects.
@@ -46,188 +44,137 @@
* remembers the map of document ID and object handle, and remaps new object handle with document ID
* by comparing the directory structure and object name.
*
+ * To start putting documents into the database, the client needs to call
+ * {@link #startAddingChildDocuments(String)} with the parent document ID. Also it needs to call
+ * {@link #stopAddingChildDocuments(String)} after putting all child documents to the database.
+ * (All explanations are same for root documents)
+ *
+ * database.startAddingChildDocuments();
+ * database.putChildDocuments();
+ * database.stopAddingChildDocuments();
+ *
+ * To update the existing documents, the client code can repeat to call the three methods again.
+ * The newly added rows update corresponding existing rows that have same MTP identifier like
+ * objectHandle.
+ *
+ * The client can call putChildDocuments multiple times to add documents by chunk, but it needs to
+ * put all documents under the parent before calling stopAddingChildDocuments. Otherwise missing
+ * documents are regarded as deleted, and will be removed from the database.
+ *
+ * If the client calls clearMtpIdentifier(), it clears MTP identifier in the database. In this case,
+ * the database tries to find corresponding rows by using document's name instead of MTP identifier
+ * at the next update cycle.
+ *
* TODO: Remove @VisibleForTesting annotation when we start to use this class.
* TODO: Improve performance by SQL optimization.
*/
@VisibleForTesting
class MtpDatabase {
- private static final int VERSION = 1;
- private static final String NAME = "mtp";
+ private final MtpDatabaseInternal mDatabase;
/**
- * Table representing documents including root documents.
+ * Mapping mode for roots/documents where we start adding child documents.
+ * Methods operate the state needs to be synchronized.
*/
- private static final String TABLE_DOCUMENTS = "Documents";
-
- /**
- * Table containing additional information only available for root documents.
- * The table uses same primary keys with corresponding documents.
- */
- private static final String TABLE_ROOT_EXTRA = "RootExtra";
-
- /**
- * View to join Documents and RootExtra tables to provide roots information.
- */
- private static final String VIEW_ROOTS = "Roots";
-
- static final String COLUMN_DEVICE_ID = "device_id";
- static final String COLUMN_STORAGE_ID = "storage_id";
- static final String COLUMN_OBJECT_HANDLE = "object_handle";
- static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id";
- static final String COLUMN_ROW_STATE = "row_state";
-
- /**
- * The state represents that the row has a valid object handle.
- */
- static final int ROW_STATE_MAPPED = 0;
-
- /**
- * The state represents that the object handle was cleared because the MTP session closed.
- * External application can still fetch the unmapped documents. If the external application
- * tries to open an unmapped document, the provider resolves the document with new object handle
- * ahead.
- */
- static final int ROW_STATE_UNMAPPED = 1;
-
- /**
- * The state represents the raw has a valid object handle but it may be going to be merged into
- * another unmapped row. After fetching all documents under the parent, the database tries to
- * map the mapping document and the unmapped document in order to keep old document ID alive.
- */
- static final int ROW_STATE_MAPPING = 2;
-
- private static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?";
- private static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?";
- private static final String SELECTION_ROOT_DOCUMENTS =
- COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
- private static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
-
- static class ParentNotFoundException extends Exception {}
-
- private static class OpenHelper extends SQLiteOpenHelper {
- private static final String QUERY_CREATE_DOCUMENTS =
- "CREATE TABLE " + TABLE_DOCUMENTS + " (" +
- Document.COLUMN_DOCUMENT_ID +
- " INTEGER PRIMARY KEY AUTOINCREMENT," +
- COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
- COLUMN_STORAGE_ID + " INTEGER," +
- COLUMN_OBJECT_HANDLE + " INTEGER," +
- COLUMN_PARENT_DOCUMENT_ID + " INTEGER," +
- COLUMN_ROW_STATE + " INTEGER NOT NULL," +
- Document.COLUMN_MIME_TYPE + " TEXT," +
- Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
- Document.COLUMN_SUMMARY + " TEXT," +
- Document.COLUMN_LAST_MODIFIED + " INTEGER," +
- Document.COLUMN_ICON + " INTEGER," +
- Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
- Document.COLUMN_SIZE + " INTEGER NOT NULL);";
-
- private static final String QUERY_CREATE_ROOT_EXTRA =
- "CREATE TABLE " + TABLE_ROOT_EXTRA + " (" +
- Root.COLUMN_ROOT_ID + " INTEGER PRIMARY KEY," +
- Root.COLUMN_FLAGS + " INTEGER NOT NULL," +
- Root.COLUMN_AVAILABLE_BYTES + " INTEGER NOT NULL," +
- Root.COLUMN_CAPACITY_BYTES + " INTEGER NOT NULL," +
- Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);";
-
- /**
- * Creates a view to join Documents table and RootExtra table on their primary keys to
- * provide DocumentContract.Root equivalent information.
- */
- private static final String QUERY_CREATE_VIEW_ROOTS =
- "CREATE VIEW " + VIEW_ROOTS + " AS SELECT " +
- TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
- Root.COLUMN_ROOT_ID + "," +
- TABLE_ROOT_EXTRA + "." + Root.COLUMN_FLAGS + "," +
- TABLE_DOCUMENTS + "." + Document.COLUMN_ICON + " AS " +
- Root.COLUMN_ICON + "," +
- TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME + " AS " +
- Root.COLUMN_TITLE + "," +
- TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
- Root.COLUMN_SUMMARY + "," +
- TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
- Root.COLUMN_DOCUMENT_ID + "," +
- TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
- TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +
- TABLE_ROOT_EXTRA + "." + Root.COLUMN_MIME_TYPES + "," +
- TABLE_DOCUMENTS + "." + COLUMN_ROW_STATE +
- " FROM " + TABLE_DOCUMENTS + " INNER JOIN " + TABLE_ROOT_EXTRA +
- " ON " +
- COLUMN_PARENT_DOCUMENT_ID + " IS NULL AND " +
- TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID +
- "=" +
- TABLE_ROOT_EXTRA + "." + Root.COLUMN_ROOT_ID;
-
- public OpenHelper(Context context) {
- super(context, NAME, null, VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(QUERY_CREATE_DOCUMENTS);
- db.execSQL(QUERY_CREATE_ROOT_EXTRA);
- db.execSQL(QUERY_CREATE_VIEW_ROOTS);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- throw new UnsupportedOperationException();
- }
- }
-
- private final SQLiteDatabase mDatabase;
+ private final Map<String, Integer> mMappingMode = new HashMap<>();
@VisibleForTesting
MtpDatabase(Context context) {
- final OpenHelper helper = new OpenHelper(context);
- mDatabase = helper.getWritableDatabase();
+ mDatabase = new MtpDatabaseInternal(context);
}
+ /**
+ * Closes the database.
+ */
@VisibleForTesting
- static void deleteDatabase(Context context) {
- SQLiteDatabase.deleteDatabase(context.getDatabasePath(NAME));
+ void close() {
+ mDatabase.close();
}
- @VisibleForTesting
+ /**
+ * {@link MtpDatabaseInternal#queryRoots}
+ */
Cursor queryRoots(String[] columnNames) {
- return mDatabase.query(
- VIEW_ROOTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?)",
- strings(ROW_STATE_MAPPED, ROW_STATE_UNMAPPED),
- null,
- null,
- null);
+ return mDatabase.queryRoots(columnNames);
}
+ /**
+ * {@link MtpDatabaseInternal#queryRootDocuments}
+ */
@VisibleForTesting
Cursor queryRootDocuments(String[] columnNames) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?)",
- strings(ROW_STATE_MAPPED, ROW_STATE_UNMAPPED),
- null,
- null,
- null);
+ return mDatabase.queryRootDocuments(columnNames);
}
+ /**
+ * {@link MtpDatabaseInternal#queryChildDocuments}
+ */
@VisibleForTesting
Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
- strings(ROW_STATE_MAPPED, ROW_STATE_UNMAPPED, parentDocumentId),
- null,
- null,
- null);
+ return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
}
+ /**
+ * {@link MtpDatabaseInternal#removeDeviceRows}
+ */
+ void removeDeviceRows(int deviceId) {
+ mDatabase.removeDeviceRows(deviceId);
+ }
+
+ /**
+ * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents.
+ * @param deviceId Device ID.
+ */
+ synchronized void startAddingRootDocuments(int deviceId) {
+ final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
+ if (mMappingMode.containsKey(mappingStateKey)) {
+ throw new Error("Mapping for the root has already started.");
+ }
+ mMappingMode.put(
+ mappingStateKey,
+ mDatabase.startAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
+ }
+
+ /**
+ * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for child of specific documents.
+ * @param parentDocumentId Document ID for parent document.
+ */
@VisibleForTesting
- void putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
+ synchronized void startAddingChildDocuments(String parentDocumentId) {
+ final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
+ if (mMappingMode.containsKey(mappingStateKey)) {
+ throw new Error("Mapping for the root has already started.");
+ }
+ mMappingMode.put(
+ mappingStateKey,
+ mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
+ }
+
+ /**
+ * Puts root information to database.
+ * @param deviceId Device ID
+ * @param resources Resources required to localize root name.
+ * @param roots List of root information.
+ * @return If roots are added or removed from the database.
+ */
+ synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
mDatabase.beginTransaction();
try {
+ final boolean heuristic;
+ final String mapColumn;
+ switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) {
+ case MAP_BY_MTP_IDENTIFIER:
+ heuristic = false;
+ mapColumn = COLUMN_STORAGE_ID;
+ break;
+ case MAP_BY_NAME:
+ heuristic = true;
+ mapColumn = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected map mode.");
+ }
final ContentValues[] valuesList = new ContentValues[roots.length];
for (int i = 0; i < roots.length; i++) {
if (roots[i].mDeviceId != deviceId) {
@@ -236,200 +183,123 @@
valuesList[i] = new ContentValues();
getRootDocumentValues(valuesList[i], resources, roots[i]);
}
- final long[] documentIds =
- putDocuments(valuesList, SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId));
+ final boolean changed = mDatabase.putDocuments(
+ valuesList,
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ heuristic,
+ mapColumn);
final ContentValues values = new ContentValues();
int i = 0;
for (final MtpRoot root : roots) {
// Use the same value for the root ID and the corresponding document ID.
- values.put(Root.COLUMN_ROOT_ID, documentIds[i++]);
- values.put(Root.COLUMN_FLAGS,
- Root.FLAG_SUPPORTS_IS_CHILD |
- Root.FLAG_SUPPORTS_CREATE);
+ values.put(
+ Root.COLUMN_ROOT_ID,
+ valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID));
+ values.put(
+ Root.COLUMN_FLAGS,
+ Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
values.put(Root.COLUMN_MIME_TYPES, "");
- mDatabase.insert(TABLE_ROOT_EXTRA, null, values);
+ mDatabase.putRootExtra(values);
}
mDatabase.setTransactionSuccessful();
+ return changed;
} finally {
mDatabase.endTransaction();
}
}
+ /**
+ * Puts document information to database.
+ * @param deviceId Device ID
+ * @param parentId Parent document ID.
+ * @param documents List of document information.
+ */
@VisibleForTesting
- void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
+ synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
+ final boolean heuristic;
+ final String mapColumn;
+ switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) {
+ case MAP_BY_MTP_IDENTIFIER:
+ heuristic = false;
+ mapColumn = COLUMN_OBJECT_HANDLE;
+ break;
+ case MAP_BY_NAME:
+ heuristic = true;
+ mapColumn = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected map mode.");
+ }
final ContentValues[] valuesList = new ContentValues[documents.length];
for (int i = 0; i < documents.length; i++) {
valuesList[i] = new ContentValues();
getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
}
- putDocuments(valuesList, SELECTION_CHILD_DOCUMENTS, parentId);
+ mDatabase.putDocuments(
+ valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
}
/**
- * Clears MTP related identifier.
- * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
- * the all documents as 'unmapped'. It also remove 'mapping' rows as mapping is cancelled now.
+ * Clears mapping between MTP identifier and document/root ID.
*/
@VisibleForTesting
- void clearMapping() {
- mDatabase.beginTransaction();
- try {
- deleteDocumentsAndRoots(COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_MAPPING));
- final ContentValues values = new ContentValues();
- values.putNull(COLUMN_OBJECT_HANDLE);
- values.putNull(COLUMN_STORAGE_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_UNMAPPED);
- mDatabase.update(TABLE_DOCUMENTS, values, null, null);
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- @VisibleForTesting
- void resolveRootDocuments(int deviceId) {
- resolveDocuments(SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId));
- }
-
- @VisibleForTesting
- void resolveChildDocuments(String parentId) {
- resolveDocuments(SELECTION_CHILD_DOCUMENTS, parentId);
+ synchronized void clearMapping() {
+ mDatabase.clearMapping();
+ mMappingMode.clear();
}
/**
- * Puts the documents into the database.
- * If the database found another unmapped document that shares the same name and parent,
- * the document may be merged into the unmapped document. In that case, the database marks the
- * root as 'mapping' and wait for {@link #resolveRootDocuments(int)} is invoked.
- * @param valuesList Values that are stored in the database.
- * @param selection SQL where closure to select rows that shares the same parent.
- * @param arg Argument for selection SQL.
- * @return List of Document ID inserted to the table.
+ * Stops adding root documents.
+ * @param deviceId Device ID.
+ * @return True if new rows are added/removed.
*/
- private long[] putDocuments(ContentValues[] valuesList, String selection, String arg) {
- mDatabase.beginTransaction();
- try {
- final long[] documentIds = new long[valuesList.length];
- int i = 0;
- for (final ContentValues values : valuesList) {
- final String displayName =
- values.getAsString(Document.COLUMN_DISPLAY_NAME);
- final long numUnmapped = DatabaseUtils.queryNumEntries(
- mDatabase,
- TABLE_DOCUMENTS,
- selection + " AND " +
- COLUMN_ROW_STATE + " = ? AND " +
- Document.COLUMN_DISPLAY_NAME + " = ?",
- strings(arg, ROW_STATE_UNMAPPED, displayName));
- if (numUnmapped != 0) {
- values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPING);
- }
- // Document ID is a primary integer key of the table. So the returned row IDs should
- // be same with the document ID.
- documentIds[i++] = mDatabase.insert(TABLE_DOCUMENTS, null, values);
- }
-
- mDatabase.setTransactionSuccessful();
- return documentIds;
- } finally {
- mDatabase.endTransaction();
+ synchronized boolean stopAddingRootDocuments(int deviceId) {
+ final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId);
+ switch (mMappingMode.get(mappingModeKey)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ mMappingMode.remove(mappingModeKey);
+ return mDatabase.stopAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ COLUMN_STORAGE_ID);
+ case MAP_BY_NAME:
+ mMappingMode.remove(mappingModeKey);
+ return mDatabase.stopAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ Document.COLUMN_DISPLAY_NAME);
+ default:
+ throw new Error("Unexpected mapping state.");
}
}
/**
- * Maps 'unmapped' document and 'mapping' document that don't have document but shares the same
- * name.
- * If the database does not find corresponding 'mapping' document, it just removes 'unmapped'
- * document from the database.
- * @param selection Query to select rows for resolving.
- * @param arg Argument for selection SQL.
+ * Stops adding documents under the parent.
+ * @param parentId Document ID of the parent.
*/
- private void resolveDocuments(String selection, String arg) {
- mDatabase.beginTransaction();
- try {
- // Get 1-to-1 mapping of unmapped document and mapping document.
- final String unmappedIdQuery = createStateFilter(
- ROW_STATE_UNMAPPED, Document.COLUMN_DOCUMENT_ID);
- final String mappingIdQuery = createStateFilter(
- ROW_STATE_MAPPING, Document.COLUMN_DOCUMENT_ID);
- // SQL should be like:
- // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
- // group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
- // WHERE device_id = ? AND parent_document_id IS NULL
- // GROUP BY display_name
- // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
- // count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
- final Cursor mergingCursor = mDatabase.query(
- TABLE_DOCUMENTS,
- new String[] {
- "group_concat(" + unmappedIdQuery + ")",
- "group_concat(" + mappingIdQuery + ")"
- },
- selection,
- strings(arg),
- Document.COLUMN_DISPLAY_NAME,
- "count(" + unmappedIdQuery + ") = 1 AND count(" + mappingIdQuery + ") = 1",
- null);
-
- final ContentValues values = new ContentValues();
- while (mergingCursor.moveToNext()) {
- final String unmappedId = mergingCursor.getString(0);
- final String mappingId = mergingCursor.getString(1);
-
- // Obtain the new values including the latest object handle from mapping row.
- getFirstRow(
- TABLE_DOCUMENTS,
- SELECTION_DOCUMENT_ID,
- new String[] { mappingId },
- values);
- values.remove(Document.COLUMN_DOCUMENT_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
- mDatabase.update(
- TABLE_DOCUMENTS,
- values,
- SELECTION_DOCUMENT_ID,
- new String[] { unmappedId });
-
- getFirstRow(
- TABLE_ROOT_EXTRA,
- SELECTION_ROOT_ID,
- new String[] { mappingId },
- values);
- if (values.size() > 0) {
- values.remove(Root.COLUMN_ROOT_ID);
- mDatabase.update(
- TABLE_ROOT_EXTRA,
- values,
- SELECTION_ROOT_ID,
- new String[] { unmappedId });
- }
-
- // Delete 'mapping' row.
- deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { mappingId });
- }
- mergingCursor.close();
-
- // Delete all unmapped rows that cannot be mapped.
- deleteDocumentsAndRoots(
- COLUMN_ROW_STATE + " = ? AND " + selection,
- strings(ROW_STATE_UNMAPPED, arg));
-
- // The database cannot find old document ID for the mapping rows.
- // Turn the all mapping rows into mapped state, which means the rows become to be
- // valid with new document ID.
- values.clear();
- values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
- mDatabase.update(
- TABLE_DOCUMENTS,
- values,
- COLUMN_ROW_STATE + " = ? AND " + selection,
- strings(ROW_STATE_MAPPING, arg));
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
+ @VisibleForTesting
+ synchronized void stopAddingChildDocuments(String parentId) {
+ final String mappingModeKey = getChildDocumentsMappingStateKey(parentId);
+ switch (mMappingMode.get(mappingModeKey)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ mDatabase.stopAddingDocuments(
+ SELECTION_CHILD_DOCUMENTS,
+ parentId,
+ COLUMN_OBJECT_HANDLE);
+ break;
+ case MAP_BY_NAME:
+ mDatabase.stopAddingDocuments(
+ SELECTION_CHILD_DOCUMENTS,
+ parentId,
+ Document.COLUMN_DISPLAY_NAME);
+ break;
+ default:
+ throw new Error("Unexpected mapping state.");
}
+ mMappingMode.remove(mappingModeKey);
}
/**
@@ -445,7 +315,7 @@
values.put(COLUMN_STORAGE_ID, root.mStorageId);
values.putNull(COLUMN_OBJECT_HANDLE);
values.putNull(COLUMN_PARENT_DOCUMENT_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
values.put(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
values.putNull(Document.COLUMN_SUMMARY);
@@ -482,7 +352,7 @@
values.put(COLUMN_STORAGE_ID, info.getStorageId());
values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
values.put(COLUMN_PARENT_DOCUMENT_ID, parentId);
- values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
values.put(Document.COLUMN_MIME_TYPE, mimeType);
values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
values.putNull(Document.COLUMN_SUMMARY);
@@ -495,72 +365,18 @@
}
/**
- * Obtains values of the first row for the query.
- * @param values ContentValues that the values are stored to.
- * @param table Target table.
- * @param selection Query to select rows.
- * @param args Argument for query.
+ * @param deviceId Device ID.
+ * @return Key for {@link #mMappingMode}.
*/
- private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
- values.clear();
- final Cursor cursor = mDatabase.query(table, null, selection, args, null, null, null, "1");
- if (cursor.getCount() == 0) {
- return;
- }
- cursor.moveToNext();
- DatabaseUtils.cursorRowToContentValues(cursor, values);
- cursor.close();
+ private static String getRootDocumentsMappingStateKey(int deviceId) {
+ return "RootDocuments/" + deviceId;
}
/**
- * Deletes a document, and its root information if the document is a root document.
- * @param selection Query to select documents.
- * @param args Arguments for selection.
+ * @param parentDocumentId Document ID for the parent document.
+ * @return Key for {@link #mMappingMode}.
*/
- private void deleteDocumentsAndRoots(String selection, String[] args) {
- mDatabase.beginTransaction();
- try {
- mDatabase.delete(
- TABLE_ROOT_EXTRA,
- Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
- false,
- TABLE_DOCUMENTS,
- new String[] { Document.COLUMN_DOCUMENT_ID },
- selection,
- null,
- null,
- null,
- null) + ")",
- args);
- mDatabase.delete(TABLE_DOCUMENTS, selection, args);
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Converts values into string array.
- * @param args Values converted into string array.
- * @return String array.
- */
- private static String[] strings(Object... args) {
- final String[] results = new String[args.length];
- for (int i = 0; i < args.length; i++) {
- results[i] = Objects.toString(args[i]);
- }
- return results;
- }
-
- /**
- * Gets SQL expression that represents the given value or NULL depends on the row state.
- * @param state Expected row state.
- * @param a SQL value.
- * @return Expression that represents a if the row state is expected one, and represents NULL
- * otherwise.
- */
- private static String createStateFilter(int state, String a) {
- return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
- " THEN " + a + " ELSE NULL END";
+ private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
+ return "ChildDocuments/" + parentDocumentId;
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
new file mode 100644
index 0000000..977b12e
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -0,0 +1,145 @@
+/*
+ * 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.mtp;
+
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+/**
+ * Class containing MtpDatabase constants.
+ */
+class MtpDatabaseConstants {
+ static final int DATABASE_VERSION = 1;
+ static final String DATABASE_NAME = null;
+
+ /**
+ * Table representing documents including root documents.
+ */
+ static final String TABLE_DOCUMENTS = "Documents";
+
+ /**
+ * Table containing additional information only available for root documents.
+ * The table uses same primary keys with corresponding documents.
+ */
+ static final String TABLE_ROOT_EXTRA = "RootExtra";
+
+ /**
+ * View to join Documents and RootExtra tables to provide roots information.
+ */
+ static final String VIEW_ROOTS = "Roots";
+
+ static final String COLUMN_DEVICE_ID = "device_id";
+ static final String COLUMN_STORAGE_ID = "storage_id";
+ static final String COLUMN_OBJECT_HANDLE = "object_handle";
+ static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id";
+ static final String COLUMN_ROW_STATE = "row_state";
+
+ /**
+ * The state represents that the row has a valid object handle.
+ */
+ static final int ROW_STATE_VALID = 0;
+
+ /**
+ * The state represents that the rows added at the previous cycle and need to be updated with
+ * fresh values.
+ * The row may not have valid object handle. External application can still fetch the documents.
+ * If the external application tries to fetch object handle, the provider resolves pending
+ * documents with invalidated documents ahead.
+ */
+ static final int ROW_STATE_INVALIDATED = 1;
+
+ /**
+ * The state represents the raw has a valid object handle but it may be going to be mapped with
+ * another rows invalidated. After fetching all documents under the parent, the database tries
+ * to map the pending documents and the invalidated documents in order to keep old document ID
+ * alive.
+ */
+ static final int ROW_STATE_PENDING = 2;
+
+ /**
+ * Mapping mode that uses MTP identifier to find corresponding rows.
+ */
+ static final int MAP_BY_MTP_IDENTIFIER = 0;
+
+ /**
+ * Mapping mode that uses name to find corresponding rows.
+ */
+ static final int MAP_BY_NAME = 1;
+
+ static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?";
+ static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?";
+ static final String SELECTION_ROOT_DOCUMENTS =
+ COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+ static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
+
+ static final String QUERY_CREATE_DOCUMENTS =
+ "CREATE TABLE " + TABLE_DOCUMENTS + " (" +
+ Document.COLUMN_DOCUMENT_ID +
+ " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
+ COLUMN_STORAGE_ID + " INTEGER," +
+ COLUMN_OBJECT_HANDLE + " INTEGER," +
+ COLUMN_PARENT_DOCUMENT_ID + " INTEGER," +
+ COLUMN_ROW_STATE + " INTEGER NOT NULL," +
+ Document.COLUMN_MIME_TYPE + " TEXT," +
+ Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
+ Document.COLUMN_SUMMARY + " TEXT," +
+ Document.COLUMN_LAST_MODIFIED + " INTEGER," +
+ Document.COLUMN_ICON + " INTEGER," +
+ Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
+ Document.COLUMN_SIZE + " INTEGER NOT NULL);";
+
+ static final String QUERY_CREATE_ROOT_EXTRA =
+ "CREATE TABLE " + TABLE_ROOT_EXTRA + " (" +
+ Root.COLUMN_ROOT_ID + " INTEGER PRIMARY KEY," +
+ Root.COLUMN_FLAGS + " INTEGER NOT NULL," +
+ Root.COLUMN_AVAILABLE_BYTES + " INTEGER NOT NULL," +
+ Root.COLUMN_CAPACITY_BYTES + " INTEGER NOT NULL," +
+ Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);";
+
+ /**
+ * Creates a view to join Documents table and RootExtra table on their primary keys to
+ * provide DocumentContract.Root equivalent information.
+ */
+ static final String QUERY_CREATE_VIEW_ROOTS =
+ "CREATE VIEW " + VIEW_ROOTS + " AS SELECT " +
+ TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
+ Root.COLUMN_ROOT_ID + "," +
+ TABLE_ROOT_EXTRA + "." + Root.COLUMN_FLAGS + "," +
+ TABLE_DOCUMENTS + "." + Document.COLUMN_ICON + " AS " +
+ Root.COLUMN_ICON + "," +
+ TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME + " AS " +
+ Root.COLUMN_TITLE + "," +
+ TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
+ Root.COLUMN_SUMMARY + "," +
+ // Temporary replace COLUMN_DOCUMENT_ID with old format.
+ TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
+ Root.COLUMN_DOCUMENT_ID + "_," +
+ TABLE_DOCUMENTS + "." + COLUMN_DEVICE_ID + "|| '_' ||" +
+ TABLE_DOCUMENTS + "." + COLUMN_STORAGE_ID + "||'_0' AS " +
+ Root.COLUMN_DOCUMENT_ID + "," +
+ TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
+ TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +
+ TABLE_ROOT_EXTRA + "." + Root.COLUMN_MIME_TYPES + "," +
+ TABLE_DOCUMENTS + "." + COLUMN_ROW_STATE +
+ " FROM " + TABLE_DOCUMENTS + " INNER JOIN " + TABLE_ROOT_EXTRA +
+ " ON " +
+ COLUMN_PARENT_DOCUMENT_ID + " IS NULL AND " +
+ TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID +
+ "=" +
+ TABLE_ROOT_EXTRA + "." + Root.COLUMN_ROOT_ID;
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
new file mode 100644
index 0000000..7328f05
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
@@ -0,0 +1,444 @@
+/*
+ * 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.mtp;
+
+import static com.android.mtp.MtpDatabaseConstants.*;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+import java.util.Objects;
+
+/**
+ * Class that provides operations processing SQLite database directly.
+ */
+class MtpDatabaseInternal {
+ private static class OpenHelper extends SQLiteOpenHelper {
+ public OpenHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(QUERY_CREATE_DOCUMENTS);
+ db.execSQL(QUERY_CREATE_ROOT_EXTRA);
+ db.execSQL(QUERY_CREATE_VIEW_ROOTS);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private final SQLiteDatabase mDatabase;
+
+ MtpDatabaseInternal(Context context) {
+ final OpenHelper helper = new OpenHelper(context);
+ mDatabase = helper.getWritableDatabase();
+ }
+
+ void close() {
+ mDatabase.close();
+ }
+
+ /**
+ * Queries roots information.
+ * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
+ * @return Database cursor.
+ */
+ Cursor queryRoots(String[] columnNames) {
+ return mDatabase.query(
+ VIEW_ROOTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+ null,
+ null,
+ null);
+ }
+
+ /**
+ * Queries root documents information.
+ * @param columnNames Column names defined in
+ * {@link android.provider.DocumentsContract.Document}.
+ * @return Database cursor.
+ */
+ Cursor queryRootDocuments(String[] columnNames) {
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+ null,
+ null,
+ null);
+ }
+
+ /**
+ * Queries documents information.
+ * @param columnNames Column names defined in
+ * {@link android.provider.DocumentsContract.Document}.
+ * @return Database cursor.
+ */
+ Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
+ null,
+ null,
+ null);
+ }
+
+ /**
+ * Remove all rows belong to a device.
+ * @param deviceId Device ID.
+ */
+ void removeDeviceRows(int deviceId) {
+ deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
+ }
+
+ /**
+ * Starts adding new documents.
+ * The methods decides mapping mode depends on if all documents under the given parent have MTP
+ * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
+ * a corresponding existing row. Otherwise it does heuristic.
+ *
+ * @param selection Query matches valid documents.
+ * @param arg Argument for selection.
+ * @return Mapping mode.
+ */
+ int startAddingDocuments(String selection, String arg) {
+ mDatabase.beginTransaction();
+ try {
+ // Delete all pending rows.
+ deleteDocumentsAndRoots(
+ selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
+
+ // Set all documents as invalidated.
+ final ContentValues values = new ContentValues();
+ values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+ mDatabase.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
+
+ // If we have rows that does not have MTP identifier, do heuristic mapping by name.
+ final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
+ mDatabase,
+ TABLE_DOCUMENTS,
+ selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
+ new String[] { arg }) > 0;
+ mDatabase.setTransactionSuccessful();
+ return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ /**
+ * Puts the documents into the database.
+ * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
+ * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
+ * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
+ * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
+ * rows. If the methods adds rows to database, it updates valueList with correct document ID.
+ *
+ * @param valuesList Values that are stored in the database.
+ * @param selection SQL where closure to select rows that shares the same parent.
+ * @param arg Argument for selection SQL.
+ * @param heuristic Whether the mapping mode is heuristic.
+ * @return Whether the method adds new rows.
+ */
+ boolean putDocuments(
+ ContentValues[] valuesList,
+ String selection,
+ String arg,
+ boolean heuristic,
+ String mappingKey) {
+ boolean added = false;
+ mDatabase.beginTransaction();
+ try {
+ for (final ContentValues values : valuesList) {
+ final Cursor candidateCursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(Document.COLUMN_DOCUMENT_ID),
+ selection + " AND " +
+ COLUMN_ROW_STATE + "=? AND " +
+ mappingKey + "=?",
+ strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
+ null,
+ null,
+ null,
+ "1");
+ final long rowId;
+ if (candidateCursor.getCount() == 0) {
+ rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ added = true;
+ } else if (!heuristic) {
+ candidateCursor.moveToNext();
+ final String documentId = candidateCursor.getString(0);
+ rowId = mDatabase.update(
+ TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, strings(documentId));
+ } else {
+ values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
+ rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ }
+ // Document ID is a primary integer key of the table. So the returned row
+ // IDs should be same with the document ID.
+ values.put(Document.COLUMN_DOCUMENT_ID, rowId);
+ candidateCursor.close();
+ }
+
+ mDatabase.setTransactionSuccessful();
+ return added;
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ /**
+ * Puts extra information for root documents.
+ * @param values Values containing extra information.
+ */
+ void putRootExtra(ContentValues values) {
+ mDatabase.replace(TABLE_ROOT_EXTRA, null, values);
+ }
+
+ /**
+ * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
+ * If the database does not find corresponding 'invalidated' document, it just removes
+ * 'invalidated' document from the database.
+ * @param selection Query to select rows for resolving.
+ * @param arg Argument for selection SQL.
+ * @param groupKey Column name used to find corresponding rows.
+ * @return Whether the methods adds or removed visible rows.
+ */
+ boolean stopAddingDocuments(String selection, String arg, String groupKey) {
+ mDatabase.beginTransaction();
+ try {
+ // Get 1-to-1 mapping of invalidated document and pending document.
+ final String invalidatedIdQuery = createStateFilter(
+ ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
+ final String pendingIdQuery = createStateFilter(
+ ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
+ // SQL should be like:
+ // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
+ // group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
+ // WHERE device_id = ? AND parent_document_id IS NULL
+ // GROUP BY display_name
+ // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
+ // count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
+ final Cursor mergingCursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ new String[] {
+ "group_concat(" + invalidatedIdQuery + ")",
+ "group_concat(" + pendingIdQuery + ")"
+ },
+ selection,
+ strings(arg),
+ groupKey,
+ "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
+ null);
+
+ final ContentValues values = new ContentValues();
+ while (mergingCursor.moveToNext()) {
+ final String invalidatedId = mergingCursor.getString(0);
+ final String pendingId = mergingCursor.getString(1);
+
+ // Obtain the new values including the latest object handle from mapping row.
+ getFirstRow(
+ TABLE_DOCUMENTS,
+ SELECTION_DOCUMENT_ID,
+ new String[] { pendingId },
+ values);
+ values.remove(Document.COLUMN_DOCUMENT_ID);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+ mDatabase.update(
+ TABLE_DOCUMENTS,
+ values,
+ SELECTION_DOCUMENT_ID,
+ new String[] { invalidatedId });
+
+ getFirstRow(
+ TABLE_ROOT_EXTRA,
+ SELECTION_ROOT_ID,
+ new String[] { pendingId },
+ values);
+ if (values.size() > 0) {
+ values.remove(Root.COLUMN_ROOT_ID);
+ mDatabase.update(
+ TABLE_ROOT_EXTRA,
+ values,
+ SELECTION_ROOT_ID,
+ new String[] { invalidatedId });
+ }
+
+ // Delete 'pending' row.
+ deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { pendingId });
+ }
+ mergingCursor.close();
+
+ boolean changed = false;
+
+ // Delete all invalidated rows that cannot be mapped.
+ if (deleteDocumentsAndRoots(
+ COLUMN_ROW_STATE + " = ? AND " + selection,
+ strings(ROW_STATE_INVALIDATED, arg))) {
+ changed = true;
+ }
+
+ // The database cannot find old document ID for the pending rows.
+ // Turn the all pending rows into valid state, which means the rows become to be
+ // valid with new document ID.
+ values.clear();
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+ if (mDatabase.update(
+ TABLE_DOCUMENTS,
+ values,
+ COLUMN_ROW_STATE + " = ? AND " + selection,
+ strings(ROW_STATE_PENDING, arg)) != 0) {
+ changed = true;
+ }
+ mDatabase.setTransactionSuccessful();
+ return changed;
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ /**
+ * Clears MTP related identifier.
+ * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
+ * the all documents as 'invalidated'. It also remove 'pending' rows as adding is cancelled
+ * now.
+ */
+ void clearMapping() {
+ mDatabase.beginTransaction();
+ try {
+ deleteDocumentsAndRoots(COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
+ final ContentValues values = new ContentValues();
+ values.putNull(COLUMN_OBJECT_HANDLE);
+ values.putNull(COLUMN_STORAGE_ID);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+ mDatabase.update(TABLE_DOCUMENTS, values, null, null);
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ /**
+ * {@link android.database.sqlite.SQLiteDatabase#beginTransaction()}
+ */
+ void beginTransaction() {
+ mDatabase.beginTransaction();
+ }
+
+ /**
+ * {@link android.database.sqlite.SQLiteDatabase#setTransactionSuccessful()}
+ */
+ void setTransactionSuccessful() {
+ mDatabase.setTransactionSuccessful();
+ }
+
+ /**
+ * {@link android.database.sqlite.SQLiteDatabase#endTransaction()}
+ */
+ void endTransaction() {
+ mDatabase.endTransaction();
+ }
+
+ /**
+ * Deletes a document, and its root information if the document is a root document.
+ * @param selection Query to select documents.
+ * @param args Arguments for selection.
+ * @return Whether the method deletes rows.
+ */
+ private boolean deleteDocumentsAndRoots(String selection, String[] args) {
+ mDatabase.beginTransaction();
+ try {
+ int deleted = 0;
+ deleted += mDatabase.delete(
+ TABLE_ROOT_EXTRA,
+ Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
+ false,
+ TABLE_DOCUMENTS,
+ new String[] { Document.COLUMN_DOCUMENT_ID },
+ selection,
+ null,
+ null,
+ null,
+ null) + ")",
+ args);
+ deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
+ mDatabase.setTransactionSuccessful();
+ return deleted != 0;
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ /**
+ * Obtains values of the first row for the query.
+ * @param values ContentValues that the values are stored to.
+ * @param table Target table.
+ * @param selection Query to select rows.
+ * @param args Argument for query.
+ */
+ private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
+ values.clear();
+ final Cursor cursor = mDatabase.query(table, null, selection, args, null, null, null, "1");
+ if (cursor.getCount() == 0) {
+ return;
+ }
+ cursor.moveToNext();
+ DatabaseUtils.cursorRowToContentValues(cursor, values);
+ cursor.close();
+ }
+
+ /**
+ * Gets SQL expression that represents the given value or NULL depends on the row state.
+ * @param state Expected row state.
+ * @param a SQL value.
+ * @return Expression that represents a if the row state is expected one, and represents NULL
+ * otherwise.
+ */
+ private static String createStateFilter(int state, String a) {
+ return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
+ " THEN " + a + " ELSE NULL END";
+ }
+
+ /**
+ * Converts values into string array.
+ * @param args Values converted into string array.
+ * @return String array.
+ */
+ private static String[] strings(Object... args) {
+ final String[] results = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ results[i] = Objects.toString(args[i]);
+ }
+ return results;
+ }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 7883e61..0931445 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -62,6 +62,7 @@
private Map<Integer, DeviceToolkit> mDeviceToolkits;
private RootScanner mRootScanner;
private Resources mResources;
+ private MtpDatabase mDatabase;
/**
* Provides singleton instance to MtpDocumentsService.
@@ -77,17 +78,23 @@
mMtpManager = new MtpManager(getContext());
mResolver = getContext().getContentResolver();
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
- mRootScanner = new RootScanner(mResolver, mMtpManager);
+ mDatabase = new MtpDatabase(getContext());
+ mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
return true;
}
@VisibleForTesting
- void onCreateForTesting(Resources resources, MtpManager mtpManager, ContentResolver resolver) {
+ void onCreateForTesting(
+ Resources resources,
+ MtpManager mtpManager,
+ ContentResolver resolver,
+ MtpDatabase database) {
mResources = resources;
mMtpManager = mtpManager;
mResolver = resolver;
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
- mRootScanner = new RootScanner(mResolver, mMtpManager);
+ mDatabase = database;
+ mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
}
@Override
@@ -95,17 +102,7 @@
if (projection == null) {
projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
}
- final MatrixCursor cursor = new MatrixCursor(projection);
- final MtpRoot[] roots = mRootScanner.getRoots();
- for (final MtpRoot root : roots) {
- final Identifier rootIdentifier = new Identifier(root.mDeviceId, root.mStorageId);
- final MatrixCursor.RowBuilder builder = cursor.newRow();
- builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId());
- builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
- builder.add(Root.COLUMN_TITLE, root.getRootName(mResources));
- builder.add(Root.COLUMN_DOCUMENT_ID, rootIdentifier.toDocumentId());
- builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace);
- }
+ final Cursor cursor = mDatabase.queryRoots(projection);
cursor.setNotificationUri(
mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
return cursor;
@@ -266,14 +263,16 @@
// TODO: Flush the device before closing (if not closed externally).
getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
mDeviceToolkits.remove(deviceId);
+ mDatabase.removeDeviceRows(deviceId);
mMtpManager.closeDevice(deviceId);
- mRootScanner.scanNow();
+ mRootScanner.notifyChange();
}
- void closeAllDevices() {
+ void close() throws InterruptedException {
boolean closed = false;
for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
try {
+ mDatabase.removeDeviceRows(deviceId);
mMtpManager.closeDevice(deviceId);
getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
closed = true;
@@ -282,8 +281,10 @@
}
}
if (closed) {
- mRootScanner.scanNow();
+ mRootScanner.notifyChange();
}
+ mRootScanner.close();
+ mDatabase.close();
}
boolean hasOpenedDevices() {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
index 328f618..2d1af26 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -79,9 +79,13 @@
@Override
public void onDestroy() {
final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
- provider.closeAllDevices();
unregisterReceiver(mReceiver);
mReceiver = null;
+ try {
+ provider.close();
+ } catch (InterruptedException e) {
+ Log.e(MtpDocumentsProvider.TAG, e.getMessage());
+ }
super.onDestroy();
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index ffab176..415f89e 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -1,14 +1,13 @@
package com.android.mtp;
import android.content.ContentResolver;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Process;
import android.provider.DocumentsContract;
import android.util.Log;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
final class RootScanner {
/**
@@ -28,18 +27,28 @@
private final static long SHORT_POLLING_TIMES = 10;
final ContentResolver mResolver;
+ final Resources mResources;
final MtpManager mManager;
- MtpRoot[] mLastRoots = new MtpRoot[0];
+ final MtpDatabase mDatabase;
+ boolean mClosed = false;
int mPollingCount;
- boolean mHasBackgroundTask = false;
+ Thread mBackgroundThread;
- RootScanner(ContentResolver resolver, MtpManager manager) {
+ RootScanner(
+ ContentResolver resolver,
+ Resources resources,
+ MtpManager manager,
+ MtpDatabase database) {
mResolver = resolver;
+ mResources = resources;
mManager = manager;
+ mDatabase = database;
}
- synchronized MtpRoot[] getRoots() {
- return mLastRoots;
+ void notifyChange() {
+ final Uri uri =
+ DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
+ mResolver.notifyChange(uri, null, false);
}
/**
@@ -47,26 +56,29 @@
* If the background thread has already gone, it restarts another background thread.
*/
synchronized void scanNow() {
+ if (mClosed) {
+ return;
+ }
mPollingCount = 0;
- if (!mHasBackgroundTask) {
- mHasBackgroundTask = true;
- new BackgroundLoaderThread().start();
+ if (mBackgroundThread == null) {
+ mBackgroundThread = new BackgroundLoaderThread();
+ mBackgroundThread.start();
} else {
notify();
}
}
- private MtpRoot[] createRoots(int[] deviceIds) {
- final ArrayList<MtpRoot> roots = new ArrayList<>();
- for (final int deviceId : deviceIds) {
- try {
- roots.addAll(Arrays.asList(mManager.getRoots(deviceId)));
- } catch (IOException error) {
- // Skip device that return error.
- Log.d(MtpDocumentsProvider.TAG, error.getMessage());
+ void close() throws InterruptedException {
+ Thread thread;
+ synchronized (this) {
+ mClosed = true;
+ thread = mBackgroundThread;
+ if (mBackgroundThread == null) {
+ return;
}
+ notify();
}
- return roots.toArray(new MtpRoot[roots.size()]);
+ thread.join();
}
private final class BackgroundLoaderThread extends Thread {
@@ -74,18 +86,28 @@
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
synchronized (RootScanner.this) {
- while (true) {
+ while (!mClosed) {
final int[] deviceIds = mManager.getOpenedDeviceIds();
- final MtpRoot[] newRoots = createRoots(deviceIds);
- if (!Arrays.equals(mLastRoots, newRoots)) {
- final Uri uri =
- DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
- mResolver.notifyChange(uri, null, false);
- mLastRoots = newRoots;
- }
if (deviceIds.length == 0) {
break;
}
+ boolean changed = false;
+ for (int deviceId : deviceIds) {
+ try {
+ mDatabase.startAddingRootDocuments(deviceId);
+ changed = mDatabase.putRootDocuments(
+ deviceId, mResources, mManager.getRoots(deviceId)) || changed;
+ changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
+ } catch (IOException exp) {
+ // The error may happen on the device. We would like to continue getting
+ // roots for other devices.
+ Log.e(MtpDocumentsProvider.TAG, exp.getMessage());
+ continue;
+ }
+ }
+ if (changed) {
+ notifyChange();
+ }
mPollingCount++;
try {
// Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
@@ -99,7 +121,7 @@
}
}
- mHasBackgroundTask = false;
+ mBackgroundThread = null;
}
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 3878ba6..ce4cf14 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -28,9 +28,9 @@
public class MtpDatabaseTest extends AndroidTestCase {
private final String[] COLUMN_NAMES = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
- MtpDatabase.COLUMN_DEVICE_ID,
- MtpDatabase.COLUMN_STORAGE_ID,
- MtpDatabase.COLUMN_OBJECT_HANDLE,
+ MtpDatabaseConstants.COLUMN_DEVICE_ID,
+ MtpDatabaseConstants.COLUMN_STORAGE_ID,
+ MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_SUMMARY,
@@ -42,13 +42,9 @@
private final TestResources resources = new TestResources();
- @Override
- public void tearDown() {
- MtpDatabase.deleteDatabase(getContext());
- }
-
public void testPutRootDocuments() throws Exception {
final MtpDatabase database = new MtpDatabase(getContext());
+ database.startAddingRootDocuments(0);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
@@ -90,7 +86,7 @@
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
- Root.COLUMN_DOCUMENT_ID,
+ Root.COLUMN_DOCUMENT_ID + "_",
Root.COLUMN_AVAILABLE_BYTES,
Root.COLUMN_CAPACITY_BYTES
});
@@ -141,7 +137,7 @@
public void testPutChildDocuments() throws Exception {
final MtpDatabase database = new MtpDatabase(getContext());
-
+ database.startAddingChildDocuments("parentId");
database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
@@ -209,13 +205,14 @@
final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
- MtpDatabase.COLUMN_STORAGE_ID,
+ MtpDatabaseConstants.COLUMN_STORAGE_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
final String[] rootColumns = new String[] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
+ database.startAddingRootDocuments(0);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
@@ -275,6 +272,7 @@
cursor.close();
}
+ database.startAddingRootDocuments(0);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
@@ -313,7 +311,7 @@
cursor.close();
}
- database.resolveRootDocuments(0);
+ database.stopAddingRootDocuments(0);
{
final Cursor cursor = database.queryRootDocuments(columns);
@@ -346,9 +344,10 @@
final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
- MtpDatabase.COLUMN_OBJECT_HANDLE,
+ MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
+ database.startAddingChildDocuments("parentId");
database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
@@ -378,6 +377,7 @@
cursor.close();
}
+ database.startAddingChildDocuments("parentId");
database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
@@ -395,7 +395,7 @@
cursor.close();
}
- database.resolveChildDocuments("parentId");
+ database.stopAddingChildDocuments("parentId");
{
final Cursor cursor = database.queryChildDocuments(columns, "parentId");
@@ -418,13 +418,15 @@
final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
- MtpDatabase.COLUMN_STORAGE_ID,
+ MtpDatabaseConstants.COLUMN_STORAGE_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
final String[] rootColumns = new String[] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
+ database.startAddingRootDocuments(0);
+ database.startAddingRootDocuments(1);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
});
@@ -460,14 +462,16 @@
database.clearMapping();
+ database.startAddingRootDocuments(0);
+ database.startAddingRootDocuments(1);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
});
database.putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
});
- database.resolveRootDocuments(0);
- database.resolveRootDocuments(1);
+ database.stopAddingRootDocuments(0);
+ database.stopAddingRootDocuments(1);
{
final Cursor cursor = database.queryRootDocuments(columns);
@@ -500,8 +504,11 @@
final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
- MtpDatabase.COLUMN_OBJECT_HANDLE
+ MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
};
+
+ database.startAddingChildDocuments("parentId1");
+ database.startAddingChildDocuments("parentId2");
database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
@@ -509,13 +516,16 @@
createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
database.clearMapping();
+
+ database.startAddingChildDocuments("parentId1");
+ database.startAddingChildDocuments("parentId2");
database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- database.resolveChildDocuments("parentId1");
+ database.stopAddingChildDocuments("parentId1");
{
final Cursor cursor = database.queryChildDocuments(columns, "parentId1");
@@ -539,25 +549,32 @@
final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
- MtpDatabase.COLUMN_STORAGE_ID,
+ MtpDatabaseConstants.COLUMN_STORAGE_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
final String[] rootColumns = new String[] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
+
+ database.startAddingRootDocuments(0);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
database.clearMapping();
+
+ database.startAddingRootDocuments(0);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
});
database.clearMapping();
+
+ database.startAddingRootDocuments(0);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
});
- database.resolveRootDocuments(0);
+ database.stopAddingRootDocuments(0);
+
{
final Cursor cursor = database.queryRootDocuments(columns);
assertEquals(1, cursor.getCount());
@@ -581,22 +598,27 @@
final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
- MtpDatabase.COLUMN_STORAGE_ID,
+ MtpDatabaseConstants.COLUMN_STORAGE_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
final String[] rootColumns = new String[] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
+
+ database.startAddingRootDocuments(0);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
database.clearMapping();
+
+ database.startAddingRootDocuments(0);
database.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
});
- database.resolveRootDocuments(0);
+ database.stopAddingRootDocuments(0);
+
{
final Cursor cursor = database.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
@@ -622,4 +644,71 @@
cursor.close();
}
}
+
+ public void testReplaceExistingRoots() {
+ // The client code should be able to replace exisitng rows with new information.
+ final MtpDatabase database = new MtpDatabase(getContext());
+ // Add one.
+ database.startAddingRootDocuments(0);
+ database.putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ database.stopAddingRootDocuments(0);
+ // Replace it.
+ database.startAddingRootDocuments(0);
+ database.putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+ });
+ database.stopAddingRootDocuments(0);
+ {
+ final String[] columns = new String[] {
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+ MtpDatabaseConstants.COLUMN_STORAGE_ID,
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME
+ };
+ final Cursor cursor = database.queryRootDocuments(columns);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("documentId", 1, cursor.getInt(0));
+ assertEquals("storageId", 100, cursor.getInt(1));
+ assertEquals("name", "Device Storage B", cursor.getString(2));
+ cursor.close();
+ }
+ {
+ final String[] columns = new String[] {
+ Root.COLUMN_ROOT_ID,
+ Root.COLUMN_AVAILABLE_BYTES
+ };
+ final Cursor cursor = database.queryRoots(columns);
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("rootId", 1, cursor.getInt(0));
+ assertEquals("availableBytes", 1000, cursor.getInt(1));
+ cursor.close();
+ }
+ }
+
+ public void _testFailToReplaceExisitingUnmappedRoots() {
+ // The client code should not be able to replace rows before resolving 'unmapped' rows.
+ final MtpDatabase database = new MtpDatabase(getContext());
+ // Add one.
+ database.startAddingRootDocuments(0);
+ database.putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ database.clearMapping();
+ // Add one.
+ database.putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+ });
+ // Add one more before resolving unmapped documents.
+ try {
+ database.putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+ });
+ fail();
+ } catch (Throwable e) {
+ assertTrue(e instanceof Error);
+ }
+ }
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 5765f0a..bc7f28c 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -36,13 +36,24 @@
private MtpDocumentsProvider mProvider;
private TestMtpManager mMtpManager;
private final TestResources mResources = new TestResources();
+ private MtpDatabase mDatabase;
@Override
public void setUp() throws IOException {
mResolver = new TestContentResolver();
mMtpManager = new TestMtpManager(getContext());
mProvider = new MtpDocumentsProvider();
- mProvider.onCreateForTesting(mResources, mMtpManager, mResolver);
+ mDatabase = new MtpDatabase(getContext());
+ mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
+ }
+
+ @Override
+ public void tearDown() {
+ try {
+ mProvider.close();
+ } catch (InterruptedException e) {
+ fail();
+ }
}
public void testOpenAndCloseDevice() throws Exception {
@@ -96,26 +107,6 @@
mResolver.waitForNotification(ROOTS_URI, 1);
}
- public void testCloseAllDevices() throws Exception {
- mMtpManager.addValidDevice(0);
- mMtpManager.setRoots(0, new MtpRoot[] {
- new MtpRoot(
- 0 /* deviceId */,
- 1 /* storageId */,
- "Device A" /* device model name */,
- "Storage A" /* volume description */,
- 1024 /* free space */,
- 2048 /* total space */,
- "" /* no volume identifier */)
- });
- mProvider.closeAllDevices();
- mProvider.openDevice(0);
- mResolver.waitForNotification(ROOTS_URI, 1);
-
- mProvider.closeAllDevices();
- mResolver.waitForNotification(ROOTS_URI, 2);
- }
-
public void testQueryRoots() throws Exception {
mMtpManager.addValidDevice(0);
mMtpManager.addValidDevice(1);
@@ -146,7 +137,7 @@
final Cursor cursor = mProvider.queryRoots(null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("0_1", cursor.getString(0));
+ assertEquals("1", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
// TODO: Add storage icon for MTP devices.
assertTrue(cursor.isNull(2) /* icon */);
@@ -162,7 +153,7 @@
assertEquals(2, cursor.getCount());
cursor.moveToNext();
cursor.moveToNext();
- assertEquals("1_1", cursor.getString(0));
+ assertEquals("2", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
// TODO: Add storage icon for MTP devices.
assertTrue(cursor.isNull(2) /* icon */);
@@ -170,13 +161,6 @@
assertEquals("1_1_0", cursor.getString(4));
assertEquals(2048, cursor.getInt(5));
}
-
- {
- mProvider.closeAllDevices();
- mResolver.waitForNotification(ROOTS_URI, 3);
- final Cursor cursor = mProvider.queryRoots(null);
- assertEquals(0, cursor.getCount());
- }
}
public void testQueryRoots_error() throws Exception {
@@ -201,7 +185,7 @@
final Cursor cursor = mProvider.queryRoots(null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("1_1", cursor.getString(0));
+ assertEquals("1", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
// TODO: Add storage icon for MTP devices.
assertTrue(cursor.isNull(2) /* icon */);
diff --git a/packages/PrintSpooler/res/drawable/ic_add.xml b/packages/PrintSpooler/res/drawable/ic_add.xml
new file mode 100644
index 0000000..1442b1b
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_add.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
+ android:fillColor="#FFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/drawable/ic_search.xml b/packages/PrintSpooler/res/drawable/ic_search.xml
deleted file mode 100644
index 991fa38b..0000000
--- a/packages/PrintSpooler/res/drawable/ic_search.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:autoMirrored="true">
-
- <item
- android:state_checked="true">
- <bitmap
- android:src="@*android:drawable/ic_menu_search"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item
- android:state_pressed="true">
- <bitmap
- android:src="@*android:drawable/ic_menu_search"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item>
- <bitmap
- android:src="@*android:drawable/ic_menu_search"
- android:tint="?android:attr/colorControlNormal">
- </bitmap>
- </item>
-
-</selector>
diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml
index 8da5769..15cc139 100644
--- a/packages/PrintSpooler/res/menu/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml
@@ -19,7 +19,7 @@
<item
android:id="@+id/action_search"
android:title="@string/search"
- android:icon="@*android:drawable/ic_search"
+ android:icon="@*android:drawable/ic_search_api_material"
android:actionViewClass="android.widget.SearchView"
android:showAsAction="ifRoom|collapseActionView"
android:alphabeticShortcut="f"
@@ -29,7 +29,7 @@
<item
android:id="@+id/action_add_printer"
android:title="@string/print_add_printer"
- android:icon="@*android:drawable/create_contact"
+ android:icon="@drawable/ic_add"
android:showAsAction="ifRoom"
android:alphabeticShortcut="a">
</item>
diff --git a/packages/PrintSpooler/res/values-af/strings.xml b/packages/PrintSpooler/res/values-af/strings.xml
index 1c39a7d..cf13374 100644
--- a/packages/PrintSpooler/res/values-af/strings.xml
+++ b/packages/PrintSpooler/res/values-af/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Kies drukdiens"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Soek tans vir drukkers"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Geen drukdienste is geaktiveer nie"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Geen drukkers gekry nie"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Druk tans <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Kanselleer tans <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-am/strings.xml b/packages/PrintSpooler/res/values-am/strings.xml
index 4d8b85e..18c90f5 100644
--- a/packages/PrintSpooler/res/values-am/strings.xml
+++ b/packages/PrintSpooler/res/values-am/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"የህትመት አገልግሎት ይምረጡ"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"አታሚዎችን በመፈለግ ላይ"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"ምንም የህትመት አገልግሎቶች አልነቁም"</string>
<string name="print_no_printers" msgid="4869403323900054866">"ምንም አታሚዎች አልተገኙም"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ን በማተም ላይ"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ን በመተው ላይ"</string>
diff --git a/packages/PrintSpooler/res/values-ar/strings.xml b/packages/PrintSpooler/res/values-ar/strings.xml
index 0fa6ec2..2d87498c 100644
--- a/packages/PrintSpooler/res/values-ar/strings.xml
+++ b/packages/PrintSpooler/res/values-ar/strings.xml
@@ -64,6 +64,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"اختر خدمة طباعة"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"البحث عن طابعات"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"لم يتم تمكين أي خدمات طباعة"</string>
<string name="print_no_printers" msgid="4869403323900054866">"لم يتم العثور على طابعات"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"جارٍ طباعة <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"جارٍ إلغاء <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-az-rAZ/strings.xml b/packages/PrintSpooler/res/values-az-rAZ/strings.xml
index 1837db4..c8ca06c 100644
--- a/packages/PrintSpooler/res/values-az-rAZ/strings.xml
+++ b/packages/PrintSpooler/res/values-az-rAZ/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Çap xidmətini seçin"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Printer axtarılır"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Heç bir çap xidməti aktiv edilməyib"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Heç bir printer tapılmadı"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> çap edilir"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ləğv edilir"</string>
diff --git a/packages/PrintSpooler/res/values-bg/strings.xml b/packages/PrintSpooler/res/values-bg/strings.xml
index 9a8ccef..7129cc1 100644
--- a/packages/PrintSpooler/res/values-bg/strings.xml
+++ b/packages/PrintSpooler/res/values-bg/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Избиране на услуга за отпечатване"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Търсене на принтери"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Няма активирани услуги за отпечатване"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Няма намерени принтери"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ се отпечатва"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ се анулира"</string>
diff --git a/packages/PrintSpooler/res/values-bn-rBD/strings.xml b/packages/PrintSpooler/res/values-bn-rBD/strings.xml
index 4f1646d..ae0b01c 100644
--- a/packages/PrintSpooler/res/values-bn-rBD/strings.xml
+++ b/packages/PrintSpooler/res/values-bn-rBD/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"মুদ্রণ পরিষেবা চয়ন করুন"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"মুদ্রকগুলি অনুসন্ধান করা হচ্ছে"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"মুদ্রণ পরিষেবা সক্ষম নেই"</string>
<string name="print_no_printers" msgid="4869403323900054866">"কোনো মুদ্রক পাওয়া যায়নি"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> মুদ্রণ করা হচ্ছে"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> বাতিল করা হচ্ছে"</string>
diff --git a/packages/PrintSpooler/res/values-ca/strings.xml b/packages/PrintSpooler/res/values-ca/strings.xml
index fa34c52..d221c6c 100644
--- a/packages/PrintSpooler/res/values-ca/strings.xml
+++ b/packages/PrintSpooler/res/values-ca/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Selecció del servei d\'impressió"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Cerca d\'impressores"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"No hi ha cap servei d\'impressió activat"</string>
<string name="print_no_printers" msgid="4869403323900054866">"No s\'ha trobat cap impressora"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"S\'està imprimint <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"S\'està cancel·lant <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-cs/strings.xml b/packages/PrintSpooler/res/values-cs/strings.xml
index 5c98d616..5396b3e 100644
--- a/packages/PrintSpooler/res/values-cs/strings.xml
+++ b/packages/PrintSpooler/res/values-cs/strings.xml
@@ -62,6 +62,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Zvolte službu tisku"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Vyhledávání tiskáren"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Nejsou aktivovány žádné tiskové služby"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nebyly nalezeny žádné tiskárny"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Tisk úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Rušení úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-da/strings.xml b/packages/PrintSpooler/res/values-da/strings.xml
index e57d435..484d66f 100644
--- a/packages/PrintSpooler/res/values-da/strings.xml
+++ b/packages/PrintSpooler/res/values-da/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Vælg udskriftstjeneste"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Søger efter printere"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Ingen udskrivningstjenester er aktiveret"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Der blev ikke fundet nogen printere"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> udskrives"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> annulleres"</string>
diff --git a/packages/PrintSpooler/res/values-de/strings.xml b/packages/PrintSpooler/res/values-de/strings.xml
index 1005b45..c6323ef 100644
--- a/packages/PrintSpooler/res/values-de/strings.xml
+++ b/packages/PrintSpooler/res/values-de/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Druckdienst auswählen"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Suche nach Druckern"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Keine Druckdienste aktiviert"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Keine Drucker gefunden"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> wird gedruckt..."</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> wird abgebrochen..."</string>
diff --git a/packages/PrintSpooler/res/values-el/strings.xml b/packages/PrintSpooler/res/values-el/strings.xml
index 75b8447..85923b7 100644
--- a/packages/PrintSpooler/res/values-el/strings.xml
+++ b/packages/PrintSpooler/res/values-el/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Επιλέξτε υπηρεσία εκτύπωσης"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Αναζήτηση για εκτυπωτές"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Δεν έχουν ενεργοποιηθεί υπηρεσίες εκτύπωσης"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Δεν βρέθηκαν εκτυπωτές"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Εκτύπωση <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Ακύρωση <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-en-rAU/strings.xml b/packages/PrintSpooler/res/values-en-rAU/strings.xml
index 87349c2..5ac14a3 100644
--- a/packages/PrintSpooler/res/values-en-rAU/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rAU/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Choose print service"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Searching for printers"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"No print services enabled"</string>
<string name="print_no_printers" msgid="4869403323900054866">"No printers found"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Printing <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelling <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-en-rGB/strings.xml b/packages/PrintSpooler/res/values-en-rGB/strings.xml
index 87349c2..5ac14a3 100644
--- a/packages/PrintSpooler/res/values-en-rGB/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rGB/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Choose print service"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Searching for printers"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"No print services enabled"</string>
<string name="print_no_printers" msgid="4869403323900054866">"No printers found"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Printing <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelling <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-en-rIN/strings.xml b/packages/PrintSpooler/res/values-en-rIN/strings.xml
index 87349c2..5ac14a3 100644
--- a/packages/PrintSpooler/res/values-en-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rIN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Choose print service"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Searching for printers"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"No print services enabled"</string>
<string name="print_no_printers" msgid="4869403323900054866">"No printers found"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Printing <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelling <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-es-rUS/strings.xml b/packages/PrintSpooler/res/values-es-rUS/strings.xml
index ffd4d79..1876ae1 100644
--- a/packages/PrintSpooler/res/values-es-rUS/strings.xml
+++ b/packages/PrintSpooler/res/values-es-rUS/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Elegir servicio de impresión"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Buscando impresoras"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"No hay servicios de impresión habilitados"</string>
<string name="print_no_printers" msgid="4869403323900054866">"No se encontraron impresoras"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Imprimiendo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-es/strings.xml b/packages/PrintSpooler/res/values-es/strings.xml
index 5660d1b..14ed0d8 100644
--- a/packages/PrintSpooler/res/values-es/strings.xml
+++ b/packages/PrintSpooler/res/values-es/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Seleccionar servicio de impresión"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Buscando impresoras"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"No hay servicios de impresión habilitados"</string>
<string name="print_no_printers" msgid="4869403323900054866">"No se encontraron impresoras"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Imprimiendo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-et-rEE/strings.xml b/packages/PrintSpooler/res/values-et-rEE/strings.xml
index fb0f320..c284332 100644
--- a/packages/PrintSpooler/res/values-et-rEE/strings.xml
+++ b/packages/PrintSpooler/res/values-et-rEE/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Prinditeenuse valimine"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Printerite otsimine"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Ühtegi printimisteenust pole lubatud"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Printereid ei leitud"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Prinditöö <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> printimine"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Prinditöö <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> tühistamine"</string>
diff --git a/packages/PrintSpooler/res/values-eu-rES/strings.xml b/packages/PrintSpooler/res/values-eu-rES/strings.xml
index 8402a9c..eab7587 100644
--- a/packages/PrintSpooler/res/values-eu-rES/strings.xml
+++ b/packages/PrintSpooler/res/values-eu-rES/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Aukeratu inprimatze-zerbitzua"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Inprimagailuak bilatzen"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Ez dago gaituta inprimatzeko zerbitzurik"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Ez da inprimagailurik aurkitu"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> inprimatzen"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> bertan behera uzten"</string>
@@ -75,7 +76,7 @@
<string name="reason_unknown" msgid="5507940196503246139">"ezezaguna"</string>
<string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: ez dago erabilgarri"</string>
<string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> erabili nahi duzu?"</string>
- <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Baliteke dokumentuak zerbitzari bat edo gehiagotan zehar igarotzea inprimagailurako bidean."</string>
+ <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Baliteke dokumentuak zerbitzari batean edo gehiagotan zehar igarotzea inprimagailurako bidean."</string>
<string-array name="color_mode_labels">
<item msgid="7602948745415174937">"Zuri-beltza"</item>
<item msgid="2762241247228983754">"Koloretakoa"</item>
diff --git a/packages/PrintSpooler/res/values-fa/strings.xml b/packages/PrintSpooler/res/values-fa/strings.xml
index ba78460..156eb47 100644
--- a/packages/PrintSpooler/res/values-fa/strings.xml
+++ b/packages/PrintSpooler/res/values-fa/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"انتخاب سرویس چاپ"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"درحال جستجوی چاپگرها"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"هیچ خدمات چاپی فعال نیست"</string>
<string name="print_no_printers" msgid="4869403323900054866">"هیچ چاپگری یافت نشد"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"در حال چاپ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"در حال لغو <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-fi/strings.xml b/packages/PrintSpooler/res/values-fi/strings.xml
index 3a82702..887d428 100644
--- a/packages/PrintSpooler/res/values-fi/strings.xml
+++ b/packages/PrintSpooler/res/values-fi/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Valitse tulostuspalvelu"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Etsitään tulostimia"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Ei käytössä olevia tulostuspalveluita"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Tulostimia ei löydy"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Tulostetaan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Peruutetaan työ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-fr-rCA/strings.xml b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
index f49885a..a97b20a 100644
--- a/packages/PrintSpooler/res/values-fr-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Sélectionner le service d\'impression"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Recherche d\'imprimantes en cours..."</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Aucun service d\'impression activé"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Aucune imprimante trouvée"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Impression de <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> en cours…"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annulation de « <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> »…"</string>
diff --git a/packages/PrintSpooler/res/values-fr/strings.xml b/packages/PrintSpooler/res/values-fr/strings.xml
index 4dcfa65..726915b 100644
--- a/packages/PrintSpooler/res/values-fr/strings.xml
+++ b/packages/PrintSpooler/res/values-fr/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Sélectionner le service d\'impression"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Recherche d\'imprimantes en cours"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Aucun service d\'impression activé"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Aucune imprimante trouvée"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Impression de \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" en cours…"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annulation de \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" en cours…"</string>
diff --git a/packages/PrintSpooler/res/values-gl-rES/strings.xml b/packages/PrintSpooler/res/values-gl-rES/strings.xml
index c805e30..6a2d031 100644
--- a/packages/PrintSpooler/res/values-gl-rES/strings.xml
+++ b/packages/PrintSpooler/res/values-gl-rES/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Escoller servizo de impresión"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Busca de impresoras"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Non hai servizos de impresión activados"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Non se atopou ningunha impresora"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-gu-rIN/strings.xml b/packages/PrintSpooler/res/values-gu-rIN/strings.xml
index 51820f2..3b3d2ed 100644
--- a/packages/PrintSpooler/res/values-gu-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-gu-rIN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"પ્રિન્ટ સેવા પસંદ કરો"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"પ્રિન્ટર્સ માટે શોધી રહ્યું છે"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"કોઈ છાપ સેવાઓ સક્ષમ કરેલ નથી"</string>
<string name="print_no_printers" msgid="4869403323900054866">"કોઈ પ્રિન્ટર મળ્યા નથી"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> છાપી રહ્યાં છે"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ને રદ કરી રહ્યું છે"</string>
diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index e2bc012..162f65b 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"प्रिंट सेवा चुनें"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"प्रिंटर खोज रहा है"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"कोई भी प्रिंट सेवा सक्षम नहीं है"</string>
<string name="print_no_printers" msgid="4869403323900054866">"कोई प्रिंटर नहीं मिले"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> प्रिंट हो रहा है"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द हो रहा है"</string>
diff --git a/packages/PrintSpooler/res/values-hr/strings.xml b/packages/PrintSpooler/res/values-hr/strings.xml
index 64c4a58..9276300 100644
--- a/packages/PrintSpooler/res/values-hr/strings.xml
+++ b/packages/PrintSpooler/res/values-hr/strings.xml
@@ -61,6 +61,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Odaberite uslugu ispisa"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Traženje pisača"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Nije omogućena nijedna usluga ispisa"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nije pronađen nijedan pisač"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Ispisivanje <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Otkazivanje zadatka <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-hu/strings.xml b/packages/PrintSpooler/res/values-hu/strings.xml
index 58e7852..ca48f9a 100644
--- a/packages/PrintSpooler/res/values-hu/strings.xml
+++ b/packages/PrintSpooler/res/values-hu/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Nyomtatási szolgáltatás kiválasztása"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Nyomtatók keresése"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Nincs engedélyezett nyomtatási szolgáltatás"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nem található nyomtató"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"A(z) <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> nyomtatása"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"A(z) <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> törlése"</string>
diff --git a/packages/PrintSpooler/res/values-hy-rAM/strings.xml b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
index 88e9192..a198277 100644
--- a/packages/PrintSpooler/res/values-hy-rAM/strings.xml
+++ b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Ընտրեք տպելու ծառայությունը"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Տպիչների որոնում"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Ակտիվացված տպման ծառայություններ չկան"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Տպիչներ չեն գտնվել"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Տպվում է՝ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>-ը չեղարկվում է"</string>
diff --git a/packages/PrintSpooler/res/values-in/strings.xml b/packages/PrintSpooler/res/values-in/strings.xml
index de76536..5050700 100644
--- a/packages/PrintSpooler/res/values-in/strings.xml
+++ b/packages/PrintSpooler/res/values-in/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Pilih layanan cetak"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Mencari printer"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Tidak ada layanan cetak yang aktif"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Tidak ditemukan printer"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Mencetak <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Membatalkan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-is-rIS/strings.xml b/packages/PrintSpooler/res/values-is-rIS/strings.xml
index 3f8aa65..33f10f5 100644
--- a/packages/PrintSpooler/res/values-is-rIS/strings.xml
+++ b/packages/PrintSpooler/res/values-is-rIS/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Veldu prentþjónustu"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Leitar að prentara"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Engin prentþjónusta er virk"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Engir prentarar fundust"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Prentar <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Hættir við <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-it/strings.xml b/packages/PrintSpooler/res/values-it/strings.xml
index 394dc88..f7c6eff 100644
--- a/packages/PrintSpooler/res/values-it/strings.xml
+++ b/packages/PrintSpooler/res/values-it/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Scegli servizio di stampa"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Ricerca di stampanti"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Non è stato attivato alcun servizio di stampa"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nessuna stampante trovata"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Stampa di <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annullamento di <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-iw/strings.xml b/packages/PrintSpooler/res/values-iw/strings.xml
index 5c5e5d9..545ba88 100644
--- a/packages/PrintSpooler/res/values-iw/strings.xml
+++ b/packages/PrintSpooler/res/values-iw/strings.xml
@@ -62,6 +62,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"בחר שירות הדפסה"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"מחפש מדפסות"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"לא הופעלו שירותי הדפסה"</string>
<string name="print_no_printers" msgid="4869403323900054866">"לא נמצאו מדפסות"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"מדפיס את <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"מבטל את <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-ja/strings.xml b/packages/PrintSpooler/res/values-ja/strings.xml
index 6a396b3..e292013 100644
--- a/packages/PrintSpooler/res/values-ja/strings.xml
+++ b/packages/PrintSpooler/res/values-ja/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"印刷サービスの選択"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"プリンタの検索中"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"使用できる印刷サービスがありません"</string>
<string name="print_no_printers" msgid="4869403323900054866">"プリンタが見つかりません"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>を印刷しています"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>をキャンセルしています"</string>
diff --git a/packages/PrintSpooler/res/values-ka-rGE/strings.xml b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
index 44e77d7..db71506 100644
--- a/packages/PrintSpooler/res/values-ka-rGE/strings.xml
+++ b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"აირჩიეთ ბეჭდვის სერვისი"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"მიმდინარეობს პრინტერების ძიება"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"ბეჭდვის სერვისები გააქტიურებული არ არის"</string>
<string name="print_no_printers" msgid="4869403323900054866">"პრინტერები ვერ მოიძებნა"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"იბეჭდება <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"მიმდინარეობს <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>-ის გაუქმება"</string>
diff --git a/packages/PrintSpooler/res/values-kk-rKZ/strings.xml b/packages/PrintSpooler/res/values-kk-rKZ/strings.xml
index a79a511..de2115a 100644
--- a/packages/PrintSpooler/res/values-kk-rKZ/strings.xml
+++ b/packages/PrintSpooler/res/values-kk-rKZ/strings.xml
@@ -60,6 +60,8 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Принтер қызметін таңдау"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Принтерлерді іздеу"</string>
+ <!-- no translation found for print_no_print_services (8561247706423327966) -->
+ <skip />
<string name="print_no_printers" msgid="4869403323900054866">"Ешқандай принтер табылмады"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> басып шығарылуда"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> жұмысын тоқтатуда"</string>
diff --git a/packages/PrintSpooler/res/values-km-rKH/strings.xml b/packages/PrintSpooler/res/values-km-rKH/strings.xml
index 87f4fde..c91e6ae 100644
--- a/packages/PrintSpooler/res/values-km-rKH/strings.xml
+++ b/packages/PrintSpooler/res/values-km-rKH/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"ជ្រើសសេវាបោះពុម្ព"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"ស្វែងរកម៉ាស៊ីនបោះពុម្ព"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"គ្មានការបើកដំណើរការសេវាបោះពុម្ពទេ"</string>
<string name="print_no_printers" msgid="4869403323900054866">"រកមិនឃើញម៉ាស៊ីនបោះពុម្ព"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"កំពុងបោះពុម្ព <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"ការបោះបង់ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-kn-rIN/strings.xml b/packages/PrintSpooler/res/values-kn-rIN/strings.xml
index eff1127..86dc11f 100644
--- a/packages/PrintSpooler/res/values-kn-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-kn-rIN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"ಮುದ್ರಣ ಸೇವೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"ಪ್ರಿಂಟರ್ಗಳಿಗಾಗಿ ಹುಡುಕಲಾಗುತ್ತಿದೆ"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"ಯಾವುದೇ ಮುದ್ರಣ ಸೇವೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿಲ್ಲ"</string>
<string name="print_no_printers" msgid="4869403323900054866">"ಯಾವುದೇ ಮುದ್ರಕಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ಮುದ್ರಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ರದ್ದು ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
diff --git a/packages/PrintSpooler/res/values-ko/strings.xml b/packages/PrintSpooler/res/values-ko/strings.xml
index 7613ad7..4450bca 100644
--- a/packages/PrintSpooler/res/values-ko/strings.xml
+++ b/packages/PrintSpooler/res/values-ko/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"인쇄 서비스 선택"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"프린터 검색 중"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"사용 가능한 프린트 서비스 없음"</string>
<string name="print_no_printers" msgid="4869403323900054866">"프린터 없음"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> 인쇄 중"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> 취소 중"</string>
diff --git a/packages/PrintSpooler/res/values-ky-rKG/strings.xml b/packages/PrintSpooler/res/values-ky-rKG/strings.xml
index 2ea1b57..b4bdc57 100644
--- a/packages/PrintSpooler/res/values-ky-rKG/strings.xml
+++ b/packages/PrintSpooler/res/values-ky-rKG/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Принтер кызматын тандоо"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Принтерлер изделүүдө"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Принтер-кызматтары иштетилген эмес"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Принтерлер табылган жок"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> басылууда"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> токтотулууда"</string>
diff --git a/packages/PrintSpooler/res/values-lo-rLA/strings.xml b/packages/PrintSpooler/res/values-lo-rLA/strings.xml
index 511a72f..500f01c 100644
--- a/packages/PrintSpooler/res/values-lo-rLA/strings.xml
+++ b/packages/PrintSpooler/res/values-lo-rLA/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"ເລືອກບໍລິການການພິມ"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"ກຳລັງຊອກຫາເຄື່ອງພິມ"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"ບໍ່ມີການບໍລິການພິມເປີດໃຊ້ງານໄວ້"</string>
<string name="print_no_printers" msgid="4869403323900054866">"ບໍ່ພົບເຄື່ອງພິມ"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"ກຳລັງພິມ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"ກຳລັງຍົກເລີກ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-lt/strings.xml b/packages/PrintSpooler/res/values-lt/strings.xml
index ce912be..ad59182 100644
--- a/packages/PrintSpooler/res/values-lt/strings.xml
+++ b/packages/PrintSpooler/res/values-lt/strings.xml
@@ -62,6 +62,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Pasirinkite spausdinimo paslaugą"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Ieškoma spausdintuvų"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Neįgalinta jokių spausdinimo paslaugų"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nerasta spausdintuvų"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Spausdinama: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Atšaukiama: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-lv/strings.xml b/packages/PrintSpooler/res/values-lv/strings.xml
index bfba6c8..3b30d04 100644
--- a/packages/PrintSpooler/res/values-lv/strings.xml
+++ b/packages/PrintSpooler/res/values-lv/strings.xml
@@ -61,6 +61,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Izvēlieties drukāšanas pakalpojumu"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Printeru meklēšana"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Nav iespējots neviens drukas pakalpojums"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Netika atrasts neviens printeris."</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Notiek darba <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> drukāšana…"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Pārtrauc drukas darbu <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>…"</string>
diff --git a/packages/PrintSpooler/res/values-mk-rMK/strings.xml b/packages/PrintSpooler/res/values-mk-rMK/strings.xml
index a81db01..42548fc 100644
--- a/packages/PrintSpooler/res/values-mk-rMK/strings.xml
+++ b/packages/PrintSpooler/res/values-mk-rMK/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Избери услуга печатење"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Пребарување печатачи"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Нема овозможени услуги за печатење"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Не се пронајдени печатачи"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> се печати"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> се откажува"</string>
diff --git a/packages/PrintSpooler/res/values-ml-rIN/strings.xml b/packages/PrintSpooler/res/values-ml-rIN/strings.xml
index c14c5ad..4e5a99c 100644
--- a/packages/PrintSpooler/res/values-ml-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-ml-rIN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"പ്രിന്റ് സേവനം തിരഞ്ഞെടുക്കുക"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"പ്രിന്ററുകൾക്കായി തിരയുന്നു"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"പ്രിന്റ് സേവനങ്ങളൊന്നും പ്രവർത്തനക്ഷമാക്കിയിട്ടില്ല"</string>
<string name="print_no_printers" msgid="4869403323900054866">"പ്രിന്ററുകളൊന്നും കണ്ടെത്തിയില്ല"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> പ്രിന്റുചെയ്യുന്നു"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> റദ്ദാക്കുന്നു"</string>
diff --git a/packages/PrintSpooler/res/values-mn-rMN/strings.xml b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
index 789f085..0f49671 100644
--- a/packages/PrintSpooler/res/values-mn-rMN/strings.xml
+++ b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Хэвлэх үйлчилгээг сонгох"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Принтер хайж байна"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Хэвлэх үйлчилгээг идэвхжүүлээгүй"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Принтер олдсонгүй"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Хэвлэж байна <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Цуцлаж байна <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-mr-rIN/strings.xml b/packages/PrintSpooler/res/values-mr-rIN/strings.xml
index 1d859cf..53225ee 100644
--- a/packages/PrintSpooler/res/values-mr-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-mr-rIN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"मुद्रण सेवा निवडा"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"प्रिंटर शोधत आहे"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"कोणत्याही मुद्रण सेवा सक्षम केलेल्या नाहीत"</string>
<string name="print_no_printers" msgid="4869403323900054866">"कोणतेही प्रिंटर आढळले नाही"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> मुद्रण करीत आहे"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द करीत आहे"</string>
diff --git a/packages/PrintSpooler/res/values-ms-rMY/strings.xml b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
index c86d0bf..a15587b 100644
--- a/packages/PrintSpooler/res/values-ms-rMY/strings.xml
+++ b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Pilih perkhidmatan cetak"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Mencari pencetak"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Perkhidmatan cetak tidak didayakan"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Tiada pencetak ditemui"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Mencetak <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Membatalkan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-my-rMM/strings.xml b/packages/PrintSpooler/res/values-my-rMM/strings.xml
index 9b2760a..10dc93a 100644
--- a/packages/PrintSpooler/res/values-my-rMM/strings.xml
+++ b/packages/PrintSpooler/res/values-my-rMM/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"စာထုတ်ရန် ဝန်ဆောင်မှုကို ရွေးချယ်ပါ"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"စာထုတ်စက်များကို ရှာနေပါသည်"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"ပုံနှိပ်ထုတ်ယူရေး ဝန်ဆောင်မှုများ ဖွင့်မထားပါ"</string>
<string name="print_no_printers" msgid="4869403323900054866">"စာထုတ်စက် တစ်ခုမှ မတွေ့ရှိပါ"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ကို စာထုတ်နေပါသည်"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ကို ပယ်ဖျက်နေပါသည်"</string>
diff --git a/packages/PrintSpooler/res/values-nb/strings.xml b/packages/PrintSpooler/res/values-nb/strings.xml
index cdec56f..8cbcee7 100644
--- a/packages/PrintSpooler/res/values-nb/strings.xml
+++ b/packages/PrintSpooler/res/values-nb/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Velg utskriftstjeneste"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Søker etter skrivere"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Ingen utskriftstjenester er slått på"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Fant ingen skrivere"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Skriver ut <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Avbryter <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-ne-rNP/strings.xml b/packages/PrintSpooler/res/values-ne-rNP/strings.xml
index 427a9ae..ad9b4e3 100644
--- a/packages/PrintSpooler/res/values-ne-rNP/strings.xml
+++ b/packages/PrintSpooler/res/values-ne-rNP/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"प्रिन्ट सेवा छनौट गर्नुहोस्"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"प्रिन्टरहरू खोज्दै"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"कुनै पनि मुद्रण सेवाहरू सक्रिय छैनन्"</string>
<string name="print_no_printers" msgid="4869403323900054866">"कुनै प्रिन्टरहरू भेटाइएन"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"प्रिन्ट गरिँदै <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"रद्द गरिँदै <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-nl/strings.xml b/packages/PrintSpooler/res/values-nl/strings.xml
index 12d52f9..bb8391e 100644
--- a/packages/PrintSpooler/res/values-nl/strings.xml
+++ b/packages/PrintSpooler/res/values-nl/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Afdrukservice kiezen"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Printers zoeken"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Geen afdrukservices ingeschakeld"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Geen printers gevonden"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> afdrukken"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> annuleren"</string>
diff --git a/packages/PrintSpooler/res/values-pa-rIN/strings.xml b/packages/PrintSpooler/res/values-pa-rIN/strings.xml
index b8d0602..aede004 100644
--- a/packages/PrintSpooler/res/values-pa-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-pa-rIN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"ਪ੍ਰਿੰਟ ਸੇਵਾ ਚੁਣੋ"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"ਪ੍ਰਿੰਟਰ ਖੋਜ ਰਿਹਾ ਹੈ"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"ਪ੍ਰਿੰਟ ਸੇਵਾਵਾਂ ਯੋਗ ਨਹੀਂ ਬਣਾਈਆਂ ਗਈਆਂ"</string>
<string name="print_no_printers" msgid="4869403323900054866">"ਕੋਈ ਪ੍ਰਿੰਟਰ ਨਹੀਂ ਮਿਲੇ"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ਨੂੰ ਪ੍ਰਿੰਟ ਕਰ ਰਿਹਾ ਹੈ"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ਨੂੰ ਰੱਦ ਕਰ ਰਿਹਾ ਹੈ"</string>
diff --git a/packages/PrintSpooler/res/values-pl/strings.xml b/packages/PrintSpooler/res/values-pl/strings.xml
index dac4b0822..4e20eed 100644
--- a/packages/PrintSpooler/res/values-pl/strings.xml
+++ b/packages/PrintSpooler/res/values-pl/strings.xml
@@ -62,6 +62,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Wybierz usługę drukowania"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Szukanie drukarek"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Brak włączonych usług drukowania"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nie znaleziono drukarek"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Drukowanie: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Anulowanie: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-pt-rBR/strings.xml b/packages/PrintSpooler/res/values-pt-rBR/strings.xml
index b073ce1..7ab0f0a 100644
--- a/packages/PrintSpooler/res/values-pt-rBR/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rBR/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Selecione o serviço de impressão"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Procurando impressoras"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Nenhum serviço de impressão ativado"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nenhuma impressora encontrada"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-pt-rPT/strings.xml b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
index da512b1..583052a 100644
--- a/packages/PrintSpooler/res/values-pt-rPT/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Escolher o serviço de impressão"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"A procurar impressoras"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Nenhum serviço de impressão ativado"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nenhuma impressora encontrada"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"A imprimir <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"A cancelar <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-pt/strings.xml b/packages/PrintSpooler/res/values-pt/strings.xml
index b073ce1..7ab0f0a 100644
--- a/packages/PrintSpooler/res/values-pt/strings.xml
+++ b/packages/PrintSpooler/res/values-pt/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Selecione o serviço de impressão"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Procurando impressoras"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Nenhum serviço de impressão ativado"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nenhuma impressora encontrada"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-ro/strings.xml b/packages/PrintSpooler/res/values-ro/strings.xml
index 1a44182..2769aae 100644
--- a/packages/PrintSpooler/res/values-ro/strings.xml
+++ b/packages/PrintSpooler/res/values-ro/strings.xml
@@ -61,6 +61,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Alegeți serviciul de printare"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Se caută imprimante"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Niciun serviciu de printare activat"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nu au fost găsite imprimante"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Se printează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Se anulează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-ru/strings.xml b/packages/PrintSpooler/res/values-ru/strings.xml
index a6ef092..c3cfac0 100644
--- a/packages/PrintSpooler/res/values-ru/strings.xml
+++ b/packages/PrintSpooler/res/values-ru/strings.xml
@@ -62,6 +62,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Выберите службу печати"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Поиск принтеров…"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Службы печати недоступны"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Ничего не найдено"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Печать задания \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\"…"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Отмена задания <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>…"</string>
diff --git a/packages/PrintSpooler/res/values-si-rLK/strings.xml b/packages/PrintSpooler/res/values-si-rLK/strings.xml
index 7520459..0e20705 100644
--- a/packages/PrintSpooler/res/values-si-rLK/strings.xml
+++ b/packages/PrintSpooler/res/values-si-rLK/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"මුද්රණ සේවාව තෝරන්න"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"මුද්රණ යන්ත්ර සොයමින්"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"මුද්රණ සේවා සබල නැත"</string>
<string name="print_no_printers" msgid="4869403323900054866">"මුද්රණ යන්ත්ර සොයා නොගැනුණි"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> මුද්රණය වේ"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"අවලංගු කෙරේ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-sk/strings.xml b/packages/PrintSpooler/res/values-sk/strings.xml
index 4575040..b8d2167 100644
--- a/packages/PrintSpooler/res/values-sk/strings.xml
+++ b/packages/PrintSpooler/res/values-sk/strings.xml
@@ -62,6 +62,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Výber tlačovej služby"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Vyhľadávanie tlačiarní"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Žiadne tlačové služby nie sú aktivované"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nenašli sa žiadne tlačiarne"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Prebieha tlač úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Prebieha zrušenie úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-sl/strings.xml b/packages/PrintSpooler/res/values-sl/strings.xml
index c0dfdb4..a320e79 100644
--- a/packages/PrintSpooler/res/values-sl/strings.xml
+++ b/packages/PrintSpooler/res/values-sl/strings.xml
@@ -62,6 +62,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Izberite tiskalno storitev"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Iskanje tiskalnikov"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Ni omogočenih tiskalnih storitev"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Tiskalnikov ni mogoče najti"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Tiskanje: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Preklic: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-sq-rAL/strings.xml b/packages/PrintSpooler/res/values-sq-rAL/strings.xml
index dbbf238..177b282 100644
--- a/packages/PrintSpooler/res/values-sq-rAL/strings.xml
+++ b/packages/PrintSpooler/res/values-sq-rAL/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Zgjidh shërbimin e printimit"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Po kërkon për printerë"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Nuk ka shërbime printimi të aktivizuara"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Nuk u gjet asnjë printer"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Po printon <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Po anulon <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-sr/strings.xml b/packages/PrintSpooler/res/values-sr/strings.xml
index 5589298..f686e6d 100644
--- a/packages/PrintSpooler/res/values-sr/strings.xml
+++ b/packages/PrintSpooler/res/values-sr/strings.xml
@@ -61,6 +61,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Изаберите услугу штампања"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Претрага штампача"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Ниједна услуга штампања није омогућена"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Није пронађен ниједан штампач"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Штампа се <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Отказује се <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-sv/strings.xml b/packages/PrintSpooler/res/values-sv/strings.xml
index a97430e..6e176f03 100644
--- a/packages/PrintSpooler/res/values-sv/strings.xml
+++ b/packages/PrintSpooler/res/values-sv/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Välj utskriftstjänst"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Söker efter skrivare"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Inga utskriftstjänster har aktiverats"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Det gick inte att hitta några skrivare"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Skriver ut <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Avbryter <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-sw/strings.xml b/packages/PrintSpooler/res/values-sw/strings.xml
index f4d51bf..8ce9190 100644
--- a/packages/PrintSpooler/res/values-sw/strings.xml
+++ b/packages/PrintSpooler/res/values-sw/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Chagua huduma ya printa"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Inatafuta printa"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Huduma za kuchapisha hazijawashwa"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Hakuna printa zilizopatikana"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Inachapisha <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Inaghairi <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-ta-rIN/strings.xml b/packages/PrintSpooler/res/values-ta-rIN/strings.xml
index 4e292d2..486ddca 100644
--- a/packages/PrintSpooler/res/values-ta-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-ta-rIN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"அச்சுப் பொறியைத் தேர்வுசெய்யவும்"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"அச்சுப்பொறிகளைத் தேடுகிறது"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"அச்சுப் பொறிகள் இல்லை"</string>
<string name="print_no_printers" msgid="4869403323900054866">"பிரிண்டர்கள் எதுவுமில்லை"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ஐ அச்சிடுகிறது"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ஐ ரத்துசெய்கிறது"</string>
diff --git a/packages/PrintSpooler/res/values-te-rIN/strings.xml b/packages/PrintSpooler/res/values-te-rIN/strings.xml
index 8cb5aa6..f0f7e07 100644
--- a/packages/PrintSpooler/res/values-te-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-te-rIN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"ముద్రణ సేవను ఎంచుకోండి"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"ప్రింటర్ల కోసం శోధిస్తోంది"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"ముద్రణ సేవలు ఏవీ ప్రారంభించలేదు"</string>
<string name="print_no_printers" msgid="4869403323900054866">"ప్రింటర్లు కనుగొనబడలేదు"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ను ముద్రిస్తోంది"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ను రద్దు చేస్తోంది"</string>
diff --git a/packages/PrintSpooler/res/values-th/strings.xml b/packages/PrintSpooler/res/values-th/strings.xml
index c501cf9..d3ef7c6 100644
--- a/packages/PrintSpooler/res/values-th/strings.xml
+++ b/packages/PrintSpooler/res/values-th/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"เลือกบริการพิมพ์"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"กำลังค้นหาเครื่องพิมพ์"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"ไม่ได้เปิดใช้บริการพิมพ์"</string>
<string name="print_no_printers" msgid="4869403323900054866">"ไม่พบเครื่องพิมพ์"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"กำลังพิมพ์ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"กำลังยกเลิก <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-tl/strings.xml b/packages/PrintSpooler/res/values-tl/strings.xml
index 18942e2..061eeb9 100644
--- a/packages/PrintSpooler/res/values-tl/strings.xml
+++ b/packages/PrintSpooler/res/values-tl/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Pumili ng serbisyo ng pag-print"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Naghahanap ng mga printer"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Walang mga naka-enable na serbisyo sa pag-print"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Walang mga printer na nakita"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Pini-print ang <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Kinakansela ang <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-tr/strings.xml b/packages/PrintSpooler/res/values-tr/strings.xml
index a7ef18c..db203ae 100644
--- a/packages/PrintSpooler/res/values-tr/strings.xml
+++ b/packages/PrintSpooler/res/values-tr/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Yazdırma hizmetini seçin"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Yazıcılar aranıyor"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Etkin yazıcı hizmeti yok"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Yazıcı bulunamadı"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> yazdırılıyor"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> iptal ediliyor"</string>
diff --git a/packages/PrintSpooler/res/values-uk/strings.xml b/packages/PrintSpooler/res/values-uk/strings.xml
index 1e21c06..09c0555 100644
--- a/packages/PrintSpooler/res/values-uk/strings.xml
+++ b/packages/PrintSpooler/res/values-uk/strings.xml
@@ -62,6 +62,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Вибрати службу друку"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Пошук принтерів"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Немає служб друку"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Принтери не знайдено"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" друкується"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" скасовується"</string>
diff --git a/packages/PrintSpooler/res/values-ur-rPK/strings.xml b/packages/PrintSpooler/res/values-ur-rPK/strings.xml
index 7356cdb..44ca82b 100644
--- a/packages/PrintSpooler/res/values-ur-rPK/strings.xml
+++ b/packages/PrintSpooler/res/values-ur-rPK/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"پرنٹ سروس منتخب کریں"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"پرنٹرز تلاش کر رہا ہے"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"کوئی پرنٹ سروس فعال نہیں"</string>
<string name="print_no_printers" msgid="4869403323900054866">"کوئی پرنٹرز نہيں ملے"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> پرنٹ کررہا ہے"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> کو منسوخ کر رہا ہے"</string>
diff --git a/packages/PrintSpooler/res/values-uz-rUZ/strings.xml b/packages/PrintSpooler/res/values-uz-rUZ/strings.xml
index 4b11608..477e424 100644
--- a/packages/PrintSpooler/res/values-uz-rUZ/strings.xml
+++ b/packages/PrintSpooler/res/values-uz-rUZ/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Chop etish xizmatini tanlang"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Printerlar qidirilmoqda"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Hech qaysi chop etish xizmati yoqilmagan"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Printerlar topilmadi"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Chop etilmoqda: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> bekor qilinmoqda"</string>
diff --git a/packages/PrintSpooler/res/values-vi/strings.xml b/packages/PrintSpooler/res/values-vi/strings.xml
index af237a4..3428ec7 100644
--- a/packages/PrintSpooler/res/values-vi/strings.xml
+++ b/packages/PrintSpooler/res/values-vi/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Chọn dịch vụ in"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Đang tìm kiếm máy in"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Chưa kích hoạt dịch vụ in nào"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Không tìm thấy máy in"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"In <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Hủy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
index 9e8e166..d1be250 100644
--- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"选择打印服务"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜索打印机"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"未启用任何打印服务"</string>
<string name="print_no_printers" msgid="4869403323900054866">"找不到打印机"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"正在打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
diff --git a/packages/PrintSpooler/res/values-zh-rHK/strings.xml b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
index 41a9f8e..bca0612 100644
--- a/packages/PrintSpooler/res/values-zh-rHK/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"選擇列印服務"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜尋打印機"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"沒有已啟用的列印服務"</string>
<string name="print_no_printers" msgid="4869403323900054866">"找不到打印機"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"正在列印 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-zh-rTW/strings.xml b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
index 5e391fa..c345252 100644
--- a/packages/PrintSpooler/res/values-zh-rTW/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"選擇列印服務"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜尋印表機"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"未啟用任何列印服務"</string>
<string name="print_no_printers" msgid="4869403323900054866">"找不到印表機"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"正在列印 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-zu/strings.xml b/packages/PrintSpooler/res/values-zu/strings.xml
index a2c3172..7ba8950 100644
--- a/packages/PrintSpooler/res/values-zu/strings.xml
+++ b/packages/PrintSpooler/res/values-zu/strings.xml
@@ -60,6 +60,7 @@
</plurals>
<string name="choose_print_service" msgid="3740309762324459694">"Khetha isevisi yephrinta"</string>
<string name="print_searching_for_printers" msgid="6550424555079932867">"Isesha amaphrinta"</string>
+ <string name="print_no_print_services" msgid="8561247706423327966">"Awekho amasevisi okuphrinta enikwe amandla"</string>
<string name="print_no_printers" msgid="4869403323900054866">"Awekho amaphrinta atholiwe"</string>
<string name="printing_notification_title_template" msgid="295903957762447362">"Iphrinta i-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Ikhansela i-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 50237832..70abdf4 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -149,6 +149,9 @@
<!-- Title for the prompt shown as a placeholder if no printers are found while not searching. [CHAR LIMIT=50] -->
<string name="print_searching_for_printers">Searching for printers</string>
+ <!-- Title for the prompt shown as a placeholder if there are no print services. [CHAR LIMIT=50] -->
+ <string name="print_no_print_services">No print services enabled</string>
+
<!-- Title for the prompt shown as a placeholder if there are no printers while searching. [CHAR LIMIT=50] -->
<string name="print_no_printers">No printers found</string>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 53e07e9..f409fd4 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -240,7 +240,9 @@
throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
+ " cannot be null");
}
- mPrintJob.setAttributes(new PrintAttributes.Builder().build());
+ if (mPrintJob.getAttributes() == null) {
+ mPrintJob.setAttributes(new PrintAttributes.Builder().build());
+ }
final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
if (adapter == null) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 3905bada..f4c15bd 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -84,6 +84,11 @@
private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
+ /**
+ * If there are any enabled print services
+ */
+ private boolean mHasEnabledPrintServices;
+
private final ArrayList<PrintServiceInfo> mAddPrinterServices =
new ArrayList<>();
@@ -175,10 +180,6 @@
}
});
- if (mAddPrinterServices.isEmpty()) {
- menu.removeItem(R.id.action_add_printer);
- }
-
return true;
}
@@ -230,6 +231,7 @@
public void onResume() {
super.onResume();
updateServicesWithAddPrinterActivity();
+ updateEmptyView((DestinationAdapter)mListView.getAdapter());
invalidateOptionsMenu();
}
@@ -258,6 +260,7 @@
}
private void updateServicesWithAddPrinterActivity() {
+ mHasEnabledPrintServices = true;
mAddPrinterServices.clear();
// Get all enabled print services.
@@ -266,6 +269,7 @@
// No enabled print services - done.
if (enabledServices.isEmpty()) {
+ mHasEnabledPrintServices = false;
return;
}
@@ -324,7 +328,10 @@
}
TextView titleView = (TextView) findViewById(R.id.title);
View progressBar = findViewById(R.id.progress_bar);
- if (adapter.getUnfilteredCount() <= 0) {
+ if (!mHasEnabledPrintServices) {
+ titleView.setText(R.string.print_no_print_services);
+ progressBar.setVisibility(View.GONE);
+ } else if (adapter.getUnfilteredCount() <= 0) {
titleView.setText(R.string.print_searching_for_printers);
progressBar.setVisibility(View.VISIBLE);
} else {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9e48849..a37196e 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -348,4 +348,7 @@
<!-- Header for items under the work user [CHAR LIMIT=30] -->
<string name="category_work">Work</string>
+ <!-- Full package name of OEM preferred device feedback reporter. Leave this blank, overlaid in Settings/TvSettings [DO NOT TRANSLATE] -->
+ <string name="oem_preferred_feedback_reporter" translatable="false" />
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
new file mode 100644
index 0000000..ff1c866
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -0,0 +1,172 @@
+/*
+ * 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.settingslib;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DeviceInfoUtils {
+ private static final String TAG = "DeviceInfoUtils";
+
+ private static final String FILENAME_PROC_VERSION = "/proc/version";
+ private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
+
+ /**
+ * Reads a line from the specified file.
+ * @param filename the file to read from
+ * @return the first line, if any.
+ * @throws IOException if the file couldn't be read
+ */
+ private static String readLine(String filename) throws IOException {
+ BufferedReader reader = new BufferedReader(new FileReader(filename), 256);
+ try {
+ return reader.readLine();
+ } finally {
+ reader.close();
+ }
+ }
+
+ public static String getFormattedKernelVersion() {
+ try {
+ return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
+ } catch (IOException e) {
+ Log.e(TAG, "IO Exception when getting kernel version for Device Info screen",
+ e);
+
+ return "Unavailable";
+ }
+ }
+
+ public static String formatKernelVersion(String rawKernelVersion) {
+ // Example (see tests for more):
+ // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \
+ // (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \
+ // Thu Jun 28 11:02:39 PDT 2012
+
+ final String PROC_VERSION_REGEX =
+ "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
+ "\\((\\S+?)\\) " + /* group 2: "x@y.com" (kernel builder) */
+ "(?:\\(gcc.+? \\)) " + /* ignore: GCC version information */
+ "(#\\d+) " + /* group 3: "#1" */
+ "(?:.*?)?" + /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
+ "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */
+
+ Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
+ if (!m.matches()) {
+ Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion);
+ return "Unavailable";
+ } else if (m.groupCount() < 4) {
+ Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
+ + " groups");
+ return "Unavailable";
+ }
+ return m.group(1) + "\n" + // 3.0.31-g6fb96c9
+ m.group(2) + " " + m.group(3) + "\n" + // x@y.com #1
+ m.group(4); // Thu Jun 28 11:02:39 PDT 2012
+ }
+
+ /**
+ * Returns " (ENGINEERING)" if the msv file has a zero value, else returns "".
+ * @return a string to append to the model number description.
+ */
+ public static String getMsvSuffix() {
+ // Production devices should have a non-zero value. If we can't read it, assume it's a
+ // production device so that we don't accidentally show that it's an ENGINEERING device.
+ try {
+ String msv = readLine(FILENAME_MSV);
+ // Parse as a hex number. If it evaluates to a zero, then it's an engineering build.
+ if (Long.parseLong(msv, 16) == 0) {
+ return " (ENGINEERING)";
+ }
+ } catch (IOException|NumberFormatException e) {
+ // Fail quietly, as the file may not exist on some devices, or may be unreadable
+ }
+ return "";
+ }
+
+ public static String getFeedbackReporterPackage(Context context) {
+ final String feedbackReporter =
+ context.getResources().getString(R.string.oem_preferred_feedback_reporter);
+ if (TextUtils.isEmpty(feedbackReporter)) {
+ // Reporter not configured. Return.
+ return feedbackReporter;
+ }
+ // Additional checks to ensure the reporter is on system image, and reporter is
+ // configured to listen to the intent. Otherwise, dont show the "send feedback" option.
+ final Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> resolvedPackages =
+ pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
+ for (ResolveInfo info : resolvedPackages) {
+ if (info.activityInfo != null) {
+ if (!TextUtils.isEmpty(info.activityInfo.packageName)) {
+ try {
+ ApplicationInfo ai =
+ pm.getApplicationInfo(info.activityInfo.packageName, 0);
+ if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Package is on the system image
+ if (TextUtils.equals(
+ info.activityInfo.packageName, feedbackReporter)) {
+ return feedbackReporter;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // No need to do anything here.
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static String getSecurityPatch() {
+ String patch = Build.VERSION.SECURITY_PATCH;
+ if (!"".equals(patch)) {
+ try {
+ SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd");
+ Date patchDate = template.parse(patch);
+ String format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dMMMMyyyy");
+ patch = DateFormat.format(format, patchDate).toString();
+ } catch (ParseException e) {
+ // broken parse; fall through and use the raw string
+ }
+ return patch;
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e4b1ed8..d994841 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -808,7 +808,12 @@
// The pairing dialog now warns of phone-book access for paired devices.
// No separate prompt is displayed after pairing.
if (getPhonebookPermissionChoice() == CachedBluetoothDevice.ACCESS_UNKNOWN) {
- setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+ if (mDevice.getBluetoothClass().getDeviceClass()
+ == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
+ setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+ } else {
+ setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
+ }
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index f5fc698..6102bef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -18,6 +18,7 @@
import android.annotation.LayoutRes;
import android.annotation.Nullable;
import android.app.Activity;
+import android.content.res.TypedArray;
import android.os.Bundle;
import android.support.v4.widget.DrawerLayout;
import android.util.Pair;
@@ -54,6 +55,13 @@
return;
}
Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
+ TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
+ if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
+ toolbar.setVisibility(View.GONE);
+ mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+ mDrawerLayout = null;
+ return;
+ }
setActionBar(toolbar);
mDrawerAdapter = new SettingsDrawerAdapter(this);
ListView listView = (ListView) findViewById(R.id.left_drawer);
diff --git a/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml b/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml
new file mode 100644
index 0000000..69edcc7
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+<!-- Recents Activity -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:zAdjustment="top">
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="250"/>
+</set>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index bf5417d..95f1eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -27,6 +27,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.view.Display;
import android.view.View;
@@ -193,6 +194,12 @@
*/
@Override
public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
+ // Ensure the device has been provisioned before allowing the user to interact with
+ // recents
+ if (!isDeviceProvisioned()) {
+ return;
+ }
+
if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
return;
}
@@ -222,6 +229,12 @@
*/
@Override
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ // Ensure the device has been provisioned before allowing the user to interact with
+ // recents
+ if (!isDeviceProvisioned()) {
+ return;
+ }
+
if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
return;
}
@@ -251,6 +264,12 @@
*/
@Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
+ // Ensure the device has been provisioned before allowing the user to interact with
+ // recents
+ if (!isDeviceProvisioned()) {
+ return;
+ }
+
if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
return;
}
@@ -280,6 +299,12 @@
*/
@Override
public void preloadRecents() {
+ // Ensure the device has been provisioned before allowing the user to interact with
+ // recents
+ if (!isDeviceProvisioned()) {
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.preloadRecents();
@@ -302,6 +327,12 @@
@Override
public void cancelPreloadingRecents() {
+ // Ensure the device has been provisioned before allowing the user to interact with
+ // recents
+ if (!isDeviceProvisioned()) {
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.cancelPreloadingRecents();
@@ -329,11 +360,23 @@
@Override
public void showNextAffiliatedTask() {
+ // Ensure the device has been provisioned before allowing the user to interact with
+ // recents
+ if (!isDeviceProvisioned()) {
+ return;
+ }
+
mImpl.showNextAffiliatedTask();
}
@Override
public void showPrevAffiliatedTask() {
+ // Ensure the device has been provisioned before allowing the user to interact with
+ // recents
+ if (!isDeviceProvisioned()) {
+ return;
+ }
+
mImpl.showPrevAffiliatedTask();
}
@@ -456,6 +499,14 @@
}
/**
+ * @return whether this device is provisioned.
+ */
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+
+ /**
* Attempts to proxy the following action to the override recents package.
* @return whether the proxying was successful
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 58f7124..3ae8827 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -42,7 +42,7 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
@@ -115,29 +115,33 @@
});
/**
- * A common Runnable to finish Recents either by calling finish() (with a custom animation) or
- * launching Home with some ActivityOptions. Generally we always launch home when we exit
- * Recents rather than just finishing the activity since we don't know what is behind Recents in
- * the task stack. The only case where we finish() directly is when we are cancelling the full
- * screen transition from the app.
+ * A common Runnable to finish Recents by launching Home with an animation depending on the
+ * last activity launch state. Generally we always launch home when we exit Recents rather than
+ * just finishing the activity since we don't know what is behind Recents in the task stack.
*/
class FinishRecentsRunnable implements Runnable {
Intent mLaunchIntent;
- ActivityOptions mLaunchOpts;
/**
- * Creates a finish runnable that starts the specified intent, using the given
- * ActivityOptions.
+ * Creates a finish runnable that starts the specified intent.
*/
- public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) {
+ public FinishRecentsRunnable(Intent launchIntent) {
mLaunchIntent = launchIntent;
- mLaunchOpts = opts;
}
@Override
public void run() {
try {
- startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
+ RecentsActivityLaunchState launchState =
+ Recents.getConfiguration().getLaunchState();
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this,
+ launchState.launchedFromSearchHome ?
+ R.anim.recents_to_search_launcher_enter :
+ R.anim.recents_to_launcher_enter,
+ launchState.launchedFromSearchHome ?
+ R.anim.recents_to_search_launcher_exit :
+ R.anim.recents_to_launcher_exit);
+ startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
} catch (Exception e) {
Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
}
@@ -191,18 +195,6 @@
mRecentsView.setTaskStack(stack);
}
- // Create the home intent runnable
- Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
- homeIntent.addCategory(Intent.CATEGORY_HOME);
- homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
- ActivityOptions.makeCustomAnimation(this,
- launchState.launchedFromSearchHome ? R.anim.recents_to_search_launcher_enter :
- R.anim.recents_to_launcher_enter,
- launchState.launchedFromSearchHome ? R.anim.recents_to_search_launcher_exit :
- R.anim.recents_to_launcher_exit));
-
// Mark the task that is the launch target
int launchTaskIndexInStack = 0;
if (launchState.launchedToTaskId != -1) {
@@ -361,6 +353,13 @@
mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
mScrimViews = new SystemBarScrimViews(this);
+ // Create the home intent runnable
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+ homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
+
// Bind the search app widget when we first start up
if (!Constants.DebugFlags.App.DisableSearchBar) {
mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
@@ -396,7 +395,7 @@
boolean wasLaunchedByAm = !launchState.launchedFromHome &&
!launchState.launchedFromAppWithThumbnail;
if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
- EventBus.getDefault().send(new EnterRecentsWindowAnimationStartedEvent());
+ EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
}
if (!launchState.launchedHasConfigurationChanged) {
@@ -422,6 +421,12 @@
}
@Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+ }
+
+ @Override
protected void onPause() {
super.onPause();
@@ -603,7 +608,7 @@
}
}
- public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
+ public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
// Try and start the enter animation (or restart it on configuration changed)
ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 4059543..50aa2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -40,7 +40,6 @@
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
@@ -70,7 +69,7 @@
* be called remotely from the system user.
*/
public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
- implements ActivityOptions.OnAnimationStartedListener, ActivityOptions.OnAnimationFinishedListener {
+ implements ActivityOptions.OnAnimationFinishedListener {
private final static String TAG = "RecentsImpl";
private final static boolean DEBUG = false;
@@ -140,7 +139,6 @@
TaskStackListenerImpl mTaskStackListener;
RecentsAppWidgetHost mAppWidgetHost;
boolean mBootCompleted;
- boolean mStartAnimationTriggered;
boolean mCanReuseTaskStackViews = true;
// Task launching
@@ -403,17 +401,22 @@
ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
// Return early if there is no running task
if (runningTask == null) return;
- // Return early if the running task is in the home stack (optimization)
- if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
// Find the task in the recents list
+ boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId);
ArrayList<Task> tasks = focusedStack.getTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 1; i--) {
Task task = tasks.get(i);
- if (task.key.id == runningTask.id) {
+ if (isTopTaskHome) {
+ toTask = tasks.get(i - 1);
+ launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_launch_next_affiliated_task_target,
+ R.anim.recents_fast_toggle_app_home_exit);
+ break;
+ } else if (task.key.id == runningTask.id) {
toTask = tasks.get(i - 1);
launchOpts = ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_launch_prev_affiliated_task_target,
@@ -613,7 +616,7 @@
final Task toTask = new Task();
final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
topTask.id, toTask);
- ForegroundThread.getHandler().post(new Runnable() {
+ ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
@Override
public void run() {
final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
@@ -635,7 +638,7 @@
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_unknown_enter,
R.anim.recents_from_unknown_exit,
- mHandler, this);
+ mHandler, null);
}
/**
@@ -646,12 +649,12 @@
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_search_launcher_enter,
R.anim.recents_from_search_launcher_exit,
- mHandler, this);
+ mHandler, null);
}
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_launcher_enter,
R.anim.recents_from_launcher_exit,
- mHandler, this);
+ mHandler, null);
}
/**
@@ -677,7 +680,7 @@
AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
specs.toArray(specsArray);
return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- specsArray, mHandler, this, this);
+ specsArray, mHandler, null, this);
} else {
// Update the destination rect
Task toTask = new Task();
@@ -688,7 +691,7 @@
if (thumbnail != null) {
return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
- (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
+ (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
}
// If both the screenshot and thumbnail fails, then just fall back to the default transition
return getUnknownTransitionActivityOptions();
@@ -841,8 +844,6 @@
private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
TaskStackLayoutAlgorithm.VisibilityReport vr) {
- mStartAnimationTriggered = false;
-
// Update the configuration based on the launch options
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
@@ -870,16 +871,7 @@
mCanReuseTaskStackViews = true;
}
- /**** OnAnimationStartedListener Implementation ****/
-
- @Override
- public void onAnimationStarted() {
- // Notify recents to start the enter animation
- if (!mStartAnimationTriggered) {
- mStartAnimationTriggered = true;
- EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
- }
- }
+ /**** OnAnimationFinishedListener Implementation ****/
@Override
public void onAnimationFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
index f187178..b31f320 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
@@ -19,8 +19,10 @@
import com.android.systemui.recents.events.EventBus;
/**
- * This is sent when the window animation into Recents starts.
+ * This is sent when the window animation into Recents completes. We use this signal to know when
+ * we can start in-app animations so that they don't conflict with the window transition into
+ * Recents.
*/
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
+public class EnterRecentsWindowAnimationCompletedEvent extends EventBus.Event {
// Simple event
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index a28601b..85b8fcf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -270,7 +270,7 @@
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
Task t = tasks.get(i);
- if (t.isFreeformTask()) {
+ if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) {
TaskView tv = stackView.getChildViewForTask(t);
if (tv == null) {
// TODO: Create a different animation task rect for this case (though it should
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index c4e2d8a..5a09ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -26,7 +26,7 @@
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
/** Manages the scrims for the various system bars. */
public class SystemBarScrimViews {
@@ -81,21 +81,11 @@
/**
* Starts animating the scrim views when entering Recents.
*/
- public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- int transitionEnterFromAppDelay = mContext.getResources().getInteger(
- R.integer.recents_enter_from_app_transition_duration);
- int transitionEnterFromHomeDelay = mContext.getResources().getInteger(
- R.integer.recents_enter_from_home_transition_duration);
-
+ public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
if (mHasStatusBarScrim && mShouldAnimateStatusBarScrim) {
mStatusBarScrimView.setTranslationY(-mStatusBarScrimView.getMeasuredHeight());
mStatusBarScrimView.animate()
.translationY(0)
- .setStartDelay(launchState.launchedFromHome ?
- transitionEnterFromHomeDelay :
- transitionEnterFromAppDelay)
.setDuration(mNavBarScrimEnterDuration)
.setInterpolator(mQuintOutInterpolator)
.withStartAction(new Runnable() {
@@ -110,9 +100,6 @@
mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight());
mNavBarScrimView.animate()
.translationY(0)
- .setStartDelay(launchState.launchedFromHome ?
- transitionEnterFromHomeDelay :
- transitionEnterFromAppDelay)
.setDuration(mNavBarScrimEnterDuration)
.setInterpolator(mQuintOutInterpolator)
.withStartAction(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 9c8829f..a57ac9d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1161,7 +1161,7 @@
TaskView frontTv = getChildViewForTask(newFrontMostTask);
if (frontTv != null) {
frontTv.onTaskBound(newFrontMostTask);
- frontTv.fadeInActionButton(0, getResources().getInteger(
+ frontTv.fadeInActionButton(getResources().getInteger(
R.integer.recents_task_enter_from_app_duration));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 4f4b91a..cb7465d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -313,10 +313,6 @@
RecentsActivityLaunchState launchState = config.getLaunchState();
Resources res = mContext.getResources();
final TaskViewTransform transform = ctx.currentTaskTransform;
- final int transitionEnterFromAppDelay = res.getInteger(
- R.integer.recents_enter_from_app_transition_duration);
- final int transitionEnterFromHomeDelay = res.getInteger(
- R.integer.recents_enter_from_home_transition_duration);
final int taskViewEnterFromAppDuration = res.getInteger(
R.integer.recents_task_enter_from_app_duration);
final int taskViewEnterFromHomeDuration = res.getInteger(
@@ -332,25 +328,22 @@
if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
// Animate the thumbnail alpha before the dim animation (to prevent updating the
// hardware layer)
- mThumbnailView.startEnterRecentsAnimation(transitionEnterFromAppDelay,
- new Runnable() {
- @Override
- public void run() {
- animateDimToProgress(0, taskViewEnterFromAppDuration,
- ctx.postAnimationTrigger.decrementOnAnimationEnd());
- }
- });
+ mThumbnailView.startEnterRecentsAnimation(new Runnable() {
+ @Override
+ public void run() {
+ animateDimToProgress(taskViewEnterFromAppDuration,
+ ctx.postAnimationTrigger.decrementOnAnimationEnd());
+ }
+ });
} else {
// Immediately start the dim animation
- animateDimToProgress(transitionEnterFromAppDelay,
- taskViewEnterFromAppDuration,
+ animateDimToProgress(taskViewEnterFromAppDuration,
ctx.postAnimationTrigger.decrementOnAnimationEnd());
}
ctx.postAnimationTrigger.increment();
// Animate the action button in
- fadeInActionButton(transitionEnterFromAppDelay,
- taskViewEnterFromAppDuration);
+ fadeInActionButton(taskViewEnterFromAppDuration);
} else {
// Animate the task up if it was occluding the launch target
if (ctx.currentTaskOccludesLaunchTarget) {
@@ -358,7 +351,6 @@
setAlpha(0f);
animate().alpha(1f)
.translationY(transform.translationY)
- .setStartDelay(transitionEnterFromAppDelay)
.setUpdateListener(null)
.setInterpolator(mFastOutSlowInInterpolator)
.setDuration(taskViewEnterFromHomeDuration)
@@ -377,8 +369,7 @@
} else if (launchState.launchedFromHome) {
// Animate the tasks up
int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
- int delay = transitionEnterFromHomeDelay +
- frontIndex * taskViewEnterFromHomeStaggerDelay;
+ int delay = frontIndex * taskViewEnterFromHomeStaggerDelay;
setScaleX(transform.scale);
setScaleY(transform.scale);
@@ -404,13 +395,12 @@
}
}
- public void fadeInActionButton(int delay, int duration) {
+ public void fadeInActionButton(int duration) {
// Hide the action button
mActionButtonView.setAlpha(0f);
// Animate the action button in
mActionButtonView.animate().alpha(1f)
- .setStartDelay(delay)
.setDuration(duration)
.setInterpolator(PhoneStatusBar.ALPHA_IN)
.start();
@@ -611,12 +601,11 @@
}
/** Animates the dim to the task progress. */
- void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {
+ void animateDimToProgress(int duration, Animator.AnimatorListener postAnimRunnable) {
// Animate the dim into view as well
int toDim = getDimFromTaskProgress();
if (toDim != getDim()) {
ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
- anim.setStartDelay(delay);
anim.setDuration(duration);
if (postAnimRunnable != null) {
anim.addListener(postAnimRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 690c297..bc50846 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -218,13 +218,13 @@
void onFocusChanged(boolean focused) {
if (focused) {
if (Float.compare(getAlpha(), 1f) != 0) {
- startFadeAnimation(1f, 0, 150, null);
+ startFadeAnimation(1f, 150, null);
}
} else {
float taskViewThumbnailAlpha = getResources().getFloat(
R.dimen.recents_task_view_thumbnail_alpha);
if (Float.compare(getAlpha(), taskViewThumbnailAlpha) != 0) {
- startFadeAnimation(taskViewThumbnailAlpha, 0, 150, null);
+ startFadeAnimation(taskViewThumbnailAlpha, 150, null);
}
}
}
@@ -244,10 +244,10 @@
}
/** Animates this task thumbnail as it enters Recents. */
- void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
+ void startEnterRecentsAnimation(Runnable postAnimRunnable) {
float taskViewThumbnailAlpha = getResources().getFloat(
R.dimen.recents_task_view_thumbnail_alpha);
- startFadeAnimation(taskViewThumbnailAlpha, delay,
+ startFadeAnimation(taskViewThumbnailAlpha,
getResources().getInteger(R.integer.recents_task_enter_from_app_duration),
postAnimRunnable);
}
@@ -256,14 +256,13 @@
void startLaunchTaskAnimation(Runnable postAnimRunnable) {
int taskViewExitToAppDuration = mContext.getResources().getInteger(
R.integer.recents_task_exit_to_app_duration);
- startFadeAnimation(1f, 0, taskViewExitToAppDuration, postAnimRunnable);
+ startFadeAnimation(1f, taskViewExitToAppDuration, postAnimRunnable);
}
/** Starts a new thumbnail alpha animation. */
- void startFadeAnimation(float finalAlpha, int delay, int duration, final Runnable postAnimRunnable) {
+ void startFadeAnimation(float finalAlpha, int duration, final Runnable postAnimRunnable) {
Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
- mThumbnailAlphaAnimator.setStartDelay(delay);
mThumbnailAlphaAnimator.setDuration(duration);
mThumbnailAlphaAnimator.setInterpolator(mFastOutSlowInInterpolator);
mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 59d4011..a520a33 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -25,8 +25,10 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region.Op;
+import android.hardware.display.DisplayManager;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -262,9 +264,13 @@
}
private void updateDisplayInfo() {
- DisplayMetrics info = mContext.getResources().getDisplayMetrics();
- mDisplayWidth = info.widthPixels;
- mDisplayHeight = info.heightPixels;
+ final DisplayManager displayManager =
+ (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ final DisplayInfo info = new DisplayInfo();
+ display.getDisplayInfo(info);
+ mDisplayWidth = info.logicalWidth;
+ mDisplayHeight = info.logicalHeight;
}
private int calculatePosition(int touchX, int touchY) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 3f0000e..069279f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -62,6 +62,7 @@
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -175,6 +176,7 @@
protected boolean mDeviceInteractive;
protected boolean mVisible;
+ protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
// mScreenOnFromKeyguard && mVisible.
private boolean mVisibleToUser;
@@ -1664,7 +1666,10 @@
return;
}
- final PendingIntent intent = sbn.getNotification().contentIntent;
+ Notification notification = sbn.getNotification();
+ final PendingIntent intent = notification.contentIntent != null
+ ? notification.contentIntent
+ : notification.fullScreenIntent;
final String notificationKey = sbn.getKey();
// Mark notification for one frame.
@@ -1746,8 +1751,8 @@
}
public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
- final PendingIntent contentIntent = sbn.getNotification().contentIntent;
- if (contentIntent != null) {
+ Notification notification = sbn.getNotification();
+ if (notification.contentIntent != null || notification.fullScreenIntent != null) {
row.setOnClickListener(this);
} else {
row.setOnClickListener(null);
@@ -2013,6 +2018,8 @@
Entry entry = mNotificationData.get(key);
if (entry == null) {
return;
+ } else if (mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
}
Notification n = notification.getNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index bbef1c0..fbe9730 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -84,10 +84,10 @@
// the close future. See b/23676310 for reference.
return;
}
- if (notif.isGroupSummary()) {
- group.summary = null;
- } else {
+ if (notif.isGroupChild()) {
group.children.remove(removed);
+ } else {
+ group.summary = null;
}
if (group.children.isEmpty()) {
if (group.summary == null) {
@@ -107,17 +107,17 @@
group = new NotificationGroup();
mGroupMap.put(groupKey, group);
}
- if (notif.isGroupSummary()) {
+ if (notif.isGroupChild()) {
+ group.children.add(added);
+ if (group.summary != null && group.children.size() == 1 && !group.expanded) {
+ group.summary.row.updateNotificationHeader();
+ }
+ } else {
group.summary = added;
group.expanded = added.row.areChildrenExpanded();
if (!group.children.isEmpty()) {
mListener.onGroupCreatedFromChildren(group);
}
- } else {
- group.children.add(added);
- if (group.summary != null && group.children.size() == 1 && !group.expanded) {
- group.summary.row.updateNotificationHeader();
- }
}
}
@@ -169,7 +169,7 @@
* @return whether a given notification is a summary in a group which has children
*/
public boolean isSummaryOfGroup(StatusBarNotification sbn) {
- if (sbn.getNotification().isGroupChild()) {
+ if (!sbn.getNotification().isGroupSummary()) {
return false;
}
NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
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 3e52515..9ab2a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -617,7 +617,6 @@
};
private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
= new HashMap<>();
- private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
private RankingMap mLatestRankingMap;
private boolean mNoAnimationOnNextBarModeChange;
private FalsingManager mFalsingManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index dc9f5e8..5cfd174 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -458,7 +458,10 @@
mReleaseOnExpandFinish = false;
} else {
for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
- removeHeadsUpEntry(entry);
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
}
}
mEntriesToRemoveAfterExpand.clear();
@@ -596,6 +599,9 @@
postTime = Math.max(postTime, currentTime);
}
removeAutoRemovalCallbacks();
+ if (mEntriesToRemoveAfterExpand.contains(entry)) {
+ mEntriesToRemoveAfterExpand.remove(entry);
+ }
if (!hasFullScreenIntent(entry) && !mRemoteInputActive) {
long finishTime = postTime + mHeadsUpNotificationDecay;
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
diff --git a/rs/java/android/renderscript/ScriptGroup.java b/rs/java/android/renderscript/ScriptGroup.java
index 54180f4..9bbacbc 100644
--- a/rs/java/android/renderscript/ScriptGroup.java
+++ b/rs/java/android/renderscript/ScriptGroup.java
@@ -278,6 +278,8 @@
public ValueAndSize(RenderScript rs, Object obj) {
if (obj instanceof Allocation) {
value = ((Allocation)obj).getID(rs);
+ // Special value for size to tell the runtime and driver that
+ // the value is an Allocation
size = -1;
} else if (obj instanceof Boolean) {
value = ((Boolean)obj).booleanValue() ? 1 : 0;
@@ -289,10 +291,10 @@
value = ((Long)obj).longValue();
size = 8;
} else if (obj instanceof Float) {
- value = ((Float)obj).longValue();
+ value = Float.floatToRawIntBits(((Float)obj).floatValue());
size = 4;
} else if (obj instanceof Double) {
- value = ((Double)obj).longValue();
+ value = Double.doubleToRawLongBits(((Double)obj).doubleValue());
size = 8;
}
}
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index be7071e..113241d 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -393,7 +393,6 @@
size_t numValues, numDependencies;
RsScriptFieldID* fieldIDs;
- uintptr_t* values;
RsClosure* depClosures;
RsScriptFieldID* depFieldIDs;
@@ -430,15 +429,6 @@
fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
}
- values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
- if (values == nullptr) {
- goto exit;
- }
-
- for (size_t i = 0; i < numValues; i++) {
- values[i] = (uintptr_t)jValues[i];
- }
-
depClosures = (RsClosure*)alloca(sizeof(RsClosure) * numDependencies);
if (depClosures == nullptr) {
goto exit;
@@ -459,7 +449,7 @@
ret = (jlong)(uintptr_t)rsClosureCreate(
(RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue,
- fieldIDs, numValues, values, numValues,
+ fieldIDs, numValues, jValues, numValues,
(int*)jSizes, numValues,
depClosures, numDependencies,
depFieldIDs, numDependencies);
@@ -511,7 +501,6 @@
size_t numValues;
RsScriptFieldID* fieldIDs;
- uintptr_t* values;
if (fieldIDs_length != values_length || values_length != sizes_length) {
ALOGE("Unmatched field IDs, values, and sizes in closure creation.");
@@ -534,18 +523,9 @@
fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
}
- values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
- if (values == nullptr) {
- goto exit;
- }
-
- for (size_t i = 0; i < numValues; i++) {
- values[i] = (uintptr_t)jValues[i];
- }
-
ret = (jlong)(uintptr_t)rsInvokeClosureCreate(
(RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength,
- fieldIDs, numValues, values, numValues,
+ fieldIDs, numValues, jValues, numValues,
(int*)jSizes, numValues);
exit:
@@ -561,15 +541,17 @@
static void
nClosureSetArg(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
jint index, jlong value, jint size) {
+ // Size is signed with -1 indicating the value is an Allocation
rsClosureSetArg((RsContext)con, (RsClosure)closureID, (uint32_t)index,
- (uintptr_t)value, (size_t)size);
+ (uintptr_t)value, size);
}
static void
nClosureSetGlobal(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
jlong fieldID, jlong value, jint size) {
+ // Size is signed with -1 indicating the value is an Allocation
rsClosureSetGlobal((RsContext)con, (RsClosure)closureID,
- (RsScriptFieldID)fieldID, (uintptr_t)value, (size_t)size);
+ (RsScriptFieldID)fieldID, (int64_t)value, size);
}
static long
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index b4411cf..781d134 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -341,7 +341,7 @@
mTransformationAnimator.start();
}
- private void setMagnificationSpec(MagnificationSpec spec) {
+ public void setMagnificationSpec(MagnificationSpec spec) {
if (DEBUG_SET_MAGNIFICATION_SPEC) {
Slog.i(LOG_TAG, "Sending: " + spec);
}
@@ -351,6 +351,10 @@
mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
}
+ public MagnificationSpec getMagnificationSpec() {
+ return mSentMagnificationSpec;
+ }
+
private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 02b5b8a..25fef18 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -79,10 +79,11 @@
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
-
import com.android.server.LocalServices;
import com.android.server.WidgetBackupProvider;
+
import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -1954,8 +1955,13 @@
if (period < MIN_UPDATE_PERIOD) {
period = MIN_UPDATE_PERIOD;
}
- mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + period, period, provider.broadcast);
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + period, period, provider.broadcast);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index fd67b41..f329cff 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -95,8 +95,6 @@
private static final int MESSAGE_BLUETOOTH_STATE_CHANGE=60;
private static final int MESSAGE_TIMEOUT_BIND =100;
private static final int MESSAGE_TIMEOUT_UNBIND =101;
- private static final int MESSAGE_GET_NAME_AND_ADDRESS=200;
- private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
private static final int MESSAGE_USER_SWITCHED = 300;
private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
@@ -587,15 +585,6 @@
}
}
- /** @hide*/
- public void getNameAndAddress() {
- if (DBG) {
- Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth +
- " mBinding = " + mBinding);
- }
- Message msg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
- mHandler.sendMessage(msg);
- }
public boolean enableNoAutoConnect()
{
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
@@ -681,14 +670,13 @@
if (mUnbinding) return;
mUnbinding = true;
if (mBluetooth != null) {
- if (!mConnection.isGetNameAddressOnly()) {
- //Unregister callback object
- try {
- mBluetooth.unregisterCallback(mBluetoothCallback);
- } catch (RemoteException re) {
- Log.e(TAG, "Unable to unregister BluetoothCallback",re);
- }
+ //Unregister callback object
+ try {
+ mBluetooth.unregisterCallback(mBluetoothCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to unregister BluetoothCallback",re);
}
+
if (DBG) Log.d(TAG, "Sending unbind request.");
mBluetooth = null;
//Unbind
@@ -780,11 +768,6 @@
if (DBG) Log.d(TAG, "Auto-enabling Bluetooth.");
sendEnableMsg(mQuietEnableExternal);
}
- if (!isNameAndAddressSet()) {
- // Sync the Bluetooth name and address from the Bluetooth Adapter
- if (DBG) Log.d(TAG, "Retrieving Bluetooth Adapter name and address...");
- getNameAndAddress();
- }
}
/**
@@ -957,42 +940,38 @@
* Inform BluetoothAdapter instances that Adapter service is up
*/
private void sendBluetoothServiceUpCallback() {
- if (!mConnection.isGetNameAddressOnly()) {
- if (DBG) Log.d(TAG,"Calling onBluetoothServiceUp callbacks");
- try {
- int n = mCallbacks.beginBroadcast();
- Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
- for (int i=0; i <n;i++) {
- try {
- mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
- }
+ if (DBG) Log.d(TAG,"Calling onBluetoothServiceUp callbacks");
+ try {
+ int n = mCallbacks.beginBroadcast();
+ Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+ for (int i=0; i <n;i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
}
- } finally {
- mCallbacks.finishBroadcast();
}
+ } finally {
+ mCallbacks.finishBroadcast();
}
}
/**
* Inform BluetoothAdapter instances that Adapter service is down
*/
private void sendBluetoothServiceDownCallback() {
- if (!mConnection.isGetNameAddressOnly()) {
- if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks");
- try {
- int n = mCallbacks.beginBroadcast();
- Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
- for (int i=0; i <n;i++) {
- try {
- mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
- }
+ if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks");
+ try {
+ int n = mCallbacks.beginBroadcast();
+ Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+ for (int i=0; i <n;i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
}
- } finally {
- mCallbacks.finishBroadcast();
}
+ } finally {
+ mCallbacks.finishBroadcast();
}
}
@@ -1052,17 +1031,6 @@
}
private class BluetoothServiceConnection implements ServiceConnection {
-
- private boolean mGetNameAddressOnly;
-
- public void setGetNameAddressOnly(boolean getOnly) {
- mGetNameAddressOnly = getOnly;
- }
-
- public boolean isGetNameAddressOnly() {
- return mGetNameAddressOnly;
- }
-
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "BluetoothServiceConnection: " + className.getClassName());
Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);
@@ -1108,104 +1076,6 @@
public void handleMessage(Message msg) {
if (DBG) Log.d (TAG, "Message: " + msg.what);
switch (msg.what) {
- case MESSAGE_GET_NAME_AND_ADDRESS: {
- if (DBG) Log.d(TAG,"MESSAGE_GET_NAME_AND_ADDRESS");
- synchronized(mConnection) {
- //Start bind request
- if ((mBluetooth == null) && (!mBinding)) {
- if (DBG) Log.d(TAG, "Binding to service to get name and address");
- mConnection.setGetNameAddressOnly(true);
- //Start bind timeout and bind
- Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
- mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
- Intent i = new Intent(IBluetooth.class.getName());
- if (!doBind(i, mConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
- UserHandle.CURRENT)) {
- mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
- } else {
- mBinding = true;
- }
- }
- else {
- Message saveMsg= mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
- saveMsg.arg1 = 0;
- if (mBluetooth != null) {
- mHandler.sendMessage(saveMsg);
- } else {
- // if enable is also called to bind the service
- // wait for MESSAGE_BLUETOOTH_SERVICE_CONNECTED
- mHandler.sendMessageDelayed(saveMsg, TIMEOUT_SAVE_MS);
- }
- }
- }
- break;
- }
- case MESSAGE_SAVE_NAME_AND_ADDRESS: {
- boolean unbind = false;
- if (DBG) Log.d(TAG,"MESSAGE_SAVE_NAME_AND_ADDRESS");
- synchronized(mConnection) {
- if (!mEnable && mBluetooth != null && !mConnection.isGetNameAddressOnly()) {
- try {
- mBluetooth.enable();
- } catch (RemoteException e) {
- Log.e(TAG,"Unable to call enable()",e);
- }
- }
- }
- if (mBluetooth != null && !mConnection.isGetNameAddressOnly()) waitForOnOff(true, false);
- synchronized(mConnection) {
- if (mBluetooth != null) {
- String name = null;
- String address = null;
- try {
- name = mBluetooth.getName();
- address = mBluetooth.getAddress();
- } catch (RemoteException re) {
- Log.e(TAG,"",re);
- }
-
- if (name != null && address != null) {
- storeNameAndAddress(name,address);
- if (mConnection.isGetNameAddressOnly()) {
- unbind = true;
- }
- } else {
- if (msg.arg1 < MAX_SAVE_RETRIES) {
- Message retryMsg = mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
- retryMsg.arg1= 1+msg.arg1;
- if (DBG) Log.d(TAG,"Retrying name/address remote retrieval and save.....Retry count =" + retryMsg.arg1);
- mHandler.sendMessageDelayed(retryMsg, TIMEOUT_SAVE_MS);
- } else {
- Log.w(TAG,"Maximum name/address remote retrieval retry exceeded");
- if (mConnection.isGetNameAddressOnly()) {
- unbind = true;
- }
- }
- }
- if (!mEnable && !mConnection.isGetNameAddressOnly()) {
- try {
- mBluetooth.disable();
- } catch (RemoteException e) {
- Log.e(TAG,"Unable to call disable()",e);
- }
- }
- } else {
- // rebind service by Request GET NAME AND ADDRESS
- // if service is unbinded by disable or
- // MESSAGE_BLUETOOTH_SERVICE_CONNECTED is not received
- Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
- mHandler.sendMessage(getMsg);
- }
- }
- if (!mEnable && mBluetooth != null && !mConnection.isGetNameAddressOnly()) {
- waitForOnOff(false, true);
- }
- if (unbind) {
- unbindAndFinish();
- }
- break;
- }
case MESSAGE_ENABLE:
if (DBG) {
Log.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth);
@@ -1308,14 +1178,6 @@
Log.e(TAG,"Unable to call configHciSnoopLog", e);
}
- if (mConnection.isGetNameAddressOnly()) {
- //Request GET NAME AND ADDRESS
- Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
- mHandler.sendMessage(getMsg);
- if (!mEnable) return;
- }
-
- mConnection.setGetNameAddressOnly(false);
//Register callback object
try {
mBluetooth.registerCallback(mBluetoothCallback);
@@ -1412,25 +1274,23 @@
SERVICE_RESTART_TIME_MS);
}
- if (!mConnection.isGetNameAddressOnly()) {
- sendBluetoothServiceDownCallback();
+ sendBluetoothServiceDownCallback();
- // Send BT state broadcast to update
- // the BT icon correctly
- if ((mState == BluetoothAdapter.STATE_TURNING_ON) ||
- (mState == BluetoothAdapter.STATE_ON)) {
- bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
- BluetoothAdapter.STATE_TURNING_OFF);
- mState = BluetoothAdapter.STATE_TURNING_OFF;
- }
- if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
- bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
- BluetoothAdapter.STATE_OFF);
- }
-
- mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
- mState = BluetoothAdapter.STATE_OFF;
+ // Send BT state broadcast to update
+ // the BT icon correctly
+ if ((mState == BluetoothAdapter.STATE_TURNING_ON) ||
+ (mState == BluetoothAdapter.STATE_ON)) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+ BluetoothAdapter.STATE_TURNING_OFF);
+ mState = BluetoothAdapter.STATE_TURNING_OFF;
}
+ if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_OFF);
+ }
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
break;
}
case MESSAGE_RESTART_BLUETOOTH_SERVICE:
@@ -1539,7 +1399,6 @@
//Start bind timeout and bind
Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
- mConnection.setGetNameAddressOnly(false);
Intent i = new Intent(IBluetooth.class.getName());
if (!doBind(i, mConnection,Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
UserHandle.CURRENT)) {
@@ -1548,21 +1407,6 @@
mBinding = true;
}
} else if (mBluetooth != null) {
- if (mConnection.isGetNameAddressOnly()) {
- // if GetNameAddressOnly is set, we can clear this flag,
- // so the service won't be unbind
- // after name and address are saved
- mConnection.setGetNameAddressOnly(false);
- //Register callback object
- try {
- mBluetooth.registerCallback(mBluetoothCallback);
- } catch (RemoteException re) {
- Log.e(TAG, "Unable to register BluetoothCallback",re);
- }
- //Inform BluetoothAdapter instances that service is up
- sendBluetoothServiceUpCallback();
- }
-
//Enable bluetooth
try {
if (!mQuietEnable) {
@@ -1594,9 +1438,7 @@
private void handleDisable() {
synchronized(mConnection) {
- // don't need to disable if GetNameAddressOnly is set,
- // service will be unbinded after Name and Address are saved
- if ((mBluetooth != null) && (!mConnection.isGetNameAddressOnly())) {
+ if (mBluetooth != null) {
if (DBG) Log.d(TAG,"Sending off request.");
try {
diff --git a/services/core/java/com/android/server/BluetoothService.java b/services/core/java/com/android/server/BluetoothService.java
index 73e8c52..019d03d 100644
--- a/services/core/java/com/android/server/BluetoothService.java
+++ b/services/core/java/com/android/server/BluetoothService.java
@@ -31,14 +31,15 @@
@Override
public void onStart() {
- Log.d(TAG, "onStart: publishing BluetoothManagerService");
- publishBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, mBluetoothManagerService);
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
+ publishBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, mBluetoothManagerService);
+ } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ Log.d(TAG, "onBootPhase: PHASE_ACTIVITY_MANAGER_READY");
mBluetoothManagerService.handleOnBootPhase();
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2b776b9..566065c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -55,8 +55,6 @@
import android.app.IAppTask;
import android.app.ITaskStackListener;
import android.app.ProfilerInfo;
-import android.app.admin.DevicePolicyManagerInternal;
-import android.app.admin.IDevicePolicyManager;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.usage.UsageEvents;
@@ -334,6 +332,11 @@
// before we decide it must be hung.
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
+ // How long we will retain processes hosting content providers in the "last activity"
+ // state before allowing them to drop down to the regular cached LRU list. This is
+ // to avoid thrashing of provider processes under low memory situations.
+ static final int CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
+
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real, when the process was
// started with a wrapper for instrumentation (such as Valgrind) because it
@@ -504,6 +507,11 @@
*/
SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
+ /**
+ * The package name of the DeviceOwner. This package is not permitted to have its data cleared.
+ */
+ String mDeviceOwnerName;
+
final UserController mUserController;
public class PendingAssistExtras extends Binder implements Runnable {
@@ -5134,12 +5142,8 @@
public boolean clearApplicationUserData(final String packageName,
final IPackageDataObserver observer, int userId) {
enforceNotIsolatedCaller("clearApplicationUserData");
-
- final DevicePolicyManagerInternal dpmi =
- LocalServices.getService(DevicePolicyManagerInternal.class);
- if (dpmi != null && dpmi.isDeviceAdminPackage(userId, packageName)) {
- throw new SecurityException(
- "Clearing DeviceAdmin/DeviceOwner/ProfileOwner data is forbidden.");
+ if (packageName != null && packageName.equals(mDeviceOwnerName)) {
+ throw new SecurityException("Clearing DeviceOwner data is forbidden.");
}
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
@@ -9240,6 +9244,17 @@
}
@Override
+ public void updateDeviceOwner(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("updateDeviceOwner called from non-system process");
+ }
+ synchronized (this) {
+ mDeviceOwnerName = packageName;
+ }
+ }
+
+ @Override
public void updateLockTaskPackages(int userId, String[] packages) {
final int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
@@ -9636,6 +9651,14 @@
if (conn.stableCount == 0 && conn.unstableCount == 0) {
cpr.connections.remove(conn);
conn.client.conProviders.remove(conn);
+ if (conn.client.setProcState < ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+ // The client is more important than last activity -- note the time this
+ // is happening, so we keep the old provider process around a bit as last
+ // activity to avoid thrashing it.
+ if (cpr.proc != null) {
+ cpr.proc.lastProviderTime = SystemClock.uptimeMillis();
+ }
+ }
stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid, cpr.name);
return true;
}
@@ -12079,8 +12102,6 @@
}
}
- enableSystemUserApps();
-
// Start up initial activity.
mBooting = true;
startHomeActivityLocked(currentUserId, "systemReady");
@@ -12132,43 +12153,6 @@
}
}
- private void enableSystemUserApps() {
- // For system user, enable apps based on the following conditions:
- // - app is whitelisted; or has no launcher icons; or has INTERACT_ACROSS_USERS permission
- // - app is not in the blacklist
- if (UserManager.isSplitSystemUser()) {
- AppsQueryHelper queryHelper = new AppsQueryHelper(mContext);
- Set<String> enableApps = new HashSet<>();
- enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
- | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
- | AppsQueryHelper.GET_DEFAULT_IMES,
- /* systemAppsOnly */ true, UserHandle.SYSTEM));
- ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
- enableApps.addAll(wlApps);
- ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
- enableApps.removeAll(blApps);
-
- List<String> systemApps = queryHelper.queryApps(0, /* systemAppsOnly */ true,
- UserHandle.SYSTEM);
- final int systemAppsSize = systemApps.size();
- for (int i = 0; i < systemAppsSize; i++) {
- String pName = systemApps.get(i);
- boolean enable = enableApps.contains(pName);
- try {
- if (enable) {
- AppGlobals.getPackageManager().installExistingPackageAsUser(pName,
- UserHandle.USER_SYSTEM);
- } else {
- AppGlobals.getPackageManager().deletePackageAsUser(pName, null,
- UserHandle.USER_SYSTEM, PackageManager.DELETE_SYSTEM_APP);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Error occured when processing package " + pName, e);
- }
- }
- }
- }
-
private boolean makeAppCrashingLocked(ProcessRecord app,
String shortMsg, String longMsg, String stackTrace) {
app.crashing = true;
@@ -18390,6 +18374,18 @@
}
}
+ if (app.lastProviderTime > 0 && (app.lastProviderTime+CONTENT_PROVIDER_RETAIN_TIME) > now) {
+ if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+ adj = ProcessList.PREVIOUS_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+ app.cached = false;
+ app.adjType = "provider";
+ }
+ if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+ procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+ }
+ }
+
if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
// A client of one of our services or providers is in the top state. We
// *may* want to be in the top state, but not if we are already running in
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ba6e9b1c..e28d198 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1338,7 +1338,12 @@
return topHomeActivity == null || !topHomeActivity.isHomeActivity();
}
- final int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1;
+ // Find the first stack below focused stack that actually got something visible.
+ int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1;
+ while (belowFocusedIndex >= 0 &&
+ mStacks.get(belowFocusedIndex).topRunningActivityLocked() == null) {
+ belowFocusedIndex--;
+ }
if ((focusedStackId == DOCKED_STACK_ID || focusedStackId == PINNED_STACK_ID)
&& stackIndex == belowFocusedIndex) {
// Stacks directly behind the docked or pinned stack are always visible.
@@ -1413,19 +1418,9 @@
if (top == null) {
return;
}
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "ensureActivitiesVisible behind " + top
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + top
+ " configChanges=0x" + Integer.toHexString(configChanges));
-
- if (mTranslucentActivityWaiting != top) {
- mUndrawnActivitiesBelowTopTranslucent.clear();
- if (mTranslucentActivityWaiting != null) {
- // Call the callback with a timeout indication.
- notifyActivityDrawnLocked(null);
- mTranslucentActivityWaiting = null;
- }
- mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
- }
+ checkTranslucentActivityWaiting(top);
// If the top activity is not fullscreen, then we need to
// make sure any activities under it are now visible.
@@ -1453,7 +1448,6 @@
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
"Make visible? " + r + " finishing=" + r.finishing
+ " state=" + r.state);
-
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
if (r != starting) {
@@ -1461,143 +1455,28 @@
}
if (r.app == null || r.app.thread == null) {
- // We need to make sure the app is running if it's the top, or it is
- // just made visible from invisible.
- // If the app is already visible, it must have died while it was visible.
- // In this case, we'll show the dead window but will not restart the app.
- // Otherwise we could end up thrashing.
- if (r == top || !r.visible) {
- // This activity needs to be visible, but isn't even running...
- // get it started and resume if no other stack in this stack is resumed.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Start and freeze screen for " + r);
- if (r != starting) {
- r.startFreezingScreenLocked(r.app, configChanges);
- }
- if (!r.visible || r.mLaunchTaskBehind) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Starting and making visible: " + r);
- setVisible(r, true);
- }
- if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(
- r, noStackActivityResumed, false);
- if (activityNdx >= activities.size()) {
- // Record may be removed if its process needs to restart.
- activityNdx = activities.size() - 1;
- } else {
- noStackActivityResumed = false;
- }
+ if (makeVisibleAndRestartIfNeeded(starting, configChanges, top,
+ noStackActivityResumed, r)) {
+ if (activityNdx >= activities.size()) {
+ // Record may be removed if its process needs to restart.
+ activityNdx = activities.size() - 1;
+ } else {
+ noStackActivityResumed = false;
}
}
} else if (r.visible) {
- // If this activity is already visible, then there is nothing
- // else to do here.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Skipping: already visible at " + r);
- r.stopFreezingScreenLocked(false);
- try {
- if (r.returningOptions != null) {
- r.app.thread.scheduleOnNewActivityOptions(r.appToken,
- r.returningOptions);
- }
- } catch(RemoteException e) {
- }
- if (r.state == ActivityState.RESUMED) {
+ if (alreadyVisible(r)) {
noStackActivityResumed = false;
}
} else {
- // This activity is not currently visible, but is running.
- // Tell it to become visible.
- r.visible = true;
- if (r.state != ActivityState.RESUMED && r != starting) {
- // If this activity is paused, tell it
- // to now show its window.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Making visible and scheduling visibility: " + r);
- try {
- if (mTranslucentActivityWaiting != null) {
- r.updateOptionsLocked(r.returningOptions);
- mUndrawnActivitiesBelowTopTranslucent.add(r);
- }
- setVisible(r, true);
- r.sleeping = false;
- r.app.pendingUiClean = true;
- r.app.thread.scheduleWindowVisibility(r.appToken, true);
- r.stopFreezingScreenLocked(false);
- } catch (Exception e) {
- // Just skip on any failure; we'll make it
- // visible when it next restarts.
- Slog.w(TAG, "Exception thrown making visibile: "
- + r.intent.getComponent(), e);
- }
- }
+ becomeVisible(starting, r);
}
-
// Aggregate current change flags.
configChanges |= r.configChangeFlags;
-
- if (r.fullscreen) {
- // At this point, nothing else needs to be shown in this task.
- behindFullscreenActivity = true;
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
- + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
- behindFullscreenActivity = true;
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
- + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- }
+ behindFullscreenActivity = updateBehindFullscreen(stackInvisible,
+ behindFullscreenActivity, task, r);
} else {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Make invisible? " + r + " finishing=" + r.finishing
- + " state=" + r.state + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- // Now for any activities that aren't visible to the user, make
- // sure they no longer are keeping the screen frozen.
- if (r.visible) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r);
- try {
- setVisible(r, false);
- switch (r.state) {
- case STOPPING:
- case STOPPED:
- if (r.app != null && r.app.thread != null) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Scheduling invisibility: " + r);
- r.app.thread.scheduleWindowVisibility(r.appToken, false);
- }
- break;
-
- case INITIALIZING:
- case RESUMED:
- case PAUSING:
- case PAUSED:
- // This case created for transitioning activities from
- // translucent to opaque {@link Activity#convertToOpaque}.
- if (getVisibleBehindActivity() == r) {
- releaseBackgroundResources(r);
- } else {
- if (!mStackSupervisor.mStoppingActivities.contains(r)) {
- mStackSupervisor.mStoppingActivities.add(r);
- }
- mStackSupervisor.scheduleIdleLocked();
- }
- break;
-
- default:
- break;
- }
- } catch (Exception e) {
- // Just skip on any failure; we'll make it
- // visible when it next restarts.
- Slog.w(TAG, "Exception thrown making hidden: "
- + r.intent.getComponent(), e);
- }
- } else {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
- }
+ becomeInvisible(stackInvisible, behindFullscreenActivity, r);
}
}
if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
@@ -1615,6 +1494,147 @@
}
}
+ private void checkTranslucentActivityWaiting(ActivityRecord top) {
+ if (mTranslucentActivityWaiting != top) {
+ mUndrawnActivitiesBelowTopTranslucent.clear();
+ if (mTranslucentActivityWaiting != null) {
+ // Call the callback with a timeout indication.
+ notifyActivityDrawnLocked(null);
+ mTranslucentActivityWaiting = null;
+ }
+ mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
+ }
+ }
+
+ private boolean makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
+ ActivityRecord top, boolean noStackActivityResumed, ActivityRecord r) {
+ // We need to make sure the app is running if it's the top, or it is just made visible from
+ // invisible. If the app is already visible, it must have died while it was visible. In this
+ // case, we'll show the dead window but will not restart the app. Otherwise we could end up
+ // thrashing.
+ if (r == top || !r.visible) {
+ // This activity needs to be visible, but isn't even running...
+ // get it started and resume if no other stack in this stack is resumed.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
+ if (r != starting) {
+ r.startFreezingScreenLocked(r.app, configChanges);
+ }
+ if (!r.visible || r.mLaunchTaskBehind) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
+ setVisible(r, true);
+ }
+ if (r != starting) {
+ mStackSupervisor.startSpecificActivityLocked(r, noStackActivityResumed, false);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void becomeInvisible(boolean stackInvisible, boolean behindFullscreenActivity,
+ ActivityRecord r) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r + " finishing="
+ + r.finishing + " state=" + r.state + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ if (!r.visible) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
+ return;
+ }
+ // Now for any activities that aren't visible to the user, make sure they no longer are
+ // keeping the screen frozen.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r);
+ try {
+ setVisible(r, false);
+ switch (r.state) {
+ case STOPPING:
+ case STOPPED:
+ if (r.app != null && r.app.thread != null) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Scheduling invisibility: " + r);
+ r.app.thread.scheduleWindowVisibility(r.appToken, false);
+ }
+ break;
+
+ case INITIALIZING:
+ case RESUMED:
+ case PAUSING:
+ case PAUSED:
+ // This case created for transitioning activities from
+ // translucent to opaque {@link Activity#convertToOpaque}.
+ if (getVisibleBehindActivity() == r) {
+ releaseBackgroundResources(r);
+ } else {
+ if (!mStackSupervisor.mStoppingActivities.contains(r)) {
+ mStackSupervisor.mStoppingActivities.add(r);
+ }
+ mStackSupervisor.scheduleIdleLocked();
+ }
+ break;
+
+ default:
+ break;
+ }
+ } catch (Exception e) {
+ // Just skip on any failure; we'll make it visible when it next restarts.
+ Slog.w(TAG, "Exception thrown making hidden: " + r.intent.getComponent(), e);
+ }
+ }
+
+ private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
+ TaskRecord task, ActivityRecord r) {
+ if (r.fullscreen) {
+ // At this point, nothing else needs to be shown in this task.
+ behindFullscreenActivity = true;
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
+ behindFullscreenActivity = true;
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ }
+ return behindFullscreenActivity;
+ }
+
+ private void becomeVisible(ActivityRecord starting, ActivityRecord r) {
+ // This activity is not currently visible, but is running. Tell it to become visible.
+ r.visible = true;
+ if (r.state != ActivityState.RESUMED && r != starting) {
+ // If this activity is paused, tell it to now show its window.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Making visible and scheduling visibility: " + r);
+ try {
+ if (mTranslucentActivityWaiting != null) {
+ r.updateOptionsLocked(r.returningOptions);
+ mUndrawnActivitiesBelowTopTranslucent.add(r);
+ }
+ setVisible(r, true);
+ r.sleeping = false;
+ r.app.pendingUiClean = true;
+ r.app.thread.scheduleWindowVisibility(r.appToken, true);
+ r.stopFreezingScreenLocked(false);
+ } catch (Exception e) {
+ // Just skip on any failure; we'll make it
+ // visible when it next restarts.
+ Slog.w(TAG, "Exception thrown making visibile: " + r.intent.getComponent(), e);
+ }
+ }
+ }
+
+ private boolean alreadyVisible(ActivityRecord r) {
+ // If this activity is already visible, then there is nothing else to do here.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
+ r.stopFreezingScreenLocked(false);
+ try {
+ if (r.returningOptions != null) {
+ r.app.thread.scheduleOnNewActivityOptions(r.appToken, r.returningOptions);
+ }
+ } catch(RemoteException e) {
+ }
+ return r.state == ActivityState.RESUMED;
+ }
+
void convertActivityToTranslucent(ActivityRecord r) {
mTranslucentActivityWaiting = r;
mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -3103,6 +3123,9 @@
// If the activity is PAUSING, we will complete the finish once
// it is done pausing; else we can just directly finish it here.
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + r);
+ if (r.visible) {
+ mWindowManager.setAppVisibility(r.appToken, false);
+ }
return finishCurrentActivityLocked(r, FINISH_AFTER_PAUSE, oomAdj) == null;
} else {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + r);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4fc8454..13d3ee1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3340,6 +3340,12 @@
return;
}
+ if (task.stack != null && task.stack.mStackId == stackId) {
+ // You are already in the right stack silly...
+ Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" + stackId);
+ return;
+ }
+
final ActivityRecord topActivity = task.getTopActivity();
if (StackId.preserveWindowOnTaskMove(stackId) && topActivity != null) {
// We are about to relaunch the activity because its configuration changed due to
@@ -3356,15 +3362,13 @@
// Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
- resizeTaskLocked(task, stack.mBounds,
- RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
+ resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
} else if (stackId == FREEFORM_WORKSPACE_STACK_ID
&& task.mBounds == null && task.mLastNonFullscreenBounds != null) {
resizeTaskLocked(task, task.mLastNonFullscreenBounds,
RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
} else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
- resizeTaskLocked(task, stack.mBounds,
- RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
+ resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
}
// The task might have already been running and its visibility needs to be synchronized with
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b77eec8..4bfe300 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -136,6 +136,7 @@
long curCpuTime; // How long proc has run CPU most recently
long lastRequestedGc; // When we last asked the app to do a gc
long lastLowMemory; // When we last told the app that memory is low
+ long lastProviderTime; // The last time someone else was using a provider in this process.
boolean reportLowMemory; // Set to true when waiting to report low mem
boolean empty; // Is this an empty background process?
boolean cached; // Is this a cached process?
@@ -317,6 +318,11 @@
pw.print(" foregroundActivities="); pw.print(foregroundActivities);
pw.print(" (rep="); pw.print(repForegroundActivities); pw.println(")");
}
+ if (lastProviderTime > 0) {
+ pw.print(prefix); pw.print("lastProviderTime=");
+ TimeUtils.formatDuration(lastProviderTime, now, pw);
+ pw.println();
+ }
if (hasStartedServices) {
pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 096c85e..b214080 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -572,8 +572,8 @@
* Removes all associated thumbnail data when a task is removed or pruned from recents.
*/
void disposeThumbnail() {
+ mLastThumbnailInfo.reset();
mLastThumbnail = null;
- mLastThumbnailInfo = null;
lastDescription = null;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 058d681..fe9fe50 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1068,6 +1068,7 @@
if (DEBUG_VOL) {
Log.d(TAG, String.format("Master mute %s, user=%d", masterMute, currentUser));
}
+ setSystemAudioMute(masterMute);
AudioSystem.setMasterMute(masterMute);
broadcastMasterMuteStatus(masterMute);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index c1aaf07..6687412 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1353,18 +1353,9 @@
if (iface != null) {
String[] dnsServers = mDefaultDnsServers;
Collection<InetAddress> dnses = linkProperties.getDnsServers();
- if (dnses != null) {
- // we currently only handle IPv4
- ArrayList<InetAddress> v4Dnses =
- new ArrayList<InetAddress>(dnses.size());
- for (InetAddress dnsAddress : dnses) {
- if (dnsAddress instanceof Inet4Address) {
- v4Dnses.add(dnsAddress);
- }
- }
- if (v4Dnses.size() > 0) {
- dnsServers = NetworkUtils.makeStrings(v4Dnses);
- }
+ if (dnses != null && !dnses.isEmpty()) {
+ // TODO: remove this invocation of NetworkUtils.makeStrings().
+ dnsServers = NetworkUtils.makeStrings(dnses);
}
try {
Network network = getConnectivityManager().getNetworkForType(upType);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 7028fa6..90dd10ea 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -948,8 +948,8 @@
// Launch the last PendingIntent we had with priority
int userId = ActivityManager.getCurrentUser();
UserRecord user = mUserRecords.get(userId);
- if (user.mLastMediaButtonReceiver != null
- || user.mRestoredMediaButtonReceiver != null) {
+ if (user != null && (user.mLastMediaButtonReceiver != null
+ || user.mRestoredMediaButtonReceiver != null)) {
if (DEBUG) {
Log.d(TAG, "Sending media key to last known PendingIntent "
+ user.mLastMediaButtonReceiver + " or restored Intent "
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 9db6a06..5b1cedc 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -239,9 +239,7 @@
throw new RuntimeException("Problem setting firewall rules", e);
}
- synchronized (mStateLock) {
- handleStateChangedLocked();
- }
+ handleStateChangedLocked();
}
public void shutdown() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 64628aa..add7a98 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -105,6 +105,7 @@
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.AppsQueryHelper;
import android.content.pm.FeatureInfo;
import android.content.pm.IOnPermissionsChangeListener;
import android.content.pm.IPackageDataObserver;
@@ -1810,10 +1811,48 @@
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
+ m.enableSystemUserApps();
ServiceManager.addService("package", m);
return m;
}
+ private void enableSystemUserApps() {
+ if (!UserManager.isSplitSystemUser()) {
+ return;
+ }
+ // For system user, enable apps based on the following conditions:
+ // - app is whitelisted or belong to one of these groups:
+ // -- system app which has no launcher icons
+ // -- system app which has INTERACT_ACROSS_USERS permission
+ // -- system IME app
+ // - app is not in the blacklist
+ AppsQueryHelper queryHelper = new AppsQueryHelper(this);
+ Set<String> enableApps = new ArraySet<>();
+ enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
+ | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
+ | AppsQueryHelper.GET_IMES, /* systemAppsOnly */ true, UserHandle.SYSTEM));
+ ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
+ enableApps.addAll(wlApps);
+ ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
+ enableApps.removeAll(blApps);
+
+ List<String> systemApps = queryHelper.queryApps(0, /* systemAppsOnly */ true,
+ UserHandle.SYSTEM);
+ final int systemAppsSize = systemApps.size();
+ synchronized (mPackages) {
+ for (int i = 0; i < systemAppsSize; i++) {
+ String pName = systemApps.get(i);
+ PackageSetting pkgSetting = mSettings.mPackages.get(pName);
+ // Should not happen, but we shouldn't be failing if it does
+ if (pkgSetting == null) {
+ continue;
+ }
+ boolean installed = enableApps.contains(pName);
+ pkgSetting.setInstalled(installed, UserHandle.USER_SYSTEM);
+ }
+ }
+ }
+
static String[] splitString(String str, char sep) {
int count = 1;
int i = 0;
@@ -1917,8 +1956,7 @@
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
- sUserManager = new UserManagerService(context, this,
- mInstallLock, mPackages);
+ sUserManager = new UserManagerService(context, this, mPackages);
// Propagate permission configuration in to package manager.
ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -12770,8 +12808,14 @@
ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
try {
if (dpm != null) {
+ final ComponentName deviceOwnerComponentName = dpm.getDeviceOwner();
+ final String deviceOwnerPackageName = deviceOwnerComponentName == null ? null
+ : deviceOwnerComponentName.getPackageName();
// Does the package contains the device owner?
- if (dpm.isDeviceOwnerPackage(packageName)) {
+ // TODO Do we have to do it even if userId != UserHandle.USER_ALL? Otherwise,
+ // this check is probably not needed, since DO should be registered as a device
+ // admin on some user too. (Original bug for this: b/17657954)
+ if (packageName.equals(deviceOwnerPackageName)) {
return true;
}
// Does it contain a device admin for any user?
@@ -16308,23 +16352,26 @@
}
/** Called by UserManagerService */
- void cleanUpUserLILPw(UserManagerService userManager, int userHandle) {
- mDirtyUsers.remove(userHandle);
- mSettings.removeUserLPw(userHandle);
- mPendingBroadcasts.remove(userHandle);
- if (mInstaller != null) {
- // Technically, we shouldn't be doing this with the package lock
- // held. However, this is very rare, and there is already so much
- // other disk I/O going on, that we'll let it slide for now.
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
- final String volumeUuid = vol.getFsUuid();
- if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
- mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+ void cleanUpUser(UserManagerService userManager, int userHandle) {
+ synchronized (mPackages) {
+ mDirtyUsers.remove(userHandle);
+ mUserNeedsBadging.delete(userHandle);
+ mSettings.removeUserLPw(userHandle);
+ mPendingBroadcasts.remove(userHandle);
+ }
+ synchronized (mInstallLock) {
+ if (mInstaller != null) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
+ mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+ }
+ }
+ synchronized (mPackages) {
+ removeUnusedPackagesLILPw(userManager, userHandle);
}
}
- mUserNeedsBadging.delete(userHandle);
- removeUnusedPackagesLILPw(userManager, userHandle);
}
/**
@@ -16374,12 +16421,18 @@
}
/** Called by UserManagerService */
- void createNewUserLILPw(int userHandle) {
+ void createNewUser(int userHandle) {
if (mInstaller != null) {
- mInstaller.createUserConfig(userHandle);
- mSettings.createNewUserLILPw(this, mInstaller, userHandle);
- applyFactoryDefaultBrowserLPw(userHandle);
- primeDomainVerificationsLPw(userHandle);
+ synchronized (mInstallLock) {
+ synchronized (mPackages) {
+ mInstaller.createUserConfig(userHandle);
+ mSettings.createNewUserLILPw(this, mInstaller, userHandle);
+ }
+ }
+ synchronized (mPackages) {
+ applyFactoryDefaultBrowserLPw(userHandle);
+ primeDomainVerificationsLPw(userHandle);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 5d8b1d2..903d12b 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -103,6 +103,9 @@
// Append privapp to existing seinfo label
private static final String PRIVILEGED_APP_STR = ":privapp";
+ // Append autoplay to existing seinfo label
+ private static final String AUTOPLAY_APP_STR = ":autoplayapp";
+
/**
* Load the mac_permissions.xml file containing all seinfo assignments used to
* label apps. The loaded mac_permissions.xml file is determined by the
@@ -316,6 +319,9 @@
}
}
+ if (pkg.applicationInfo.isAutoPlayApp())
+ pkg.applicationInfo.seinfo += AUTOPLAY_APP_STR;
+
if (pkg.applicationInfo.isPrivilegedApp())
pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3a1d2de..ab0b182 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -97,8 +97,7 @@
*
* Method naming convention:
* <ul>
- * <li> Methods suffixed with "LILP" should be called within {@link #mInstallLock} and
- * {@link #mPackagesLock} locks obtained in the respective order.
+ * <li> Methods suffixed with "LP" should be called within the {@link #mPackagesLock} lock.
* <li> Methods suffixed with "LR" should be called within the {@link #mRestrictionsLock} lock.
* <li> Methods suffixed with "LU" should be called within the {@link #mUsersLock} lock.
* </ul>
@@ -164,7 +163,6 @@
private final Context mContext;
private final PackageManagerService mPm;
- private final Object mInstallLock;
private final Object mPackagesLock;
// Short-term lock for internal state, when interaction/sync with PM is not required
private final Object mUsersLock = new Object();
@@ -215,6 +213,7 @@
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+ @GuardedBy("mGuestRestrictions")
private final Bundle mGuestRestrictions = new Bundle();
/**
@@ -226,6 +225,7 @@
@GuardedBy("mUsersLock")
private int[] mUserIds;
+ @GuardedBy("mPackagesLock")
private int mNextSerialNumber;
private int mUserVersion = 0;
@@ -245,11 +245,9 @@
}
}
- /**
- * Available for testing purposes.
- */
- UserManagerService(File dataDir, File baseUserPath) {
- this(null, null, new Object(), new Object(), dataDir, baseUserPath);
+ @VisibleForTesting
+ UserManagerService(File dataDir) {
+ this(null, null, new Object(), dataDir);
}
/**
@@ -257,68 +255,53 @@
* associated with the package manager, and the given lock is the
* package manager's own lock.
*/
- UserManagerService(Context context, PackageManagerService pm,
- Object installLock, Object packagesLock) {
- this(context, pm, installLock, packagesLock,
- Environment.getDataDirectory(),
- new File(Environment.getDataDirectory(), "user"));
+ UserManagerService(Context context, PackageManagerService pm, Object packagesLock) {
+ this(context, pm, packagesLock, Environment.getDataDirectory());
}
- /**
- * Available for testing purposes.
- */
private UserManagerService(Context context, PackageManagerService pm,
- Object installLock, Object packagesLock,
- File dataDir, File baseUserPath) {
+ Object packagesLock, File dataDir) {
mContext = context;
mPm = pm;
- mInstallLock = installLock;
mPackagesLock = packagesLock;
mHandler = new MainHandler();
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- mUsersDir = new File(dataDir, USER_INFO_DIR);
- mUsersDir.mkdirs();
- // Make zeroth user directory, for services to migrate their files to that location
- File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
- userZeroDir.mkdirs();
- FileUtils.setPermissions(mUsersDir.toString(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG
- |FileUtils.S_IROTH|FileUtils.S_IXOTH,
- -1, -1);
- mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
- initDefaultGuestRestrictions();
- readUserListLILP();
- sInstance = this;
- }
+ synchronized (mPackagesLock) {
+ mUsersDir = new File(dataDir, USER_INFO_DIR);
+ mUsersDir.mkdirs();
+ // Make zeroth user directory, for services to migrate their files to that location
+ File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
+ userZeroDir.mkdirs();
+ FileUtils.setPermissions(mUsersDir.toString(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
+ -1, -1);
+ mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
+ initDefaultGuestRestrictions();
+ readUserListLP();
+ sInstance = this;
}
mLocalService = new LocalService();
LocalServices.addService(UserManagerInternal.class, mLocalService);
}
void systemReady() {
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- synchronized (mUsersLock) {
- // Prune out any partially created/partially removed users.
- ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- UserInfo ui = mUsers.valueAt(i);
- if ((ui.partial || ui.guestToRemove) && i != 0) {
- partials.add(ui);
- }
- }
- final int partialsSize = partials.size();
- for (int i = 0; i < partialsSize; i++) {
- UserInfo ui = partials.get(i);
- Slog.w(LOG_TAG, "Removing partially created user " + ui.id
- + " (name=" + ui.name + ")");
- removeUserStateLILP(ui.id);
- }
+ // Prune out any partially created/partially removed users.
+ ArrayList<UserInfo> partials = new ArrayList<>();
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i);
+ if ((ui.partial || ui.guestToRemove) && i != 0) {
+ partials.add(ui);
}
}
}
+ final int partialsSize = partials.size();
+ for (int i = 0; i < partialsSize; i++) {
+ UserInfo ui = partials.get(i);
+ Slog.w(LOG_TAG, "Removing partially created user " + ui.id
+ + " (name=" + ui.name + ")");
+ removeUserState(ui.id);
+ }
onUserForeground(UserHandle.USER_SYSTEM);
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -627,17 +610,22 @@
public void makeInitialized(int userId) {
checkManageUsersPermission("makeInitialized");
- synchronized (mPackagesLock) {
- UserInfo info = getUserInfoNoChecks(userId);
+ boolean scheduleWriteUser = false;
+ UserInfo info;
+ synchronized (mUsersLock) {
+ info = mUsers.get(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
- // TODO Check if we should return here instead of a null check below
+ return;
}
- if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+ if ((info.flags & UserInfo.FLAG_INITIALIZED) == 0) {
info.flags |= UserInfo.FLAG_INITIALIZED;
- scheduleWriteUser(info);
+ scheduleWriteUser = true;
}
}
+ if (scheduleWriteUser) {
+ scheduleWriteUser(info);
+ }
}
/**
@@ -645,17 +633,18 @@
* restrictions.
*/
private void initDefaultGuestRestrictions() {
- if (mGuestRestrictions.isEmpty()) {
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+ synchronized (mGuestRestrictions) {
+ if (mGuestRestrictions.isEmpty()) {
+ mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
+ mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+ }
}
}
@Override
public Bundle getDefaultGuestRestrictions() {
checkManageUsersPermission("getDefaultGuestRestrictions");
- // TODO Switch to mGuestRestrictions for locking
- synchronized (mPackagesLock) {
+ synchronized (mGuestRestrictions) {
return new Bundle(mGuestRestrictions);
}
}
@@ -663,12 +652,12 @@
@Override
public void setDefaultGuestRestrictions(Bundle restrictions) {
checkManageUsersPermission("setDefaultGuestRestrictions");
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- mGuestRestrictions.clear();
- mGuestRestrictions.putAll(restrictions);
- writeUserListLILP();
- }
+ synchronized (mGuestRestrictions) {
+ mGuestRestrictions.clear();
+ mGuestRestrictions.putAll(restrictions);
+ }
+ synchronized (mPackagesLock) {
+ writeUserListLP();
}
}
@@ -775,7 +764,7 @@
Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
!= newRestrictions);
mBaseUserRestrictions.put(userId, newRestrictions);
- scheduleWriteUser(mUsers.get(userId));
+ scheduleWriteUser(getUserInfoNoChecks(userId));
}
final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
@@ -996,9 +985,9 @@
}
}
- private void readUserListLILP() {
+ private void readUserListLP() {
if (!mUserListFile.exists()) {
- fallbackToSingleUserLILP();
+ fallbackToSingleUserLP();
return;
}
FileInputStream fis = null;
@@ -1015,7 +1004,7 @@
if (type != XmlPullParser.START_TAG) {
Slog.e(LOG_TAG, "Unable to read user list");
- fallbackToSingleUserLILP();
+ fallbackToSingleUserLP();
return;
}
@@ -1036,7 +1025,7 @@
final String name = parser.getName();
if (name.equals(TAG_USER)) {
String id = parser.getAttributeValue(null, ATTR_ID);
- UserInfo user = readUserLILP(Integer.parseInt(id));
+ UserInfo user = readUserLP(Integer.parseInt(id));
if (user != null) {
synchronized (mUsersLock) {
@@ -1051,8 +1040,10 @@
&& type != XmlPullParser.END_TAG) {
if (type == XmlPullParser.START_TAG) {
if (parser.getName().equals(TAG_RESTRICTIONS)) {
- UserRestrictionsUtils
- .readRestrictions(parser, mGuestRestrictions);
+ synchronized (mGuestRestrictions) {
+ UserRestrictionsUtils
+ .readRestrictions(parser, mGuestRestrictions);
+ }
}
break;
}
@@ -1061,25 +1052,18 @@
}
}
updateUserIds();
- upgradeIfNecessaryLILP();
- } catch (IOException ioe) {
- fallbackToSingleUserLILP();
- } catch (XmlPullParserException pe) {
- fallbackToSingleUserLILP();
+ upgradeIfNecessaryLP();
+ } catch (IOException | XmlPullParserException e) {
+ fallbackToSingleUserLP();
} finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
+ IoUtils.closeQuietly(fis);
}
}
/**
* Upgrade steps between versions, either for fixing bugs or changing the data format.
*/
- private void upgradeIfNecessaryLILP() {
+ private void upgradeIfNecessaryLP() {
int userVersion = mUserVersion;
if (userVersion < 1) {
// Assign a proper name for the owner, if not initialized correctly before
@@ -1132,11 +1116,11 @@
+ USER_VERSION);
} else {
mUserVersion = userVersion;
- writeUserListLILP();
+ writeUserListLP();
}
}
- private void fallbackToSingleUserLILP() {
+ private void fallbackToSingleUserLP() {
int flags = UserInfo.FLAG_INITIALIZED;
// In split system user mode, the admin and primary flags are assigned to the first human
// user.
@@ -1161,7 +1145,7 @@
updateUserIds();
initDefaultGuestRestrictions();
- writeUserListLILP();
+ writeUserListLP();
writeUserLP(system);
}
@@ -1247,8 +1231,7 @@
* <user id="2"></user>
* </users>
*/
- private void writeUserListLILP() {
- // TODO Investigate removing a dependency on mInstallLock
+ private void writeUserListLP() {
FileOutputStream fos = null;
AtomicFile userListFile = new AtomicFile(mUserListFile);
try {
@@ -1266,8 +1249,10 @@
serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion));
serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
- UserRestrictionsUtils
- .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+ synchronized (mGuestRestrictions) {
+ UserRestrictionsUtils
+ .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+ }
serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
int[] userIdsToWrite;
synchronized (mUsersLock) {
@@ -1293,7 +1278,7 @@
}
}
- private UserInfo readUserLILP(int id) {
+ private UserInfo readUserLP(int id) {
int flags = 0;
int serialNumber = id;
String name = null;
@@ -1468,8 +1453,7 @@
}
private UserInfo createUserInternal(String name, int flags, int parentId) {
- if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
- UserManager.DISALLOW_ADD_USER, false)) {
+ if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) {
Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
return null;
}
@@ -1480,120 +1464,114 @@
final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
final long ident = Binder.clearCallingIdentity();
- UserInfo userInfo = null;
+ UserInfo userInfo;
final int userId;
try {
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- UserInfo parent = null;
- if (parentId != UserHandle.USER_NULL) {
- synchronized (mUsersLock) {
- parent = getUserInfoLU(parentId);
- }
- if (parent == null) return null;
+ synchronized (mPackagesLock) {
+ UserInfo parent = null;
+ if (parentId != UserHandle.USER_NULL) {
+ synchronized (mUsersLock) {
+ parent = getUserInfoLU(parentId);
}
- if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
- Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
+ if (parent == null) return null;
+ }
+ if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
+ Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
+ return null;
+ }
+ if (!isGuest && !isManagedProfile && isUserLimitReached()) {
+ // If we're not adding a guest user or a managed profile and the limit has
+ // been reached, cannot add a user.
+ return null;
+ }
+ // If we're adding a guest and there already exists one, bail.
+ if (isGuest && findCurrentGuestUser() != null) {
+ return null;
+ }
+ // In legacy mode, restricted profile's parent can only be the owner user
+ if (isRestricted && !UserManager.isSplitSystemUser()
+ && (parentId != UserHandle.USER_SYSTEM)) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+ return null;
+ }
+ if (isRestricted && UserManager.isSplitSystemUser()) {
+ if (parent == null) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ + "specified");
return null;
}
- if (!isGuest && !isManagedProfile && isUserLimitReached()) {
- // If we're not adding a guest user or a managed profile and the limit has
- // been reached, cannot add a user.
+ if (!parent.canHaveProfile()) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ + "created for the specified parent user id " + parentId);
return null;
}
- // If we're adding a guest and there already exists one, bail.
- if (isGuest && findCurrentGuestUser() != null) {
- return null;
+ }
+ // In split system user mode, we assign the first human user the primary flag.
+ // And if there is no device owner, we also assign the admin flag to primary user.
+ if (UserManager.isSplitSystemUser()
+ && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
+ flags |= UserInfo.FLAG_PRIMARY;
+ DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (devicePolicyManager == null
+ || devicePolicyManager.getDeviceOwner() == null) {
+ flags |= UserInfo.FLAG_ADMIN;
}
- // In legacy mode, restricted profile's parent can only be the owner user
- if (isRestricted && !UserManager.isSplitSystemUser()
- && (parentId != UserHandle.USER_SYSTEM)) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
- return null;
- }
- if (isRestricted && UserManager.isSplitSystemUser()) {
- if (parent == null) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
- + "specified");
- return null;
- }
- if (!parent.canHaveProfile()) {
- Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
- + "created for the specified parent user id " + parentId);
- return null;
- }
- }
- // In split system user mode, we assign the first human user the primary flag.
- // And if there is no device owner, we also assign the admin flag to primary
- // user.
- if (UserManager.isSplitSystemUser()
- && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
- flags |= UserInfo.FLAG_PRIMARY;
- DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- if (devicePolicyManager == null
- || devicePolicyManager.getDeviceOwner() == null) {
- flags |= UserInfo.FLAG_ADMIN;
- }
- }
- userId = getNextAvailableId();
- userInfo = new UserInfo(userId, name, null, flags);
- userInfo.serialNumber = mNextSerialNumber++;
- long now = System.currentTimeMillis();
- userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
- userInfo.partial = true;
- Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+ }
+ userId = getNextAvailableId();
+ userInfo = new UserInfo(userId, name, null, flags);
+ userInfo.serialNumber = mNextSerialNumber++;
+ long now = System.currentTimeMillis();
+ userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
+ userInfo.partial = true;
+ Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+ synchronized (mUsersLock) {
mUsers.put(userId, userInfo);
- writeUserListLILP();
- if (parent != null) {
- if (isManagedProfile) {
- if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- parent.profileGroupId = parent.id;
- scheduleWriteUser(parent);
- }
- userInfo.profileGroupId = parent.profileGroupId;
- } else if (isRestricted) {
- if (!parent.canHaveProfile()) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
- }
- if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
- parent.restrictedProfileParentId = parent.id;
- scheduleWriteUser(parent);
- }
- userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
+ }
+ writeUserListLP();
+ if (parent != null) {
+ if (isManagedProfile) {
+ if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ parent.profileGroupId = parent.id;
+ writeUserLP(parent);
}
- }
-
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- storage.createUserKey(userId, userInfo.serialNumber);
- for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
- final String volumeUuid = vol.getFsUuid();
- try {
- final File userDir = Environment.getDataUserDirectory(volumeUuid,
- userId);
- storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
- enforceSerialNumber(userDir, userInfo.serialNumber);
- } catch (IOException e) {
- Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+ userInfo.profileGroupId = parent.profileGroupId;
+ } else if (isRestricted) {
+ if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
+ parent.restrictedProfileParentId = parent.id;
+ writeUserLP(parent);
}
- }
- mPm.createNewUserLILPw(userId);
- userInfo.partial = false;
- scheduleWriteUser(userInfo);
- updateUserIds();
- Bundle restrictions = new Bundle();
- synchronized (mRestrictionsLock) {
- mBaseUserRestrictions.append(userId, restrictions);
+ userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
}
}
}
- mPm.newUserCreated(userId);
- if (userInfo != null) {
- Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
- mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
- android.Manifest.permission.MANAGE_USERS);
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ storage.createUserKey(userId, userInfo.serialNumber);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ try {
+ final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
+ storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
+ enforceSerialNumber(userDir, userInfo.serialNumber);
+ } catch (IOException e) {
+ Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+ }
}
+ mPm.createNewUser(userId);
+ userInfo.partial = false;
+ synchronized (mPackagesLock) {
+ writeUserLP(userInfo);
+ }
+ updateUserIds();
+ Bundle restrictions = new Bundle();
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.append(userId, restrictions);
+ }
+ mPm.newUserCreated(userId);
+ Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+ addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
+ android.Manifest.permission.MANAGE_USERS);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1782,11 +1760,7 @@
// Clean up any ActivityManager state
LocalServices.getService(ActivityManagerInternal.class)
.onUserRemoved(userHandle);
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- removeUserStateLILP(userHandle);
- }
- }
+ removeUserState(userHandle);
}
}.start();
}
@@ -1798,10 +1772,10 @@
}
}
- private void removeUserStateLILP(final int userHandle) {
+ private void removeUserState(final int userHandle) {
mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
// Cleanup package manager settings
- mPm.cleanUpUserLILPw(this, userHandle);
+ mPm.cleanUpUser(this, userHandle);
// Remove this user from the list
synchronized (mUsersLock) {
@@ -1811,7 +1785,9 @@
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
userFile.delete();
// Update the user list
- writeUserListLILP();
+ synchronized (mPackagesLock) {
+ writeUserListLP();
+ }
updateUserIds();
removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
}
@@ -2146,17 +2122,15 @@
* @param userId the user that was just foregrounded
*/
public void onUserForeground(int userId) {
- synchronized (mPackagesLock) {
- UserInfo user = getUserInfoNoChecks(userId);
- long now = System.currentTimeMillis();
- if (user == null || user.partial) {
- Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
- return;
- }
- if (now > EPOCH_PLUS_30_YEARS) {
- user.lastLoggedInTime = now;
- scheduleWriteUser(user);
- }
+ UserInfo user = getUserInfoNoChecks(userId);
+ if (user == null || user.partial) {
+ Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
+ return;
+ }
+ long now = System.currentTimeMillis();
+ if (now > EPOCH_PLUS_30_YEARS) {
+ user.lastLoggedInTime = now;
+ scheduleWriteUser(user);
}
}
@@ -2164,7 +2138,6 @@
* Returns the next available user id, filling in any holes in the ids.
* TODO: May not be a good idea to recycle ids, in case it results in confusion
* for data and battery stats collection, or unexpected cross-talk.
- * @return
*/
private int getNextAvailableId() {
synchronized (mUsersLock) {
@@ -2348,7 +2321,9 @@
}
pw.println();
pw.println("Guest restrictions:");
- UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions);
+ synchronized (mGuestRestrictions) {
+ UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions);
+ }
}
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index ac79b36..97713fc 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -54,6 +54,15 @@
@Override
public void onReceive(Context context, Intent intent) {
+ // When a package is replaced we will receive two intents, one representing the
+ // removal of the old package and one representing the addition of the new
+ // package. We here ignore the intent representing the removed package to make
+ // sure we don't change WebView provider twice.
+ if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)
+ && intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
+ return;
+ }
+
for (String packageName : WebViewFactory.getWebViewPackageNames()) {
String webviewPackage = "package:" + packageName;
@@ -73,7 +82,8 @@
}
};
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mWebViewUpdatedReceiver, filter);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f54fd83..4e38f67 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -55,17 +55,6 @@
* from mDisplayWindows; */
private final WindowList mWindows = new WindowList();
- // This protects the following display size properties, so that
- // getDisplaySize() doesn't need to acquire the global lock. This is
- // needed because the window manager sometimes needs to use ActivityThread
- // while it has its global state locked (for example to load animation
- // resources), but the ActivityThread also needs get the current display
- // size sometimes when it has its package lock held.
- //
- // These will only be modified with both mWindowMap and mDisplaySizeLock
- // held (in that order) so the window manager doesn't need to acquire this
- // lock when needing these values in its normal operation.
- final Object mDisplaySizeLock = new Object();
int mInitialDisplayWidth = 0;
int mInitialDisplayHeight = 0;
int mInitialDisplayDensity = 0;
@@ -202,18 +191,16 @@
}
void initializeDisplayBaseInfo() {
- synchronized(mDisplaySizeLock) {
- // Bootstrap the default logical display from the display manager.
- final DisplayInfo newDisplayInfo =
- mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId);
- if (newDisplayInfo != null) {
- mDisplayInfo.copyFrom(newDisplayInfo);
- }
- mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
- mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
- mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
- mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+ // Bootstrap the default logical display from the display manager.
+ final DisplayInfo newDisplayInfo =
+ mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId);
+ if (newDisplayInfo != null) {
+ mDisplayInfo.copyFrom(newDisplayInfo);
}
+ mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
+ mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
+ mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
+ mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
}
void getLogicalDisplayRect(Rect out) {
@@ -291,16 +278,18 @@
final ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks();
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
final Task task = tasks.get(taskNdx);
- // We need to use the visible frame on the window for any touch-related tests.
- // Can't use the task's bounds because the original task bounds might be adjusted
- // to fit the content frame. For example, the presence of the IME adjusting the
+ final WindowState win = task.getTopVisibleAppMainWindow();
+ if (win == null) {
+ continue;
+ }
+ // We need to use the task's dim bounds (which is derived from the visible
+ // bounds of its apps windows) for any touch-related tests. Can't use
+ // the task's original bounds because it might be adjusted to fit the
+ // content frame. For example, the presence of the IME adjusting the
// windows frames when the app window is the IME target.
- final WindowState win = task.getTopAppMainWindow();
- if (win != null) {
- win.getVisibleBounds(mTmpRect);
- if (mTmpRect.contains(x, y)) {
- return task.mTaskId;
- }
+ task.getDimBounds(mTmpRect);
+ if (mTmpRect.contains(x, y)) {
+ return task.mTaskId;
}
}
}
@@ -308,10 +297,10 @@
}
/**
- * Find the window whose outside touch area (for resizing) (x, y) falls within.
+ * Find the task whose outside touch area (for resizing) (x, y) falls within.
* Returns null if the touch doesn't fall into a resizing area.
*/
- WindowState findWindowForControlPoint(int x, int y) {
+ Task findTaskForControlPoint(int x, int y) {
final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
TaskStack stack = mStacks.get(stackNdx);
@@ -325,24 +314,22 @@
return null;
}
- // We need to use the visible frame on the window for any touch-related
- // tests. Can't use the task's bounds because the original task bounds
- // might be adjusted to fit the content frame. (One example is when the
- // task is put to top-left quadrant, the actual visible frame would not
- // start at (0,0) after it's adjusted for the status bar.)
- final WindowState win = task.getTopAppMainWindow();
- if (win != null) {
- win.getVisibleBounds(mTmpRect);
- mTmpRect.inset(-delta, -delta);
- if (mTmpRect.contains(x, y)) {
- mTmpRect.inset(delta, delta);
- if (!mTmpRect.contains(x, y)) {
- return win;
- }
- // User touched inside the task. No need to look further,
- // focus transfer will be handled in ACTION_UP.
- return null;
+ // We need to use the task's dim bounds (which is derived from the visible
+ // bounds of its apps windows) for any touch-related tests. Can't use
+ // the task's original bounds because it might be adjusted to fit the
+ // content frame. One example is when the task is put to top-left quadrant,
+ // the actual visible area would not start at (0,0) after it's adjusted
+ // for the status bar.
+ task.getDimBounds(mTmpRect);
+ mTmpRect.inset(-delta, -delta);
+ if (mTmpRect.contains(x, y)) {
+ mTmpRect.inset(delta, delta);
+ if (!mTmpRect.contains(x, y)) {
+ return task;
}
+ // User touched inside the task. No need to look further,
+ // focus transfer will be handled in ACTION_UP.
+ return null;
}
}
}
@@ -351,12 +338,18 @@
void setTouchExcludeRegion(Task focusedTask) {
mTouchExcludeRegion.set(mBaseDisplayRect);
- WindowList windows = getWindowList();
final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
- for (int i = windows.size() - 1; i >= 0; --i) {
- final WindowState win = windows.get(i);
- final Task task = win.getTask();
- if (win.isVisibleLw() && task != null) {
+ boolean addBackFocusedTask = false;
+ for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+ TaskStack stack = mStacks.get(stackNdx);
+ final ArrayList<Task> tasks = stack.getTasks();
+ for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ final Task task = tasks.get(taskNdx);
+ final WindowState win = task.getTopVisibleAppMainWindow();
+ if (win == null) {
+ continue;
+ }
+
/**
* Exclusion region is the region that TapDetector doesn't care about.
* Here we want to remove all non-focused tasks from the exclusion region.
@@ -368,13 +361,17 @@
*/
final boolean isFreeformed = task.inFreeformWorkspace();
if (task != focusedTask || isFreeformed) {
- mTmpRect.set(win.mVisibleFrame);
- mTmpRect.intersect(win.mVisibleInsets);
- /**
- * If the task is freeformed, enlarge the area to account for outside
- * touch area for resize.
- */
+ task.getDimBounds(mTmpRect);
if (isFreeformed) {
+ // If we're removing a freeform, focused app from the exclusion region,
+ // we need to add back its touchable frame later. Remember the touchable
+ // frame now.
+ if (task == focusedTask) {
+ addBackFocusedTask = true;
+ mTmpRect2.set(mTmpRect);
+ }
+ // If the task is freeformed, enlarge the area to account for outside
+ // touch area for resize.
mTmpRect.inset(-delta, -delta);
// Intersect with display content rect. If we have system decor (status bar/
// navigation bar), we want to exclude that from the tap detection.
@@ -385,17 +382,14 @@
}
mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
}
- /**
- * If we removed the focused task above, add it back and only leave its
- * outside touch area in the exclusion. TapDectector is not interested in
- * any touch inside the focused task itself.
- */
- if (task == focusedTask && isFreeformed) {
- mTmpRect.inset(delta, delta);
- mTouchExcludeRegion.op(mTmpRect, Region.Op.UNION);
- }
}
}
+ // If we removed the focused task above, add it back and only leave its
+ // outside touch area in the exclusion. TapDectector is not interested in
+ // any touch inside the focused task itself.
+ if (addBackFocusedTask) {
+ mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
+ }
if (mTapDetector != null) {
mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1b86488..46cd7cd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -284,7 +284,12 @@
boolean getMaxVisibleBounds(Rect out) {
boolean foundTop = false;
for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final WindowState win = mAppTokens.get(i).findMainWindow();
+ final AppWindowToken token = mAppTokens.get(i);
+ // skip hidden (or about to hide) apps
+ if (token.mIsExiting || token.clientHidden || token.hiddenRequested) {
+ continue;
+ }
+ final WindowState win = token.findMainWindow();
if (win == null) {
continue;
}
@@ -413,14 +418,20 @@
return mStack != null && mStack.mStackId == DOCKED_STACK_ID;
}
- WindowState getTopAppMainWindow() {
- final int tokensCount = mAppTokens.size();
- return tokensCount > 0 ? mAppTokens.get(tokensCount - 1).findMainWindow() : null;
+ WindowState getTopVisibleAppMainWindow() {
+ final AppWindowToken token = getTopVisibleAppToken();
+ return token != null ? token.findMainWindow() : null;
}
- AppWindowToken getTopAppWindowToken() {
- final int tokensCount = mAppTokens.size();
- return tokensCount > 0 ? mAppTokens.get(tokensCount - 1) : null;
+ AppWindowToken getTopVisibleAppToken() {
+ for (int i = mAppTokens.size() - 1; i >= 0; i--) {
+ final AppWindowToken token = mAppTokens.get(i);
+ // skip hidden (or about to hide) apps
+ if (!token.mIsExiting && !token.clientHidden && !token.hiddenRequested) {
+ return token;
+ }
+ }
+ return null;
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index dd47e7a..f5e4e3b 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -332,30 +332,34 @@
+ ", {" + startX + ", " + startY + "}");
}
mCtrlType = CTRL_NONE;
+ mTask = win.getTask();
+ mStartDragX = startX;
+ mStartDragY = startY;
+
+ // Use the dim bounds, not the original task bounds. The cursor
+ // movement should be calculated relative to the visible bounds.
+ // Also, use the dim bounds of the task which accounts for
+ // multiple app windows. Don't use any bounds from win itself as it
+ // may not be the same size as the task.
+ mTask.getDimBounds(mTmpRect);
+
if (resize) {
- final Rect visibleFrame = win.mVisibleFrame;
- if (startX < visibleFrame.left) {
+ if (startX < mTmpRect.left) {
mCtrlType |= CTRL_LEFT;
}
- if (startX > visibleFrame.right) {
+ if (startX > mTmpRect.right) {
mCtrlType |= CTRL_RIGHT;
}
- if (startY < visibleFrame.top) {
+ if (startY < mTmpRect.top) {
mCtrlType |= CTRL_TOP;
}
- if (startY > visibleFrame.bottom) {
+ if (startY > mTmpRect.bottom) {
mCtrlType |= CTRL_BOTTOM;
}
mResizing = true;
}
- mTask = win.getTask();
- mStartDragX = startX;
- mStartDragY = startY;
-
- // Use the visible bounds, not the original task bounds. The cursor
- // movement should be calculated relative to the visible bounds.
- mWindowOriginalBounds.set(win.mVisibleFrame);
+ mWindowOriginalBounds.set(mTmpRect);
}
private void endDragLocked() {
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 1fe359e..f5b83bb 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -90,11 +90,11 @@
case MotionEvent.ACTION_HOVER_MOVE: {
final int x = (int) motionEvent.getX();
final int y = (int) motionEvent.getY();
- final WindowState window = mDisplayContent.findWindowForControlPoint(x, y);
- if (window == null) {
+ final Task task = mDisplayContent.findTaskForControlPoint(x, y);
+ if (task == null) {
break;
}
- window.getVisibleBounds(mTmpRect);
+ task.getDimBounds(mTmpRect);
if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
int iconShape = STYLE_DEFAULT;
if (x < mTmpRect.left) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 38bd71d..a96bd2c 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -67,7 +67,7 @@
private final WindowSurfacePlacer mWindowPlacerLocked;
/** Is any window animating? */
- boolean mAnimating;
+ private boolean mAnimating;
/** Is any app window animating? */
boolean mAppWindowAnimating;
@@ -168,7 +168,8 @@
appAnimator.wasAnimating = appAnimator.animating;
if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
appAnimator.animating = true;
- mAnimating = mAppWindowAnimating = true;
+ setAnimating(true);
+ mAppWindowAnimating = true;
} else if (appAnimator.wasAnimating) {
// stopped animating, do one more pass through the layout
setAppLayoutChanges(appAnimator,
@@ -186,7 +187,8 @@
final AppWindowAnimator appAnimator = exitingAppTokens.get(i).mAppAnimator;
appAnimator.wasAnimating = appAnimator.animating;
if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
- mAnimating = mAppWindowAnimating = true;
+ setAnimating(true);
+ mAppWindowAnimating = true;
} else if (appAnimator.wasAnimating) {
// stopped animating, do one more pass through the layout
setAppLayoutChanges(appAnimator,
@@ -282,7 +284,7 @@
final boolean wasAnimating = winAnimator.mWasAnimating;
final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
winAnimator.mWasAnimating = nowAnimating;
- mAnimating |= nowAnimating;
+ orAnimating(nowAnimating);
if (DEBUG_WALLPAPER) {
Slog.v(TAG, win + ": wasAnimating=" + wasAnimating +
@@ -546,7 +548,7 @@
}
}
}
- mAnimating = true;
+ setAnimating(true);
}
// If this window's app token is running a detached wallpaper
@@ -617,7 +619,7 @@
// We can now show all of the drawn windows!
if (!mService.mOpeningApps.contains(wtoken)) {
- mAnimating |= appAnimator.showAllWindowsLocked();
+ orAnimating(appAnimator.showAllWindowsLocked());
}
}
}
@@ -636,7 +638,7 @@
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
boolean wasAnimating = mAnimating;
- mAnimating = false;
+ setAnimating(false);
mAppWindowAnimating = false;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
@@ -657,7 +659,7 @@
displayAnimator.mScreenRotationAnimation;
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
- mAnimating = true;
+ setAnimating(true);
} else {
mBulkUpdateParams |= SET_UPDATE_ROTATION;
screenRotationAnimation.kill();
@@ -697,7 +699,7 @@
screenRotationAnimation.updateSurfacesInTransaction();
}
- mAnimating |= mService.getDisplayContentLocked(displayId).animateDimLayers();
+ orAnimating(mService.getDisplayContentLocked(displayId).animateDimLayers());
//TODO (multidisplay): Magnification is supported only for the default display.
if (mService.mAccessibilityController != null
@@ -920,4 +922,16 @@
private class DisplayContentsAnimator {
ScreenRotationAnimation mScreenRotationAnimation = null;
}
+
+ boolean isAnimating() {
+ return mAnimating;
+ }
+
+ void setAnimating(boolean animating) {
+ mAnimating = animating;
+ }
+
+ void orAnimating(boolean animating) {
+ mAnimating |= animating;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f17698c..4293f0a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2737,6 +2737,10 @@
if (win.mAppToken != null) {
win.mAppToken.updateReportedVisibilityLocked();
}
+ if (winAnimator.mReportSurfaceResized) {
+ winAnimator.mReportSurfaceResized = false;
+ result |= WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED;
+ }
outFrame.set(win.mCompatFrame);
outOverscanInsets.set(win.mOverscanInsets);
outContentInsets.set(win.mContentInsets);
@@ -6904,27 +6908,25 @@
final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- synchronized(displayContent.mDisplaySizeLock) {
- displayInfo.rotation = mRotation;
- displayInfo.logicalWidth = dw;
- displayInfo.logicalHeight = dh;
- displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
- displayInfo.appWidth = appWidth;
- displayInfo.appHeight = appHeight;
- displayInfo.getLogicalMetrics(mRealDisplayMetrics,
- CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
- displayInfo.getAppMetrics(mDisplayMetrics);
- if (displayContent.mDisplayScalingDisabled) {
- displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
- } else {
- displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
- }
-
- mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
- displayContent.getDisplayId(), displayInfo);
-
- displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
+ displayInfo.rotation = mRotation;
+ displayInfo.logicalWidth = dw;
+ displayInfo.logicalHeight = dh;
+ displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
+ displayInfo.appWidth = appWidth;
+ displayInfo.appHeight = appHeight;
+ displayInfo.getLogicalMetrics(mRealDisplayMetrics,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ displayInfo.getAppMetrics(mDisplayMetrics);
+ if (displayContent.mDisplayScalingDisabled) {
+ displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
+ } else {
+ displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
}
+
+ mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
+ displayContent.getDisplayId(), displayInfo);
+
+ displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
if (false) {
Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
}
@@ -7069,15 +7071,16 @@
}
private void startResizingTask(DisplayContent displayContent, int startX, int startY) {
- WindowState win = null;
+ Task task = null;
synchronized (mWindowMap) {
- win = displayContent.findWindowForControlPoint(startX, startY);
- if (win == null || !startPositioningLocked(win, true /*resize*/, startX, startY)) {
+ task = displayContent.findTaskForControlPoint(startX, startY);
+ if (task == null || !startPositioningLocked(
+ task.getTopVisibleAppMainWindow(), true /*resize*/, startX, startY)) {
return;
}
}
try {
- mActivityManager.setFocusedTask(win.getTask().mTaskId);
+ mActivityManager.setFocusedTask(task.mTaskId);
} catch(RemoteException e) {}
}
@@ -7682,7 +7685,7 @@
synchronized (mWindowMap) {
// Since we're holding both mWindowMap and mAnimator we don't need to
// hold mAnimator.mLayoutToAnim.
- if (mAnimator.mAnimating || mAnimationScheduled) {
+ if (mAnimator.isAnimating() || mAnimationScheduled) {
// If we are animating, don't do the gc now but
// delay a bit so we don't interrupt the animation.
sendEmptyMessageDelayed(H.FORCE_GC, 2000);
@@ -8055,10 +8058,8 @@
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- synchronized(displayContent.mDisplaySizeLock) {
- size.x = displayContent.mInitialDisplayWidth;
- size.y = displayContent.mInitialDisplayHeight;
- }
+ size.x = displayContent.mInitialDisplayWidth;
+ size.y = displayContent.mInitialDisplayHeight;
}
}
}
@@ -8068,10 +8069,8 @@
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- synchronized(displayContent.mDisplaySizeLock) {
- size.x = displayContent.mBaseDisplayWidth;
- size.y = displayContent.mBaseDisplayHeight;
- }
+ size.x = displayContent.mBaseDisplayWidth;
+ size.y = displayContent.mBaseDisplayHeight;
}
}
}
@@ -8140,13 +8139,9 @@
}
}
- private void setForcedDisplayScalingModeLocked(DisplayContent displayContent,
- int mode) {
+ private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, int mode) {
Slog.i(TAG, "Using display scaling mode: " + (mode == 0 ? "auto" : "off"));
-
- synchronized(displayContent.mDisplaySizeLock) {
- displayContent.mDisplayScalingDisabled = (mode != 0);
- }
+ displayContent.mDisplayScalingDisabled = (mode != 0);
reconfigureDisplayLocked(displayContent);
}
@@ -8164,13 +8159,11 @@
try {
width = Integer.parseInt(sizeStr.substring(0, pos));
height = Integer.parseInt(sizeStr.substring(pos+1));
- synchronized(displayContent.mDisplaySizeLock) {
- if (displayContent.mBaseDisplayWidth != width
- || displayContent.mBaseDisplayHeight != height) {
- Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
- }
+ if (displayContent.mBaseDisplayWidth != width
+ || displayContent.mBaseDisplayHeight != height) {
+ Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
+ displayContent.mBaseDisplayWidth = width;
+ displayContent.mBaseDisplayHeight = height;
}
} catch (NumberFormatException ex) {
}
@@ -8187,11 +8180,9 @@
int density;
try {
density = Integer.parseInt(densityStr);
- synchronized(displayContent.mDisplaySizeLock) {
- if (displayContent.mBaseDisplayDensity != density) {
- Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
- displayContent.mBaseDisplayDensity = density;
- }
+ if (displayContent.mBaseDisplayDensity != density) {
+ Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
+ displayContent.mBaseDisplayDensity = density;
}
} catch (NumberFormatException ex) {
}
@@ -8201,21 +8192,16 @@
int mode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DISPLAY_SCALING_FORCE, 0);
if (mode != 0) {
- synchronized(displayContent.mDisplaySizeLock) {
- Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
- displayContent.mDisplayScalingDisabled = true;
- }
+ Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
+ displayContent.mDisplayScalingDisabled = true;
}
}
// displayContent must not be null
private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
Slog.i(TAG, "Using new display size: " + width + "x" + height);
-
- synchronized(displayContent.mDisplaySizeLock) {
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
- }
+ displayContent.mBaseDisplayWidth = width;
+ displayContent.mBaseDisplayHeight = height;
reconfigureDisplayLocked(displayContent);
}
@@ -8251,9 +8237,7 @@
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- synchronized(displayContent.mDisplaySizeLock) {
- return displayContent.mInitialDisplayDensity;
- }
+ return displayContent.mInitialDisplayDensity;
}
}
return -1;
@@ -8264,9 +8248,7 @@
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- synchronized(displayContent.mDisplaySizeLock) {
- return displayContent.mBaseDisplayDensity;
- }
+ return displayContent.mBaseDisplayDensity;
}
}
return -1;
@@ -8301,10 +8283,7 @@
// displayContent must not be null
private void setForcedDisplayDensityLocked(DisplayContent displayContent, int density) {
Slog.i(TAG, "Using new display density: " + density);
-
- synchronized(displayContent.mDisplaySizeLock) {
- displayContent.mBaseDisplayDensity = density;
- }
+ displayContent.mBaseDisplayDensity = density;
reconfigureDisplayLocked(displayContent);
}
@@ -8395,12 +8374,10 @@
private void setOverscanLocked(DisplayContent displayContent,
int left, int top, int right, int bottom) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- synchronized (displayContent.mDisplaySizeLock) {
- displayInfo.overscanLeft = left;
- displayInfo.overscanTop = top;
- displayInfo.overscanRight = right;
- displayInfo.overscanBottom = bottom;
- }
+ displayInfo.overscanLeft = left;
+ displayInfo.overscanTop = top;
+ displayInfo.overscanRight = right;
+ displayInfo.overscanBottom = bottom;
mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, displayInfo.name, left, top,
right, bottom);
@@ -9987,14 +9964,11 @@
DisplayInfo displayInfo = displayContent.getDisplayInfo();
final Rect rect = new Rect();
mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
- synchronized (displayContent.mDisplaySizeLock) {
- displayInfo.overscanLeft = rect.left;
- displayInfo.overscanTop = rect.top;
- displayInfo.overscanRight = rect.right;
- displayInfo.overscanBottom = rect.bottom;
- mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
- displayId, displayInfo);
- }
+ displayInfo.overscanLeft = rect.left;
+ displayInfo.overscanTop = rect.top;
+ displayInfo.overscanRight = rect.right;
+ displayInfo.overscanBottom = rect.bottom;
+ mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(displayId, displayInfo);
configureDisplayPolicyLocked(displayContent);
// TODO: Create an input channel for each display with touch capability.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 93b40c0..673c21f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -729,8 +729,18 @@
mVisibleFrame.set(mContentFrame);
mStableFrame.set(mContentFrame);
} else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
- mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
- mContentFrame.set(mFrame);
+ if (isVisibleLw()) {
+ // We don't adjust the dock divider frame for reasons other than performance. The
+ // real reason is that if it gets adjusted before it is shown for the first time,
+ // it would get size (0, 0). This causes a problem when we finally show the dock
+ // divider and try to draw to it. We do set the surface size at that moment to
+ // the correct size, but it's too late for the Surface Flinger to make it
+ // available for view rendering and as a result the renderer receives size 1, 1.
+ // This way we just keep the divider at the original size and Surface Flinger
+ // will return the correct value to the renderer.
+ mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
+ mContentFrame.set(mFrame);
+ }
} else {
mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
Math.max(mContentFrame.top, mFrame.top),
@@ -1698,7 +1708,7 @@
Task task = getTask();
if (task == null || task.inHomeStack()
- || task.getTopAppWindowToken() != mAppToken) {
+ || task.getTopVisibleAppToken() != mAppToken) {
// Don't save surfaces for home stack apps. These usually resume and draw
// first frame very fast. Saving surfaces are mostly a waste of memory.
// Don't save if the window is not the topmost window.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a3bb320..f7805b1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,7 +16,9 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYERS;
@@ -24,12 +26,12 @@
import static com.android.server.wm.WindowManagerService.DEBUG_STARTING_WINDOW;
import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerService.localLOGV;
import static com.android.server.wm.WindowManagerService.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerService.SHOW_SURFACE_ALLOC;
import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
-import static com.android.server.wm.WindowState.*;
+import static com.android.server.wm.WindowManagerService.localLOGV;
+import static com.android.server.wm.WindowState.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
@@ -37,7 +39,6 @@
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Debug;
@@ -47,12 +48,10 @@
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.Surface.OutOfResourcesException;
-import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
@@ -61,7 +60,6 @@
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
-import java.util.ArrayList;
/**
* Keep track of animations and surface operations for a single WindowState.
@@ -101,6 +99,11 @@
* we must tell them application to resize (and thus redraw itself).
*/
boolean mSurfaceResized;
+ /**
+ * Whether we should inform the client on next relayoutWindow that
+ * the surface has been resized since last time.
+ */
+ boolean mReportSurfaceResized;
WindowSurfaceController mSurfaceController;
private WindowSurfaceController mPendingDestroySurface;
@@ -183,6 +186,8 @@
int mAttrType;
+ private final Rect mTmpSize = new Rect();
+
WindowStateAnimator(final WindowState win) {
final WindowManagerService service = win.mService;
@@ -442,7 +447,7 @@
if (!isWindowAnimating()) {
//TODO (multidisplay): Accessibility is supported only for the default display.
if (mService.mAccessibilityController != null
- && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ && mWin.getDisplayId() == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
}
@@ -496,7 +501,7 @@
}
if (mDrawState == DRAW_PENDING) {
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
- Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + this + " in "
+ Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + mWin + " in "
+ mSurfaceController);
if (DEBUG_STARTING_WINDOW && startingWindow) {
Slog.v(TAG, "Draw state now committed in " + mWin);
@@ -528,7 +533,7 @@
mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = performShowLocked();
}
- if (mDestroyPreservedSurfaceUponRedraw && result) {
+ if (mDestroyPreservedSurfaceUponRedraw) {
mService.mDestroyPreservedSurface.add(mWin);
}
return result;
@@ -581,52 +586,16 @@
flags |= SurfaceControl.SECURE;
}
- float left = w.mFrame.left + w.mXOffset;
- float top = w.mFrame.top + w.mYOffset;
-
- int width;
- int height;
- if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) {
- // for a scaled surface, we always want the requested
- // size.
- width = w.mRequestedWidth;
- height = w.mRequestedHeight;
- } else {
- // When we're doing a drag-resizing, request a surface that's fullscreen size,
- // so that we don't need to reallocate during the process. This also prevents
- // buffer drops due to size mismatch.
- final DisplayInfo displayInfo = w.getDisplayInfo();
- if (displayInfo != null && w.isDragResizing()) {
- left = 0;
- top = 0;
- width = displayInfo.logicalWidth;
- height = displayInfo.logicalHeight;
- } else {
- width = w.mCompatFrame.width();
- height = w.mCompatFrame.height();
- }
- }
-
- // Something is wrong and SurfaceFlinger will not like this,
- // try to revert to sane values
- if (width <= 0) {
- width = 1;
- }
- if (height <= 0) {
- height = 1;
- }
-
- // Adjust for surface insets.
- width += attrs.surfaceInsets.left + attrs.surfaceInsets.right;
- height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
- left -= attrs.surfaceInsets.left;
- top -= attrs.surfaceInsets.top;
+ mTmpSize.set(w.mFrame.left + w.mXOffset, w.mFrame.top + w.mYOffset, 0, 0);
+ calculateSurfaceBounds(w, attrs);
+ final int width = mTmpSize.width();
+ final int height = mTmpSize.height();
if (DEBUG_VISIBILITY) {
Slog.v(TAG, "Creating surface in session "
+ mSession.mSurfaceSession + " window " + this
+ " w=" + width + " h=" + height
- + " x=" + left + " y=" + top
+ + " x=" + mTmpSize.left + " y=" + mTmpSize.top
+ " format=" + attrs.format + " flags=" + flags);
}
@@ -692,15 +661,15 @@
Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
WindowManagerService.logSurface(w, "CREATE pos=("
+ w.mFrame.left + "," + w.mFrame.top + ") ("
- + w.mCompatFrame.width() + "x" + w.mCompatFrame.height()
- + "), layer=" + mAnimLayer + " HIDE", null);
+ + width + "x" + height + "), layer=" + mAnimLayer + " HIDE", null);
}
// Start a new transaction and apply position & offset.
final int layerStack = w.getDisplayContent().getDisplay().getLayerStack();
if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
- "POS " + left + ", " + top, null);
- mSurfaceController.setPositionAndLayer(left, top, layerStack, mAnimLayer);
+ "POS " + mTmpSize.left + ", " + mTmpSize.top, null);
+ mSurfaceController.setPositionAndLayer(mTmpSize.left, mTmpSize.top, layerStack,
+ mAnimLayer);
mLastHidden = true;
if (WindowManagerService.localLOGV) Slog.v(
@@ -709,6 +678,57 @@
return mSurfaceController;
}
+ private void calculateSurfaceBounds(WindowState w, LayoutParams attrs) {
+ if ((attrs.flags & FLAG_SCALED) != 0) {
+ // For a scaled surface, we always want the requested size.
+ mTmpSize.right = mTmpSize.left + w.mRequestedWidth;
+ mTmpSize.bottom = mTmpSize.top + w.mRequestedHeight;
+ } else {
+ // When we're doing a drag-resizing, request a surface that's fullscreen size,
+ // so that we don't need to reallocate during the process. This also prevents
+ // buffer drops due to size mismatch.
+ if (w.isDragResizing()) {
+ if (w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM) {
+ mTmpSize.left = 0;
+ mTmpSize.top = 0;
+ }
+ final DisplayInfo displayInfo = w.getDisplayInfo();
+ mTmpSize.right = mTmpSize.left + displayInfo.logicalWidth;
+ mTmpSize.bottom = mTmpSize.top + displayInfo.logicalHeight;
+ } else {
+ mTmpSize.right = mTmpSize.left + w.mCompatFrame.width();
+ mTmpSize.bottom = mTmpSize.top + w.mCompatFrame.height();
+ }
+ }
+
+ // Something is wrong and SurfaceFlinger will not like this, try to revert to sane values.
+ if (mTmpSize.width() < 1) {
+ Slog.w(TAG, "Width of " + w + " is not positive " + mTmpSize.width());
+ mTmpSize.right = mTmpSize.left + 1;
+ }
+ if (mTmpSize.height() < 1) {
+ Slog.w(TAG, "Height of " + w + " is not positive " + mTmpSize.height());
+ mTmpSize.bottom = mTmpSize.top + 1;
+ }
+
+ final int displayId = w.getDisplayId();
+ float scale = 1.0f;
+ // Magnification is supported only for the default display.
+ if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
+ final MagnificationSpec spec =
+ mService.mAccessibilityController.getMagnificationSpecForWindowLocked(w);
+ if (spec != null && !spec.isNop()) {
+ scale = spec.scale;
+ }
+ }
+
+ // Adjust for surface insets.
+ mTmpSize.left -= scale * attrs.surfaceInsets.left;
+ mTmpSize.top -= scale * attrs.surfaceInsets.top;
+ mTmpSize.right += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
+ mTmpSize.bottom += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
+ }
+
void destroySurfaceLocked() {
final AppWindowToken wtoken = mWin.mAppToken;
if (wtoken != null) {
@@ -891,7 +911,7 @@
tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
MagnificationSpec spec = mService.mAccessibilityController
.getMagnificationSpecForWindowLocked(mWin);
if (spec != null && !spec.isNop()) {
@@ -974,7 +994,7 @@
MagnificationSpec spec = null;
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
spec = mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
}
if (spec != null) {
@@ -1069,7 +1089,7 @@
} else if (w.mDecorFrame.isEmpty()) {
// Windows without policy decor aren't cropped.
w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
- } else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.mAnimating) {
+ } else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.isAnimating()) {
// If we're animating, the wallpaper crop should only be updated at the end of the
// animation.
mTmpClipRect.set(w.mSystemDecorRect);
@@ -1109,12 +1129,8 @@
// The clip rect was generated assuming (0,0) as the window origin,
// so we need to translate to match the actual surface coordinates.
clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
- // We don't want to clip to stack bounds windows that are currently doing entrance
- // animation for docked window, otherwise the animating window will be suddenly cut off.
- if (!(mAnimator.mAnimating && w.inDockedWorkspace())) {
- adjustCropToStackBounds(w, clipRect, isFreeformResizing);
- }
+ adjustCropToStackBounds(w, clipRect, isFreeformResizing);
w.transformFromScreenToSurfaceSpace(clipRect);
@@ -1127,100 +1143,57 @@
private void adjustCropToStackBounds(WindowState w, Rect clipRect, boolean isFreeformResizing) {
final AppWindowToken appToken = w.mAppToken;
final Task task = w.getTask();
- // We don't apply the the stack bounds to the window that is being replaced, because it was
- // living in a different stack. If we suddenly crop it to the new stack bounds, it might
- // get cut off. We don't want it to happen, so we let it ignore the stack bounds until it
- // gets removed. The window that will replace it will abide them.
- if (task != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
- TaskStack stack = task.mStack;
- stack.getDimBounds(mTmpStackBounds);
- // When we resize we use the big surface approach, which means we can't trust the
- // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
- // hardcoding it, we use surface coordinates.
- final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
- w.mFrame.left + mWin.mXOffset - w.getAttrs().surfaceInsets.left;
- final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
- w.mFrame.top + mWin.mYOffset - w.getAttrs().surfaceInsets.top;
- // We need to do some acrobatics with surface position, because their clip region is
- // relative to the inside of the surface, but the stack bounds aren't.
- clipRect.left = Math.max(0,
- Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
- clipRect.top = Math.max(0,
- Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
- clipRect.right = Math.max(0,
- Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
- clipRect.bottom = Math.max(0,
- Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
+ if (task == null || !appToken.mCropWindowsToStack) {
+ return;
}
+
+ // We don't apply the stack bounds crop if:
+ // 1. The window is currently animating docked mode, otherwise the animating window will be
+ // suddenly cut off.
+ // 2. The window that is being replaced during animation, because it was living in a
+ // different stack. If we suddenly crop it to the new stack bounds, it might get cut off.
+ // We don't want it to happen, so we let it ignore the stack bounds until it gets removed.
+ // The window that will replace it will abide them.
+ if (isAnimating() && (appToken.mWillReplaceWindow || w.inDockedWorkspace())) {
+ return;
+ }
+
+ final TaskStack stack = task.mStack;
+ stack.getDimBounds(mTmpStackBounds);
+ // When we resize we use the big surface approach, which means we can't trust the
+ // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
+ // hardcoding it, we use surface coordinates.
+ final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
+ w.mFrame.left + mWin.mXOffset - w.getAttrs().surfaceInsets.left;
+ final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
+ w.mFrame.top + mWin.mYOffset - w.getAttrs().surfaceInsets.top;
+ // We need to do some acrobatics with surface position, because their clip region is
+ // relative to the inside of the surface, but the stack bounds aren't.
+ clipRect.left = Math.max(0,
+ Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
+ clipRect.top = Math.max(0,
+ Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
+ clipRect.right = Math.max(0,
+ Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
+ clipRect.bottom = Math.max(0,
+ Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
}
void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
final WindowState w = mWin;
- float left = w.mShownPosition.x;
- float top = w.mShownPosition.y;
+ mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0);
+ calculateSurfaceBounds(w, w.getAttrs());
- int width;
- int height;
- if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
- // for a scaled surface, we always want the requested
- // size.
- width = w.mRequestedWidth;
- height = w.mRequestedHeight;
- } else {
- // When we're doing a drag-resizing, request a surface that's fullscreen size,
- // so that we don't need to reallocate during the process. This also prevents
- // buffer drops due to size mismatch.
- final DisplayInfo displayInfo = w.getDisplayInfo();
-
- // In freeform resize mode, put surface at 0/0.
- if (w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM) {
- left = 0;
- top = 0;
- }
- if (displayInfo != null && w.isDragResizing()) {
- width = displayInfo.logicalWidth;
- height = displayInfo.logicalHeight;
- } else {
- width = w.mCompatFrame.width();
- height = w.mCompatFrame.height();
- }
- }
-
- // Something is wrong and SurfaceFlinger will not like this,
- // try to revert to sane values
- if (width < 1) {
- width = 1;
- }
- if (height < 1) {
- height = 1;
- }
-
- // Adjust for surface insets.
- final LayoutParams attrs = w.getAttrs();
- final int displayId = w.getDisplayId();
- float scale = 1.0f;
- // Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
- MagnificationSpec spec =
- mService.mAccessibilityController.getMagnificationSpecForWindowLocked(w);
- if (spec != null && !spec.isNop()) {
- scale = spec.scale;
- }
- }
-
- width += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
- height += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
- left -= scale * attrs.surfaceInsets.left;
- top -= scale * attrs.surfaceInsets.top;
-
- mSurfaceController.setPositionInTransaction(left, top, recoveringMemory);
- mSurfaceResized = mSurfaceController.setSizeInTransaction(width, height,
+ mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, recoveringMemory);
+ mSurfaceResized = mSurfaceController.setSizeInTransaction(
+ mTmpSize.width(), mTmpSize.height(),
mDsDx * w.mHScale, mDtDx * w.mVScale,
mDsDy * w.mHScale, mDtDy * w.mVScale,
recoveringMemory);
if (mSurfaceResized) {
+ mReportSurfaceResized = true;
mAnimator.setPendingLayoutChanges(w.getDisplayId(),
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
w.applyDimLayerIfNeeded();
@@ -1449,7 +1422,7 @@
// Force the show in the next prepareSurfaceLocked() call.
mLastAlpha = -1;
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM)
- Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this);
+ Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + mWin);
mDrawState = HAS_DRAWN;
mService.scheduleAnimationLocked();
@@ -1532,7 +1505,7 @@
applyAnimationLocked(transit, true);
//TODO (multidisplay): Magnification is supported only for the default display.
if (mService.mAccessibilityController != null
- && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ && mWin.getDisplayId() == DEFAULT_DISPLAY) {
mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 3b57634..ae96658 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -542,11 +542,8 @@
mService.scheduleAnimationLocked();
- if (DEBUG_WINDOW_TRACE) {
- Slog.e(TAG,
- "performSurfacePlacementInner exit: animating="
- + mService.mAnimator.mAnimating);
- }
+ if (DEBUG_WINDOW_TRACE) Slog.e(TAG,
+ "performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
}
private void applySurfaceChangesTransaction(boolean recoveringMemory, int numDisplays,
@@ -1184,7 +1181,7 @@
">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
SurfaceControl.openTransaction();
try {
- mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+ mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
} finally {
SurfaceControl.closeTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
@@ -1467,7 +1464,7 @@
appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
}
mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
- mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+ mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index eee7d92..c611503 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -156,11 +156,6 @@
/**
* Implementation of the device policy APIs.
- *
- * Locking policies:
- * - {@link DevicePolicyManagerService} must not call into {@link IActivityManager} within {@code
- * this} lock to avoid lock inversion.
- * - Methods that call into {@link IActivityManager} must have the "AM" suffix.
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@@ -1110,7 +1105,7 @@
.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
}
- IActivityManager getIActivityManagerInner() {
+ IActivityManager getIActivityManager() {
return ActivityManagerNative.getDefault();
}
@@ -1241,16 +1236,6 @@
}
/**
- * Caller must not hold {@code this} lock. See also the class javadoc.
- */
- final IActivityManager getIActivityManager() {
- if (Thread.holdsLock(this)) {
- Slog.wtfStack(LOG_TAG, "Call to ActivityManager detected within DPMS lock");
- }
- return mInjector.getIActivityManagerInner();
- }
-
- /**
* Instantiates the service.
*/
public DevicePolicyManagerService(Context context) {
@@ -1364,6 +1349,8 @@
// TODO PO may not have a class name either due to b/17652534. Address that too.
+ updateDeviceOwnerLocked();
+
// TODO Notify UM to update restrictions (?)
}
}
@@ -1625,25 +1612,17 @@
@VisibleForTesting
boolean isActiveAdminWithPolicyForUserLocked(ActiveAdmin admin, int reqPolicy,
int userId) {
- boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
- boolean ownsProfile = (getProfileOwner(userId) != null
- && getProfileOwner(userId).getPackageName()
- .equals(admin.info.getPackageName()));
+ final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId);
+ final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId);
if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || (ownsDevice && ownsProfile)) {
- return true;
- }
+ return ownsDevice;
} else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || ownsProfile) {
- return true;
- }
+ // DO always has the PO power.
+ return ownsDevice || ownsProfile;
} else {
- if (admin.info.usesPolicy(reqPolicy)) {
- return true;
- }
+ return admin.info.usesPolicy(reqPolicy);
}
- return false;
}
void sendAdminCommandLocked(ActiveAdmin admin, String action) {
@@ -2050,23 +2029,36 @@
validatePasswordOwnerLocked(policy);
updateMaximumTimeToLockLocked(policy);
- updateLockTaskPackages(policy.mLockTaskPackages, userHandle);
+ updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
if (policy.mStatusBarDisabled) {
setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
}
}
- private void updateLockTaskPackages(List<String> packages, final int userId) {
- final String[] copy = packages.toArray(new String[packages.size()]);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- getIActivityManager().updateLockTaskPackages(userId, copy);
- } catch (RemoteException willNotHappen) {
- }
+ private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ mInjector.getIActivityManager()
+ .updateLockTaskPackages(userId, packages.toArray(new String[packages.size()]));
+ } catch (RemoteException e) {
+ // Not gonna happen.
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+
+ private void updateDeviceOwnerLocked() {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ if (getDeviceOwner() != null) {
+ mInjector.getIActivityManager()
+ .updateDeviceOwner(getDeviceOwner().getPackageName());
}
- });
+ } catch (RemoteException e) {
+ // Not gonna happen.
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
static void validateQualityConstant(int quality) {
@@ -2137,21 +2129,14 @@
}
private void ensureDeviceOwnerUserStarted() {
- if (!mOwners.hasDeviceOwner()) {
- return;
- }
- final int userId = mOwners.getDeviceOwnerUserId();
- if (userId == UserHandle.USER_SYSTEM) {
- return;
- }
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Starting non-system DO user: " + userId);
- }
- mHandler.post(new Runnable() {
- @Override
- public void run() {
+ if (mOwners.hasDeviceOwner()) {
+ final int userId = mOwners.getDeviceOwnerUserId();
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Starting non-system DO user: " + userId);
+ }
+ if (userId != UserHandle.USER_SYSTEM) {
try {
- getIActivityManager().startUserInBackground(userId);
+ mInjector.getIActivityManager().startUserInBackground(userId);
// STOPSHIP Prevent the DO user from being killed.
@@ -2159,7 +2144,7 @@
Slog.w(LOG_TAG, "Exception starting user", e);
}
}
- });
+ }
}
private void cleanUpOldUsers() {
@@ -2425,19 +2410,15 @@
}
enforceCrossUserPermission(userHandle);
synchronized (this) {
- return packageHasActiveAdminsLocked(packageName, userHandle);
- }
- }
-
- boolean packageHasActiveAdminsLocked(String packageName, int userHandle) {
- DevicePolicyData policy = getUserData(userHandle);
- final int N = policy.mAdminList.size();
- for (int i = 0; i < N; i++) {
- if (policy.mAdminList.get(i).info.getPackageName().equals(packageName)) {
- return true;
+ DevicePolicyData policy = getUserData(userHandle);
+ final int N = policy.mAdminList.size();
+ for (int i=0; i<N; i++) {
+ if (policy.mAdminList.get(i).info.getPackageName().equals(packageName)) {
+ return true;
+ }
}
+ return false;
}
- return false;
}
@Override
@@ -2452,8 +2433,9 @@
return;
}
if (admin.getUid() != mInjector.binderGetCallingUid()) {
- // Active device owners must remain active admins.
- if (isDeviceOwner(adminReceiver)) {
+ // Active device/profile owners must remain active admins.
+ if (isDeviceOwner(adminReceiver, userHandle)
+ || isProfileOwner(adminReceiver, userHandle)) {
return;
}
mContext.enforceCallingOrSelfPermission(
@@ -3198,12 +3180,21 @@
}
@Override
- public boolean resetPassword(String passwordOrNull, int flags) {
+ public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
if (!mHasFeature) {
return false;
}
final int userHandle = UserHandle.getCallingUserId();
- enforceNotManagedProfile(userHandle, "reset the password");
+
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ if (mUserManager.getCredentialOwnerProfile(userHandle) != userHandle) {
+ throw new SecurityException("You can not change password for this profile because"
+ + " it shares the password with the owner profile");
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
String password = passwordOrNull != null ? passwordOrNull : "";
@@ -3211,8 +3202,35 @@
synchronized (this) {
// This api can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(null,
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(null,
DeviceAdminInfo.USES_POLICY_RESET_PASSWORD);
+ final ComponentName adminComponent = admin.info.getComponent();
+
+ // As of N, only profile owners and device owners can reset the password.
+ if (!(isProfileOwner(adminComponent, userHandle)
+ || isDeviceOwner(adminComponent, userHandle))) {
+ final boolean preN = getTargetSdk(admin.info.getPackageName(), userHandle)
+ < android.os.Build.VERSION_CODES.N;
+ // As of N, password resetting to empty/null is not allowed anymore.
+ // TODO Should we allow DO/PO to set an empty password?
+ if (TextUtils.isEmpty(password)) {
+ if (!preN) {
+ throw new SecurityException("Cannot call with null password");
+ } else {
+ Slog.e(LOG_TAG, "Cannot call with null password");
+ return false;
+ }
+ }
+ // As of N, password cannot be changed by the admin if it is already set.
+ if (isLockScreenSecureUnchecked(userHandle)) {
+ if (!preN) {
+ throw new SecurityException("Admin cannot change current password");
+ } else {
+ Slog.e(LOG_TAG, "Admin cannot change current password");
+ return false;
+ }
+ }
+ }
quality = getPasswordQuality(null, userHandle);
if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
int realQuality = LockPatternUtils.computePasswordQuality(password);
@@ -3314,9 +3332,9 @@
// Don't do this with the lock held, because it is going to call
// back in to the service.
- long ident = mInjector.binderClearCallingIdentity();
+ ident = mInjector.binderClearCallingIdentity();
try {
- LockPatternUtils utils = new LockPatternUtils(mContext);
+ LockPatternUtils utils = mInjector.newLockPatternUtils();
if (!TextUtils.isEmpty(password)) {
utils.saveLockPassword(password, null, quality, userHandle);
} else {
@@ -3341,6 +3359,15 @@
return true;
}
+ private boolean isLockScreenSecureUnchecked(int userId) {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ return mInjector.newLockPatternUtils().isSecure(userId);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+
private void setDoNotAskCredentialsOnBoot() {
synchronized (this) {
DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
@@ -3696,10 +3723,11 @@
}
@Override
- public void wipeData(int flags, final int userHandle) {
+ public void wipeData(int flags) {
if (!mHasFeature) {
return;
}
+ final int userHandle = mInjector.userHandleGetCallingUserId();
enforceCrossUserPermission(userHandle);
synchronized (this) {
// This API can only be called by an active device admin,
@@ -3712,8 +3740,7 @@
long ident = mInjector.binderClearCallingIdentity();
try {
if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- if (userHandle != UserHandle.USER_SYSTEM
- || !isDeviceOwner(admin.info.getComponent())) {
+ if (!isDeviceOwner(admin.info.getComponent(), userHandle)) {
throw new SecurityException(
"Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
}
@@ -3740,7 +3767,7 @@
@Override
public void run() {
try {
- IActivityManager am = getIActivityManager();
+ IActivityManager am = mInjector.getIActivityManager();
if (am.getCurrentUser().id == userHandle) {
am.switchUser(UserHandle.USER_SYSTEM);
}
@@ -4336,7 +4363,7 @@
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- final int userHandle = UserHandle.getCallingUserId();
+ final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
@@ -4348,7 +4375,7 @@
// Tell the user manager that the restrictions have changed.
synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
synchronized (this) {
- if (isDeviceOwner(who)) {
+ if (isDeviceOwner(who, userHandle)) {
mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
} else {
mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
@@ -4496,6 +4523,7 @@
mOwners.setDeviceOwner(admin, ownerName, userId);
mOwners.writeDeviceOwner();
+ updateDeviceOwnerLocked();
Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
ident = mInjector.binderClearCallingIdentity();
@@ -4509,24 +4537,17 @@
}
}
- public boolean isDeviceOwner(ComponentName who) {
- if (!mHasFeature) {
- return false;
- }
+ public boolean isDeviceOwner(ComponentName who, int userId) {
synchronized (this) {
- return mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerComponent().equals(who);
+ return mOwners.hasDeviceOwner()
+ && mOwners.getDeviceOwnerUserId() == userId
+ && mOwners.getDeviceOwnerComponent().equals(who);
}
}
- @Override
- public boolean isDeviceOwnerPackage(String packageName) {
- if (!mHasFeature) {
- return false;
- }
- synchronized (this) {
- return mOwners.hasDeviceOwner()
- && mOwners.getDeviceOwnerComponent().getPackageName().equals(packageName);
- }
+ public boolean isProfileOwner(ComponentName who, int userId) {
+ final ComponentName profileOwner = getProfileOwner(userId);
+ return who != null && who.equals(profileOwner);
}
@Override
@@ -4597,6 +4618,7 @@
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
+ updateDeviceOwnerLocked();
// Reactivate backup service.
long ident = mInjector.binderClearCallingIdentity();
try {
@@ -4645,6 +4667,30 @@
}
}
+ @Override
+ public boolean setDeviceOwnerLockScreenInfo(ComponentName who, String info) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ if (!mHasFeature) {
+ return false;
+ }
+
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ long token = mInjector.binderClearCallingIdentity();
+ try {
+ new LockPatternUtils(mContext).setDeviceOwnerInfo(info);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(token);
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public String getDeviceOwnerLockScreenInfo() {
+ return new LockPatternUtils(mContext).getDeviceOwnerInfo();
+ }
+
private void clearUserPoliciesLocked(UserHandle userHandle) {
int userId = userHandle.getIdentifier();
// Reset some of the user-specific policies
@@ -5357,14 +5403,14 @@
}
}
- private boolean checkCallerIsCurrentUserOrProfileAM() {
+ private boolean checkCallerIsCurrentUserOrProfile() {
int callingUserId = UserHandle.getCallingUserId();
long token = mInjector.binderClearCallingIdentity();
try {
UserInfo currentUser;
UserInfo callingUser = mUserManager.getUserInfo(callingUserId);
try {
- currentUser = getIActivityManager().getCurrentUser();
+ currentUser = mInjector.getIActivityManager().getCurrentUser();
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Failed to talk to activity managed.", e);
return false;
@@ -5395,7 +5441,7 @@
// TODO When InputMethodManager supports per user calls remove
// this restriction.
- if (!checkCallerIsCurrentUserOrProfileAM()) {
+ if (!checkCallerIsCurrentUserOrProfile()) {
return false;
}
@@ -5447,7 +5493,7 @@
public List getPermittedInputMethodsForCurrentUser() {
UserInfo currentUser;
try {
- currentUser = getIActivityManager().getCurrentUser();
+ currentUser = mInjector.getIActivityManager().getCurrentUser();
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Failed to make remote calls to get current user", e);
// Activity managed is dead, just allow all IMEs
@@ -5542,7 +5588,7 @@
}
// Start user in background.
- getIActivityManager().startUserInBackground(userHandle);
+ mInjector.getIActivityManager().startUserInBackground(userHandle);
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Failed to make remote calls for configureUser", e);
}
@@ -5575,20 +5621,20 @@
Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
- }
- long id = mInjector.binderClearCallingIdentity();
- try {
- int userId = UserHandle.USER_SYSTEM;
- if (userHandle != null) {
- userId = userHandle.getIdentifier();
+ long id = mInjector.binderClearCallingIdentity();
+ try {
+ int userId = UserHandle.USER_SYSTEM;
+ if (userHandle != null) {
+ userId = userHandle.getIdentifier();
+ }
+ return mInjector.getIActivityManager().switchUser(userId);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Couldn't switch user", e);
+ return false;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
}
- return getIActivityManager().switchUser(userId);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Couldn't switch user", e);
- return false;
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
}
}
@@ -5622,9 +5668,8 @@
ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- final boolean isDeviceOwner = isDeviceOwner(who);
- if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
- && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
+ final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+ if (!isDeviceOwner && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
throw new SecurityException(
"Profile owners cannot set user restriction " + key);
}
@@ -6060,7 +6105,7 @@
// Store the settings persistently.
saveSettingsLocked(userHandle);
- updateLockTaskPackages(packages, userHandle);
+ updateLockTaskPackagesLocked(packages, userHandle);
}
/**
@@ -6117,9 +6162,8 @@
Bundle adminExtras = new Bundle();
adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
for (ActiveAdmin admin : policy.mAdminList) {
- boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
- boolean ownsProfile = (getProfileOwner(userHandle) != null
- && getProfileOwner(userHandle).equals(admin.info.getPackageName()));
+ final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userHandle);
+ final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userHandle);
if (ownsDevice || ownsProfile) {
if (isEnabled) {
sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_ENTERING,
@@ -6171,13 +6215,12 @@
@Override
public void setSecureSetting(ComponentName who, String setting, String value) {
Preconditions.checkNotNull(who, "ComponentName is null");
- int callingUserId = UserHandle.getCallingUserId();
- final ContentResolver contentResolver = mContext.getContentResolver();
+ int callingUserId = mInjector.userHandleGetCallingUserId();
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (isDeviceOwner(who)) {
+ if (isDeviceOwner(who, mInjector.userHandleGetCallingUserId())) {
if (!SECURE_SETTINGS_DEVICEOWNER_WHITELIST.contains(setting)) {
throw new SecurityException(String.format(
"Permission denial: Device owners cannot update %1$s", setting));
@@ -6428,16 +6471,6 @@
}
}
- @Override
- public boolean isDeviceAdminPackage(int userId, String packageName) {
- if (packageName == null) {
- return false;
- }
- synchronized (DevicePolicyManagerService.this) {
- return packageHasActiveAdminsLocked(packageName, userId);
- }
- }
-
private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
synchronized (DevicePolicyManagerService.this) {
@@ -6499,13 +6532,26 @@
* @param callerUid UID of the caller.
* @return true if the caller is the device owner app
*/
- private boolean isCallerDeviceOwner(int callerUid) {
- String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
- for (String pkg : pkgs) {
- if (isDeviceOwnerPackage(pkg)) {
- return true;
+ @VisibleForTesting
+ boolean isCallerDeviceOwner(int callerUid) {
+ synchronized (this) {
+ if (!mOwners.hasDeviceOwner()) {
+ return false;
+ }
+ if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) {
+ return false;
+ }
+ final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent()
+ .getPackageName();
+ final String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
+
+ for (String pkg : pkgs) {
+ if (deviceOwnerPackageName.equals(pkg)) {
+ return true;
+ }
}
}
+
return false;
}
@@ -6585,10 +6631,8 @@
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long ident = mInjector.binderClearCallingIdentity();
try {
- final ApplicationInfo ai = mIPackageManager
- .getApplicationInfo(packageName, 0, user.getIdentifier());
- final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
- if (targetSdkVersion < android.os.Build.VERSION_CODES.M) {
+ if (getTargetSdk(packageName, user.getIdentifier())
+ < android.os.Build.VERSION_CODES.M) {
return false;
}
final PackageManager packageManager = mContext.getPackageManager();
@@ -6684,23 +6728,48 @@
}
return true;
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE.equals(action)) {
- if (getProfileOwner(callingUserId) != null) {
- return false;
- }
- if (mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
- return false;
- }
- if (callingUserId != UserHandle.USER_SYSTEM) {
- // Device owner provisioning can only be initiated from system user.
- return false;
- }
- return true;
+ return isDeviceOwnerProvisioningAllowed(callingUserId);
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_USER.equals(action)) {
+ if (!UserManager.isSplitSystemUser()) {
+ // ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
+ return false;
+ }
if (hasUserSetupCompleted(callingUserId)) {
return false;
}
return true;
+ } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE.equals(action)) {
+ if (!UserManager.isSplitSystemUser()) {
+ // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
+ return false;
+ }
+ return isDeviceOwnerProvisioningAllowed(callingUserId);
}
throw new IllegalArgumentException("Unknown provisioning action " + action);
}
+
+ private boolean isDeviceOwnerProvisioningAllowed(int callingUserId) {
+ if (getProfileOwner(callingUserId) != null) {
+ return false;
+ }
+ if (mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+ return false;
+ }
+ if (callingUserId != UserHandle.USER_SYSTEM) {
+ // Device owner provisioning can only be initiated from system user.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns the target sdk version number that the given packageName was built for
+ * in the given user.
+ */
+ private int getTargetSdk(String packageName, int userId) throws RemoteException {
+ final ApplicationInfo ai = mIPackageManager
+ .getApplicationInfo(packageName, 0, userId);
+ final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
+ return targetSdkVersion;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 6419338..2c01b8a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -128,7 +128,7 @@
}
@Override
- IActivityManager getIActivityManagerInner() {
+ IActivityManager getIActivityManager() {
return context.iactivityManager;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ca3d950..36980e3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -476,6 +476,10 @@
// Fire!
assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
+ // Verify internal calls.
+ verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+ eq(admin1.getPackageName()));
+
// TODO We should check if the caller has called clearCallerIdentity().
verify(mContext.ibackupManager, times(1)).setBackupServiceActive(
eq(UserHandle.USER_SYSTEM), eq(false));
@@ -542,6 +546,10 @@
dpm.setActiveAdmin(admin1, /* replace =*/ false);
assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
+ // Verify internal calls.
+ verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+ eq(admin1.getPackageName()));
+
assertEquals(admin1.getPackageName(), dpm.getDeviceOwner());
// Set up other mocks.
@@ -576,6 +584,10 @@
dpm.setActiveAdmin(admin1, /* replace =*/ false);
assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
+ // Verify internal calls.
+ verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+ eq(admin1.getPackageName()));
+
assertEquals(admin1.getPackageName(), dpm.getDeviceOwner());
// Now call clear from the secondary user, which should throw.
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 5db1bde..723e827 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -23,6 +23,7 @@
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.MockView;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
@@ -126,6 +127,9 @@
if (view == null) {
view = loadCustomView(name, attrs);
}
+ } catch (InflateException e) {
+ // Don't catch the InflateException below as that results in hiding the real cause.
+ throw e;
} catch (Exception e) {
// Wrap the real exception in a ClassNotFoundException, so that the calling method
// can deal with it.
@@ -154,23 +158,30 @@
}
ta.recycle();
}
- final Object lastContext = mConstructorArgs[0];
- mConstructorArgs[0] = context;
- // try to load the class from using the custom view loader
- try {
- view = loadCustomView(name, attrs);
- } catch (Exception e2) {
- // Wrap the real exception in an InflateException so that the calling
- // method can deal with it.
- InflateException exception = new InflateException();
- if (!e2.getClass().equals(ClassNotFoundException.class)) {
- exception.initCause(e2);
- } else {
- exception.initCause(e);
+ if (!(e.getCause() instanceof ClassNotFoundException)) {
+ // There is some unknown inflation exception in inflating a View that was found.
+ view = new MockView(context, attrs);
+ ((MockView) view).setText(name);
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, e.getMessage(), e, null);
+ } else {
+ final Object lastContext = mConstructorArgs[0];
+ mConstructorArgs[0] = context;
+ // try to load the class from using the custom view loader
+ try {
+ view = loadCustomView(name, attrs);
+ } catch (Exception e2) {
+ // Wrap the real exception in an InflateException so that the calling
+ // method can deal with it.
+ InflateException exception = new InflateException();
+ if (!e2.getClass().equals(ClassNotFoundException.class)) {
+ exception.initCause(e2);
+ } else {
+ exception.initCause(e);
+ }
+ throw exception;
+ } finally {
+ mConstructorArgs[0] = lastContext;
}
- throw exception;
- } finally {
- mConstructorArgs[0] = lastContext;
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index 44a9aad..d392f21 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -17,39 +17,90 @@
package com.android.layoutlib.bridge;
import android.content.Context;
-import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.TextView;
/**
* Base class for mocked views.
- *
- * TODO: implement onDraw and draw a rectangle in a random color with the name of the class
- * (or better the id of the view).
+ * <p/>
+ * FrameLayout with a single TextView. Doesn't allow adding any other views to itself.
*/
-public class MockView extends TextView {
+public class MockView extends FrameLayout {
+
+ private final TextView mView;
+
+ public MockView(Context context) {
+ this(context, null);
+ }
public MockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public MockView(Context context, AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, 0);
+ public MockView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public MockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
-
- setText(this.getClass().getSimpleName());
- setTextColor(0xFF000000);
+ mView = new TextView(context, attrs);
+ mView.setTextColor(0xFF000000);
setGravity(Gravity.CENTER);
+ setText(getClass().getSimpleName());
+ addView(mView);
+ setBackgroundColor(0xFF7F7F7F);
+ }
+
+ // Only allow adding one TextView.
+ @Override
+ public void addView(View child) {
+ if (child == mView) {
+ super.addView(child);
+ }
}
@Override
- public void onDraw(Canvas canvas) {
- canvas.drawARGB(0xFF, 0x7F, 0x7F, 0x7F);
+ public void addView(View child, int index) {
+ if (child == mView) {
+ super.addView(child, index);
+ }
+ }
- super.onDraw(canvas);
+ @Override
+ public void addView(View child, int width, int height) {
+ if (child == mView) {
+ super.addView(child, width, height);
+ }
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ if (child == mView) {
+ super.addView(child, params);
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (child == mView) {
+ super.addView(child, index, params);
+ }
+ }
+
+ // The following methods are called by the IDE via reflection, and should be considered part
+ // of the API.
+ // Historically, MockView used to be a textView and had these methods. Now, we simply delegate
+ // them to the contained textView.
+
+ public void setText(CharSequence text) {
+ mView.setText(text);
+ }
+
+ public void setGravity(int gravity) {
+ mView.setGravity(gravity);
}
}