diff options
257 files changed, 6163 insertions, 2662 deletions
diff --git a/Android.mk b/Android.mk index 505b12d0c865..d48887d17e3b 100644 --- a/Android.mk +++ b/Android.mk @@ -348,6 +348,7 @@ LOCAL_SRC_FILES += \ media/java/android/media/IRingtonePlayer.aidl \ media/java/android/media/IVolumeController.aidl \ media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \ + media/java/android/media/midi/IBluetoothMidiService.aidl \ media/java/android/media/midi/IMidiDeviceListener.aidl \ media/java/android/media/midi/IMidiDeviceOpenCallback.aidl \ media/java/android/media/midi/IMidiDeviceServer.aidl \ diff --git a/api/current.txt b/api/current.txt index 991756a80654..d82927242b10 100644 --- a/api/current.txt +++ b/api/current.txt @@ -35370,6 +35370,7 @@ package android.view { field public static final int KEYCODE_SLEEP = 223; // 0xdf field public static final int KEYCODE_SOFT_LEFT = 1; // 0x1 field public static final int KEYCODE_SOFT_RIGHT = 2; // 0x2 + field public static final int KEYCODE_SOFT_SLEEP = 276; // 0x114 field public static final int KEYCODE_SPACE = 62; // 0x3e field public static final int KEYCODE_STAR = 17; // 0x11 field public static final int KEYCODE_STB_INPUT = 180; // 0xb4 diff --git a/api/system-current.txt b/api/system-current.txt index b8e560063626..590505691085 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -37664,6 +37664,7 @@ package android.view { field public static final int KEYCODE_SLEEP = 223; // 0xdf field public static final int KEYCODE_SOFT_LEFT = 1; // 0x1 field public static final int KEYCODE_SOFT_RIGHT = 2; // 0x2 + field public static final int KEYCODE_SOFT_SLEEP = 276; // 0x114 field public static final int KEYCODE_SPACE = 62; // 0x3e field public static final int KEYCODE_STAR = 17; // 0x11 field public static final int KEYCODE_STB_INPUT = 180; // 0xb4 diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java index 471fa3bd046e..214dc5d35a24 100644 --- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java +++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java @@ -51,7 +51,8 @@ public final class Dpm extends BaseCommand { out.println( "usage: dpm [subcommand] [options]\n" + "usage: dpm set-active-admin [ --user <USER_ID> ] <COMPONENT>\n" + - "usage: dpm set-device-owner <COMPONENT>\n" + + // STOPSHIP Finalize it + "usage: dpm set-device-owner [ --user <USER_ID> *EXPERIMENTAL* ] <COMPONENT>\n" + "usage: dpm set-profile-owner [ --user <USER_ID> ] <COMPONENT>\n" + "\n" + "dpm set-active-admin: Sets the given component as active admin" + @@ -106,22 +107,22 @@ public final class Dpm extends BaseCommand { } private void runSetDeviceOwner() throws RemoteException { - ComponentName component = parseComponentName(nextArgRequired()); - mDevicePolicyManager.setActiveAdmin(component, true /*refreshing*/, UserHandle.USER_SYSTEM); + parseArgs(true); + mDevicePolicyManager.setActiveAdmin(mComponent, true /*refreshing*/, mUserId); - String packageName = component.getPackageName(); + String packageName = mComponent.getPackageName(); try { - if (!mDevicePolicyManager.setDeviceOwner(packageName, null /*ownerName*/)) { + if (!mDevicePolicyManager.setDeviceOwner(packageName, null /*ownerName*/, mUserId)) { throw new RuntimeException( "Can't set package " + packageName + " as device owner."); } } catch (Exception e) { // Need to remove the admin that we just added. - mDevicePolicyManager.removeActiveAdmin(component, UserHandle.USER_SYSTEM); + mDevicePolicyManager.removeActiveAdmin(mComponent, UserHandle.USER_SYSTEM); throw e; } System.out.println("Success: Device owner set to package " + packageName); - System.out.println("Active admin set to component " + component.toShortString()); + System.out.println("Active admin set to component " + mComponent.toShortString()); } private void runSetProfileOwner() throws RemoteException { diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index a475041f2ebe..d751f96f65f6 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -426,7 +426,7 @@ public class AccountManager { @RequiresPermission(GET_ACCOUNTS) public Account[] getAccounts() { try { - return mService.getAccounts(null); + return mService.getAccounts(null, mContext.getOpPackageName()); } catch (RemoteException e) { // won't ever happen throw new RuntimeException(e); @@ -451,7 +451,7 @@ public class AccountManager { @RequiresPermission(GET_ACCOUNTS) public Account[] getAccountsAsUser(int userId) { try { - return mService.getAccountsAsUser(null, userId); + return mService.getAccountsAsUser(null, userId, mContext.getOpPackageName()); } catch (RemoteException e) { // won't ever happen throw new RuntimeException(e); @@ -468,7 +468,7 @@ public class AccountManager { */ public Account[] getAccountsForPackage(String packageName, int uid) { try { - return mService.getAccountsForPackage(packageName, uid); + return mService.getAccountsForPackage(packageName, uid, mContext.getOpPackageName()); } catch (RemoteException re) { // won't ever happen throw new RuntimeException(re); @@ -485,7 +485,8 @@ public class AccountManager { */ public Account[] getAccountsByTypeForPackage(String type, String packageName) { try { - return mService.getAccountsByTypeForPackage(type, packageName); + return mService.getAccountsByTypeForPackage(type, packageName, + mContext.getOpPackageName()); } catch (RemoteException re) { // won't ever happen throw new RuntimeException(re); @@ -522,7 +523,8 @@ public class AccountManager { /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */ public Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) { try { - return mService.getAccountsAsUser(type, userHandle.getIdentifier()); + return mService.getAccountsAsUser(type, userHandle.getIdentifier(), + mContext.getOpPackageName()); } catch (RemoteException e) { // won't ever happen throw new RuntimeException(e); @@ -610,7 +612,7 @@ public class AccountManager { if (features == null) throw new IllegalArgumentException("features is null"); return new Future2Task<Boolean>(handler, callback) { public void doWork() throws RemoteException { - mService.hasFeatures(mResponse, account, features); + mService.hasFeatures(mResponse, account, features, mContext.getOpPackageName()); } public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { @@ -662,7 +664,8 @@ public class AccountManager { if (type == null) throw new IllegalArgumentException("type is null"); return new Future2Task<Account[]>(handler, callback) { public void doWork() throws RemoteException { - mService.getAccountsByFeatures(mResponse, type, features); + mService.getAccountsByFeatures(mResponse, type, features, + mContext.getOpPackageName()); } public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_ACCOUNTS)) { diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 04b3c8864375..4378df408d10 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -30,12 +30,14 @@ interface IAccountManager { String getPassword(in Account account); String getUserData(in Account account, String key); AuthenticatorDescription[] getAuthenticatorTypes(int userId); - Account[] getAccounts(String accountType); - Account[] getAccountsForPackage(String packageName, int uid); - Account[] getAccountsByTypeForPackage(String type, String packageName); - Account[] getAccountsAsUser(String accountType, int userId); - void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features); - void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features); + Account[] getAccounts(String accountType, String opPackageName); + Account[] getAccountsForPackage(String packageName, int uid, String opPackageName); + Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName); + Account[] getAccountsAsUser(String accountType, int userId, String opPackageName); + void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features, + String opPackageName); + void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, + in String[] features, String opPackageName); boolean addAccountExplicitly(in Account account, String password, in Bundle extras); void removeAccount(in IAccountManagerResponse response, in Account account, boolean expectActivityLaunch); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 73c378617419..f7dcf02da0a5 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2736,40 +2736,6 @@ public class Activity extends ContextThemeWrapper } /** - * Returns the bounds of the task that contains this activity. - * - * @return Rect The bounds that contains the activity. - * @hide - */ - @Override - public Rect getActivityBounds() throws RemoteException { - return ActivityManagerNative.getDefault().getActivityBounds(mToken); - } - - /** - * Sets the bounds (size and position) of the task or stack that contains this - * activity. - * NOTE: The requested bounds might not the fully honored by the system depending - * on the window placement policy. - * - * @param newBounds The new target bounds of the activity in task or stack. - * @hide - */ - @Override - public void setActivityBounds(Rect newBounds) throws RemoteException { - ActivityManagerNative.getDefault().setActivityBounds(mToken, newBounds); - } - - /** - * Activates this activity, hence bringing it to the top and giving it focus. - * Note: This will only work for activities which are located on the freeform desktop. - * @hide - */ - public void activateActivity() throws RemoteException { - ActivityManagerNative.getDefault().activateActivity(mToken); - } - - /** * Called to process key events. You can override this to intercept all * key events before they are dispatched to the window. Be sure to call * this implementation for key events that should be handled normally. @@ -3839,6 +3805,12 @@ public class Activity extends ContextThemeWrapper * #checkSelfPermission(String)}. * </p> * <p> + * Calling this API for permissions already granted to your app would show UI + * to the user to decide whether the app can still hold these permissions. This + * can be useful if the way your app uses data guarded by the permissions + * changes significantly. + * </p> + * <p> * You cannot request a permission if your activity sets {@link * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to * <code>true</code> because in this case the activity would not receive diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 1f1f356bc5f7..d1c73bc945ff 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -752,24 +752,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case GET_ACTIVITY_BOUNDS_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); - IBinder token = data.readStrongBinder(); - Rect r = getActivityBounds(token); - reply.writeNoException(); - r.writeToParcel(reply, 0); - return true; - } - - case SET_ACTIVITY_BOUNDS_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); - IBinder token = data.readStrongBinder(); - Rect r = Rect.CREATOR.createFromParcel(data); - setActivityBounds(token, r); - reply.writeNoException(); - return true; - } - case POSITION_TASK_IN_STACK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int taskId = data.readInt(); @@ -827,14 +809,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case ACTIVATE_ACTIVITY_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); - IBinder token = data.readStrongBinder(); - activateActivity(token); - reply.writeNoException(); - return true; - } - case SET_FOCUSED_TASK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int taskId = data.readInt(); @@ -3631,18 +3605,6 @@ class ActivityManagerProxy implements IActivityManager return focusedStackId; } @Override - public void activateActivity(IBinder token) throws RemoteException - { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - data.writeStrongBinder(token); - mRemote.transact(ACTIVATE_ACTIVITY_TRANSACTION, data, reply, 0); - reply.readException(); - data.recycle(); - reply.recycle(); - } - @Override public void setFocusedTask(int taskId) throws RemoteException { Parcel data = Parcel.obtain(); @@ -5941,35 +5903,6 @@ class ActivityManagerProxy implements IActivityManager } @Override - public void setActivityBounds(IBinder token, Rect r) throws RemoteException - { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - data.writeStrongBinder(token); - r.writeToParcel(data, 0); - mRemote.transact(SET_ACTIVITY_BOUNDS_TRANSACTION, data, reply, 0); - reply.readException(); - data.recycle(); - reply.recycle(); - } - - @Override - public Rect getActivityBounds(IBinder token) throws RemoteException - { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - data.writeStrongBinder(token); - mRemote.transact(GET_ACTIVITY_BOUNDS_TRANSACTION, data, reply, 0); - reply.readException(); - Rect rect = Rect.CREATOR.createFromParcel(reply); - data.recycle(); - reply.recycle(); - return rect; - } - - @Override public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 42ac67c7ae44..09c0a6e3ae46 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -235,8 +235,10 @@ public class AppOpsManager { public static final int OP_WRITE_EXTERNAL_STORAGE = 60; /** @hide Turned on the screen. */ public static final int OP_TURN_SCREEN_ON = 61; + /** @hide Get device accounts. */ + public static final int OP_GET_ACCOUNTS = 62; /** @hide */ - public static final int _NUM_OP = 62; + public static final int _NUM_OP = 63; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -331,6 +333,9 @@ public class AppOpsManager { /** Required to write/modify/update system settingss. */ public static final String OPSTR_WRITE_SETTINGS = "android:write_settings"; + /** @hide Get device accounts. */ + public static final String OPSTR_GET_ACCOUNTS + = "android:get_accounts"; /** * This maps each operation to the operation that serves as the @@ -403,6 +408,7 @@ public class AppOpsManager { OP_READ_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE, OP_TURN_SCREEN_ON, + OP_GET_ACCOUNTS, }; /** @@ -472,6 +478,7 @@ public class AppOpsManager { OPSTR_READ_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE, null, + OPSTR_GET_ACCOUNTS }; /** @@ -541,6 +548,7 @@ public class AppOpsManager { "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE", "TURN_ON_SCREEN", + "GET_ACCOUNTS", }; /** @@ -610,6 +618,7 @@ public class AppOpsManager { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, null, // no permission for turning the screen on + Manifest.permission.GET_ACCOUNTS }; /** @@ -680,6 +689,7 @@ public class AppOpsManager { null, // READ_EXTERNAL_STORAGE null, // WRITE_EXTERNAL_STORAGE null, // TURN_ON_SCREEN + null, // GET_ACCOUNTS }; /** @@ -749,6 +759,7 @@ public class AppOpsManager { false, // READ_EXTERNAL_STORAGE false, // WRITE_EXTERNAL_STORAGE false, // TURN_ON_SCREEN + false, // GET_ACCOUNTS }; /** @@ -817,6 +828,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, // OP_TURN_ON_SCREEN + AppOpsManager.MODE_ALLOWED, }; /** @@ -889,6 +901,7 @@ public class AppOpsManager { false, false, false, + false }; /** diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 82206ea998ea..f70423fbbc9a 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1166,6 +1166,12 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * android.content.Context#checkSelfPermission(String)}. * </p> * <p> + * Calling this API for permissions already granted to your app would show UI + * to the user to decide whether the app can still hold these permissions. This + * can be useful if the way your app uses data guarded by the permissions + * changes significantly. + * </p> + * <p> * You cannot request a permission if your activity sets {@link * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to * <code>true</code> because in this case the activity would not receive diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 47abf261cfdd..66fa79639256 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -147,7 +147,6 @@ public interface IActivityManager extends IInterface { public boolean isInHomeStack(int taskId) throws RemoteException; public void setFocusedStack(int stackId) throws RemoteException; public int getFocusedStackId() throws RemoteException; - public void activateActivity(IBinder token) throws RemoteException; public void setFocusedTask(int taskId) throws RemoteException; public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException; public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; @@ -491,8 +490,6 @@ public interface IActivityManager extends IInterface { throws RemoteException; public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException; public void resizeTask(int taskId, Rect bounds) throws RemoteException; - public void setActivityBounds(IBinder token, Rect bounds) throws RemoteException; - public Rect getActivityBounds(IBinder token) throws RemoteException; public Rect getTaskBounds(int taskId) throws RemoteException; public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException; @@ -891,7 +888,4 @@ public interface IActivityManager extends IInterface { int GET_ACTIVITY_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 343; int MOVE_ACTIVITY_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 344; int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345; - int GET_ACTIVITY_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346; - int SET_ACTIVITY_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347; - int ACTIVATE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 93813279c0dc..55aec5906cac 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -953,6 +953,9 @@ public class Notification implements Parcelable private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs) { this.mIcon = icon; + if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { + this.icon = icon.getResId(); + } this.title = title; this.actionIntent = intent; this.mExtras = extras != null ? extras : new Bundle(); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 7ffac0a6dc39..ac50699ca110 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2684,13 +2684,26 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if the package name is null or invalid * @throws IllegalStateException If the preconditions mentioned are not met. */ - public boolean setDeviceOwner(String packageName) throws IllegalArgumentException, - IllegalStateException { + public boolean setDeviceOwner(String packageName) { return setDeviceOwner(packageName, null); } /** * @hide + */ + public boolean setDeviceOwner(String packageName, int userId) { + return setDeviceOwner(packageName, null, userId); + } + + /** + * @hide + */ + public boolean setDeviceOwner(String packageName, String ownerName) { + return setDeviceOwner(packageName, ownerName, UserHandle.USER_SYSTEM); + } + + /** + * @hide * Sets the given package as the device owner. The package must already be installed. There * must not already be a device owner. * Only apps with the MANAGE_PROFILE_AND_DEVICE_OWNERS permission and the shell uid can call @@ -2699,15 +2712,16 @@ public class DevicePolicyManager { * the caller is the shell uid, and there are no additional users and no accounts. * @param packageName the package name of the application to be registered as the device owner. * @param ownerName the human readable name of the institution that owns this device. + * @param userId ID of the user on which the device owner runs. * @return whether the package was successfully registered as the device owner. * @throws IllegalArgumentException if the package name is null or invalid * @throws IllegalStateException If the preconditions mentioned are not met. */ - public boolean setDeviceOwner(String packageName, String ownerName) + public boolean setDeviceOwner(String packageName, String ownerName, int userId) throws IllegalArgumentException, IllegalStateException { if (mService != null) { try { - return mService.setDeviceOwner(packageName, ownerName); + return mService.setDeviceOwner(packageName, ownerName, userId); } catch (RemoteException re) { Log.w(TAG, "Failed to set device owner"); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 376a3d82f7f1..55a21c61f53a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -113,7 +113,7 @@ interface IDevicePolicyManager { void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); - boolean setDeviceOwner(String packageName, String ownerName); + boolean setDeviceOwner(String packageName, String ownerName, int userId); boolean isDeviceOwner(String packageName); String getDeviceOwner(); String getDeviceOwnerName(); diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java index cc06b67295e7..f27fc2af9b14 100644 --- a/core/java/android/content/pm/ComponentInfo.java +++ b/core/java/android/content/pm/ComponentInfo.java @@ -18,6 +18,7 @@ package android.content.pm; import android.graphics.drawable.Drawable; import android.os.Parcel; +import android.os.Parcelable; import android.util.Printer; /** @@ -160,7 +161,12 @@ public class ComponentInfo extends PackageItemInfo { public void writeToParcel(Parcel dest, int parcelableFlags) { super.writeToParcel(dest, parcelableFlags); - applicationInfo.writeToParcel(dest, parcelableFlags); + if ((parcelableFlags & Parcelable.PARCELABLE_ELIDE_DUPLICATES) != 0) { + dest.writeInt(0); + } else { + dest.writeInt(1); + applicationInfo.writeToParcel(dest, parcelableFlags); + } dest.writeString(processName); dest.writeInt(descriptionRes); dest.writeInt(enabled ? 1 : 0); @@ -169,7 +175,10 @@ public class ComponentInfo extends PackageItemInfo { protected ComponentInfo(Parcel source) { super(source); - applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source); + final boolean hasApplicationInfo = (source.readInt() != 0); + if (hasApplicationInfo) { + applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source); + } processName = source.readString(); descriptionRes = source.readInt(); enabled = (source.readInt() != 0); diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 9e6c6b501f62..d40bab58ae77 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -305,10 +305,10 @@ public class PackageInfo implements Parcelable { dest.writeLong(firstInstallTime); dest.writeLong(lastUpdateTime); dest.writeIntArray(gids); - dest.writeTypedArray(activities, parcelableFlags); - dest.writeTypedArray(receivers, parcelableFlags); - dest.writeTypedArray(services, parcelableFlags); - dest.writeTypedArray(providers, parcelableFlags); + dest.writeTypedArray(activities, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + dest.writeTypedArray(receivers, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + dest.writeTypedArray(services, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + dest.writeTypedArray(providers, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); dest.writeTypedArray(instrumentation, parcelableFlags); dest.writeTypedArray(permissions, parcelableFlags); dest.writeStringArray(requestedPermissions); @@ -372,5 +372,22 @@ public class PackageInfo implements Parcelable { restrictedAccountType = source.readString(); requiredAccountType = source.readString(); overlayTarget = source.readString(); + + // The component lists were flattened with the redundant ApplicationInfo + // instances omitted. Distribute the canonical one here as appropriate. + if (applicationInfo != null) { + propagateApplicationInfo(applicationInfo, activities); + propagateApplicationInfo(applicationInfo, receivers); + propagateApplicationInfo(applicationInfo, services); + propagateApplicationInfo(applicationInfo, providers); + } + } + + private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) { + if (components != null) { + for (ComponentInfo ci : components) { + ci.applicationInfo = appInfo; + } + } } } diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index c248a9ee9b21..04c690b8334b 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -610,14 +610,28 @@ public final class AssetManager implements AutoCloseable { * {@hide} */ public final int addAssetPath(String path) { + return addAssetPathInternal(path, false); + } + + /** + * Add an application assets to the asset manager and loading it as shared library. + * This can be either a directory or ZIP file. Not for use by applications. Returns + * the cookie of the added asset, or 0 on failure. + * {@hide} + */ + public final int addAssetPathAsSharedLibrary(String path) { + return addAssetPathInternal(path, true); + } + + private final int addAssetPathInternal(String path, boolean appAsLib) { synchronized (this) { - int res = addAssetPathNative(path); + int res = addAssetPathNative(path, appAsLib); makeStringBlocks(mStringBlocks); return res; } } - private native final int addAssetPathNative(String path); + private native final int addAssetPathNative(String path, boolean appAsLib); /** * Add a set of assets to overlay an already added set of assets. diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 1f23c0a6ba3f..7fef5e17c5cb 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -27,6 +27,7 @@ import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.security.keystore.AndroidKeyStoreProvider; @@ -705,15 +706,23 @@ public class FingerprintManager { public void addLockoutResetCallback(final LockoutResetCallback callback) { if (mService != null) { try { + final PowerManager powerManager = mContext.getSystemService(PowerManager.class); mService.addLockoutResetCallback( new IFingerprintServiceLockoutResetCallback.Stub() { @Override public void onLockoutReset(long deviceId) throws RemoteException { + final PowerManager.WakeLock wakeLock = powerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback"); + wakeLock.acquire(); mHandler.post(new Runnable() { @Override public void run() { - callback.onLockoutReset(); + try { + callback.onLockoutReset(); + } finally { + wakeLock.release(); + } } }); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl index c9a5d59874f2..e027a2b3d260 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl @@ -23,6 +23,8 @@ import android.os.UserHandle; * Callback when lockout period expired and clients are allowed to authenticate again. * @hide */ -oneway interface IFingerprintServiceLockoutResetCallback { +interface IFingerprintServiceLockoutResetCallback { + + /** Method is synchronous so wakelock is held when this is called from a WAKEUP alarm. */ void onLockoutReset(long deviceId); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 405583649957..9a2a24128b92 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1243,6 +1243,8 @@ public class ConnectivityManager { /** The hardware does not support this request. */ public static final int ERROR_HARDWARE_UNSUPPORTED = -30; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; public static final int NATT_PORT = 4500; diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java index 5f46c73f645e..cab88b9972b5 100644 --- a/core/java/android/net/NetworkFactory.java +++ b/core/java/android/net/NetworkFactory.java @@ -169,7 +169,8 @@ public class NetworkFactory extends Handler { } } - private void handleAddRequest(NetworkRequest request, int score) { + @VisibleForTesting + protected void handleAddRequest(NetworkRequest request, int score) { NetworkRequestInfo n = mNetworkRequests.get(request.requestId); if (n == null) { if (DBG) log("got request " + request + " with score " + score); @@ -184,7 +185,8 @@ public class NetworkFactory extends Handler { evalRequest(n); } - private void handleRemoveRequest(NetworkRequest request) { + @VisibleForTesting + protected void handleRemoveRequest(NetworkRequest request) { NetworkRequestInfo n = mNetworkRequests.get(request.requestId); if (n != null) { mNetworkRequests.remove(request.requestId); diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java index 4407c9deb911..78a9401a2abd 100644 --- a/core/java/android/nfc/cardemulation/AidGroup.java +++ b/core/java/android/nfc/cardemulation/AidGroup.java @@ -1,3 +1,19 @@ +/* + * 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 android.nfc.cardemulation; import java.io.IOException; diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java index ad34e61db910..a2994790efb5 100644 --- a/core/java/android/nfc/cardemulation/HostApduService.java +++ b/core/java/android/nfc/cardemulation/HostApduService.java @@ -1,3 +1,19 @@ +/* + * 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 android.nfc.cardemulation; import android.annotation.SdkConstant; diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/core/java/android/nfc/cardemulation/OffHostApduService.java index 0d01762ae8e2..6a8aeee1ebee 100644 --- a/core/java/android/nfc/cardemulation/OffHostApduService.java +++ b/core/java/android/nfc/cardemulation/OffHostApduService.java @@ -1,3 +1,19 @@ +/* + * 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 android.nfc.cardemulation; import android.annotation.SdkConstant; diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java index 448b591718f8..13b5de9e3a0d 100644 --- a/core/java/android/os/Parcelable.java +++ b/core/java/android/os/Parcelable.java @@ -62,7 +62,17 @@ public interface Parcelable { * may want to release resources at this point. */ public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001; - + + /** + * Flag for use with {@link #writeToParcel}: a parent object will take + * care of managing duplicate state/data that is nominally replicated + * across its inner data members. This flag instructs the inner data + * types to omit that data during marshaling. Exact behavior may vary + * on a case by case basis. + * @hide + */ + public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002; + /** * Bit masks for use with {@link #describeContents}: each bit represents a * kind of object considered to have potential special significance when diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d1744b44282a..a038b0d30887 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5719,6 +5719,15 @@ public final class Settings { public static final String CAMERA_GESTURE_DISABLED = "camera_gesture_disabled"; /** + * Whether the camera launch gesture to double tap the power button when the screen is off + * should be disabled. + * + * @hide + */ + public static final String CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED = + "camera_double_tap_power_gesture_disabled"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index 409542dc5257..7cf1d715e947 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -31,7 +31,7 @@ import android.security.KeystoreArguments; */ interface IKeystoreService { int getState(int userId); - byte[] get(String name); + byte[] get(String name, int uid); int insert(String name, in byte[] item, int uid, int flags); int del(String name, int uid); int exist(String name, int uid); @@ -49,7 +49,7 @@ interface IKeystoreService { byte[] get_pubkey(String name); int grant(String name, int granteeUid); int ungrant(String name, int granteeUid); - long getmtime(String name); + long getmtime(String name, int uid); int duplicate(String srcKey, int srcUid, String destKey, int destUid); int is_hardware_backed(String string); int clear_uid(long uid); @@ -59,13 +59,13 @@ interface IKeystoreService { int generateKey(String alias, in KeymasterArguments arguments, in byte[] entropy, int uid, int flags, out KeyCharacteristics characteristics); int getKeyCharacteristics(String alias, in KeymasterBlob clientId, in KeymasterBlob appId, - out KeyCharacteristics characteristics); + int uid, out KeyCharacteristics characteristics); int importKey(String alias, in KeymasterArguments arguments, int format, in byte[] keyData, int uid, int flags, out KeyCharacteristics characteristics); ExportResult exportKey(String alias, int format, in KeymasterBlob clientId, - in KeymasterBlob appId); + in KeymasterBlob appId, int uid); OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable, - in KeymasterArguments params, in byte[] entropy); + in KeymasterArguments params, in byte[] entropy, int uid); OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input); OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature, in byte[] entropy); diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 73b4a6ed8ea9..017364a5a0b1 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -211,4 +211,12 @@ interface IWindowSession { * The assumption is that this method will be called rather infrequently. */ void pokeDrawLock(IBinder window); + + /** + * Starts a task window move with {startX, startY} as starting point. The amount of move + * will be the offset between {startX, startY} and the new cursor position. + * + * Returns true if the move started successfully; false otherwise. + */ + boolean startMovingTask(IWindow window, float startX, float startY); } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 61ba59049c13..1c20cab55a5d 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -788,8 +788,7 @@ public class KeyEvent extends InputEvent implements Parcelable { /** Key code constant: Step backward media key. * Steps media backward, one frame at a time. */ public static final int KEYCODE_MEDIA_STEP_BACKWARD = 275; - /** Key code constant: put device to sleep unless a wakelock is held. - * @hide */ + /** Key code constant: put device to sleep unless a wakelock is held. */ public static final int KEYCODE_SOFT_SLEEP = 276; private static final int LAST_KEYCODE = KEYCODE_SOFT_SLEEP; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f4fc6e717247..eb591c1bfa86 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -19750,6 +19750,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Starts a move from {startX, startY}, the amount of the movement will be the offset + * between {startX, startY} and the new cursor positon. + * @param startX horizontal coordinate where the move started. + * @param startY vertical coordinate where the move started. + * @return whether moving was started successfully. + * @hide + */ + public final boolean startMovingTask(float startX, float startY) { + if (ViewDebug.DEBUG_POSITIONING) { + Log.d(VIEW_LOG_TAG, "startMovingTask: {" + startX + "," + startY + "}"); + } + try { + return mAttachInfo.mSession.startMovingTask(mAttachInfo.mWindow, startX, startY); + } catch (RemoteException e) { + Log.e(VIEW_LOG_TAG, "Unable to start moving", e); + } + return false; + } + + /** * Handles drag events sent by the system following a call to * {@link android.view.View#startDrag(ClipData,DragShadowBuilder,Object,int) startDrag()}. *<p> diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 8bf53a872b91..8278335143fe 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -78,6 +78,12 @@ public class ViewDebug { public static final boolean DEBUG_DRAG = false; /** + * Enables detailed logging of task positioning operations. + * @hide + */ + public static final boolean DEBUG_POSITIONING = false; + + /** * This annotation can be used to mark fields and methods to be dumped by * the view server. Only non-void methods with no arguments can be annotated * by this annotation. diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 5893f4a3645e..b146a51292e1 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -562,28 +562,6 @@ public abstract class Window { /** Returns the current stack Id for the window. */ int getWindowStackId() throws RemoteException; - - /** - * Returns the bounds of the task that contains this activity. - * - * @return Rect The bounds that contains the activity. - */ - Rect getActivityBounds() throws RemoteException; - - /** - * Sets the bounds (size and position) of the task or stack that contains this - * activity. - * NOTE: The requested bounds might not the fully honored by the system depending - * on the window placement policy. - * - * @param newBounds The new target bounds of the activity in task or stack. - */ - void setActivityBounds(Rect newBounds) throws RemoteException; - - /** - * Activates this activity, hence bringing it to the top and giving it focus. - */ - void activateActivity() throws RemoteException; } public Window(Context context) { diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index 64c41039bea5..2dd84e3fbcdc 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -85,6 +85,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter private OpenOverflowRunnable mPostedOpenRunnable; private ActionMenuPopupCallback mPopupCallback; + private final boolean mShowCascadingMenus; + final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); int mOpenSubMenuId; @@ -126,6 +128,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter public ActionMenuPresenter(Context context) { super(context, com.android.internal.R.layout.action_menu_layout, com.android.internal.R.layout.action_menu_item_layout); + + mShowCascadingMenus = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableCascadingSubmenus); } @Override @@ -500,14 +505,29 @@ public class ActionMenuPresenter extends BaseMenuPresenter } View anchor = findViewForItem(topSubMenu.getItem()); if (anchor == null) { - if (mOverflowButton == null) return false; - anchor = mOverflowButton; + // This means the submenu was opened from an overflow menu item, indicating the + // MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to + // ensure that the MenuPopup acts as presenter for the submenu, and acts on its + // responsibility to display the new submenu. + return false; } mOpenSubMenuId = subMenu.getItem().getItemId(); - mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu); - mActionButtonPopup.setAnchorView(anchor); + + boolean preserveIconSpacing = false; + final int count = subMenu.size(); + for (int i = 0; i < count; i++) { + MenuItem childItem = subMenu.getItem(i); + if (childItem.isVisible() && childItem.getIcon() != null) { + preserveIconSpacing = true; + break; + } + } + + mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor); + mActionButtonPopup.setForceShowIcon(preserveIconSpacing); mActionButtonPopup.show(); + super.onSubMenuSelected(subMenu); return true; } @@ -926,12 +946,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter } private class ActionButtonSubmenu extends MenuPopupHelper { - private SubMenuBuilder mSubMenu; - - public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) { - super(context, subMenu, null, false, + public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) { + super(context, subMenu, anchorView, false, com.android.internal.R.attr.actionOverflowMenuStyle); - mSubMenu = subMenu; MenuItemImpl item = (MenuItemImpl) subMenu.getItem(); if (!item.isActionButton()) { @@ -940,17 +957,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter } setCallback(mPopupPresenterCallback); - - boolean preserveIconSpacing = false; - final int count = subMenu.size(); - for (int i = 0; i < count; i++) { - MenuItem childItem = subMenu.getItem(i); - if (childItem.isVisible() && childItem.getIcon() != null) { - preserveIconSpacing = true; - break; - } - } - setForceShowIcon(preserveIconSpacing); } @Override diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java index 553651308e9c..bcbafc9af158 100644 --- a/core/java/android/widget/DropDownListView.java +++ b/core/java/android/widget/DropDownListView.java @@ -25,10 +25,8 @@ import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.IntProperty; -import android.util.Log; import android.view.MotionEvent; import android.view.View; -import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.TextView; import android.widget.ListView; @@ -128,6 +126,61 @@ public class DropDownListView extends ListView { setCacheColorHint(0); // Transparent, since the background drawable could be anything. } + @Override + protected boolean shouldShowSelector() { + View selectedView = getSelectedView(); + return selectedView != null && selectedView.isEnabled() || super.shouldShowSelector(); + } + + protected void clearSelection() { + setSelectedPositionInt(-1); + setNextSelectedPositionInt(-1); + } + + @Override + public boolean onHoverEvent(MotionEvent ev) { + final int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE) { + final int position = pointToPosition((int) ev.getX(), (int) ev.getY()); + if (position != INVALID_POSITION && position != mSelectedPosition) { + final View hoveredItem = getChildAt(position - getFirstVisiblePosition()); + if (hoveredItem.isEnabled()) { + // Force a focus so that the proper selector state gets used when we update. + requestFocus(); + + positionSelector(position, hoveredItem); + setSelectedPositionInt(position); + setNextSelectedPositionInt(position); + } + updateSelectorState(); + } + } else { + // Do not cancel the selected position if the selection is visible by other reasons. + if (!super.shouldShowSelector()) { + setSelectedPositionInt(INVALID_POSITION); + } + } + return super.onHoverEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + final int position = pointToPosition(x, y); + if (position == INVALID_POSITION) { + return super.onTouchEvent(event); + } + + if (position != mSelectedPosition) { + setSelectedPositionInt(position); + setNextSelectedPositionInt(position); + } + + return super.onTouchEvent(event); + } + /** * Handles forwarded events. * diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 3d07d87dfe70..b95bc283d11a 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -69,7 +69,6 @@ public class ListPopupWindow implements ShowableListMenu { private static final int EXPAND_LIST_TIMEOUT = 250; private Context mContext; - private PopupWindow mPopup; private ListAdapter mAdapter; private DropDownListView mDropDownList; @@ -112,6 +111,8 @@ public class ListPopupWindow implements ShowableListMenu { private int mLayoutDirection; + PopupWindow mPopup; + /** * The provided prompt view should appear above list content. * diff --git a/core/java/android/widget/MenuPopupWindow.java b/core/java/android/widget/MenuPopupWindow.java index 9e47e8513d49..900aa326d502 100644 --- a/core/java/android/widget/MenuPopupWindow.java +++ b/core/java/android/widget/MenuPopupWindow.java @@ -17,10 +17,17 @@ package android.widget; import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.transition.Transition; import android.util.AttributeSet; -import android.view.MotionEvent; +import android.view.KeyEvent; import android.view.View; -import android.view.accessibility.AccessibilityManager; +import android.view.ViewGroup; +import android.view.ViewParent; + +import com.android.internal.view.menu.ListMenuItemView; +import com.android.internal.view.menu.MenuAdapter; /** * A MenuPopupWindow represents the popup window for menu. @@ -40,52 +47,58 @@ public class MenuPopupWindow extends ListPopupWindow { return new MenuDropDownListView(context, hijackFocus); } - static class MenuDropDownListView extends DropDownListView { - private boolean mHoveredOnDisabledItem = false; - private AccessibilityManager mAccessibilityManager; + public void setEnterTransition(Transition enterTransition) { + mPopup.setEnterTransition(enterTransition); + } - MenuDropDownListView(Context context, boolean hijackFocus) { - super(context, hijackFocus); - mAccessibilityManager = (AccessibilityManager) getContext().getSystemService( - Context.ACCESSIBILITY_SERVICE); - } + /** + * Set whether this window is touch modal or if outside touches will be sent to + * other windows behind it. + */ + public void setTouchModal(boolean touchModal) { + mPopup.setTouchModal(touchModal); + } - @Override - protected boolean shouldShowSelector() { - return (isHovered() && !mHoveredOnDisabledItem) || super.shouldShowSelector(); - } + private static class MenuDropDownListView extends DropDownListView { + final int mAdvanceKey; + final int mRetreatKey; - @Override - public boolean onHoverEvent(MotionEvent ev) { - mHoveredOnDisabledItem = false; + public MenuDropDownListView(Context context, boolean hijackFocus) { + super(context, hijackFocus); - // Accessibility system should already handle hover events and selections, menu does - // not have to handle it by itself. - if (mAccessibilityManager.isTouchExplorationEnabled()) { - return super.onHoverEvent(ev); + final Resources res = context.getResources(); + final Configuration config = res.getConfiguration(); + if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT; + mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT; + } else { + mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT; + mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT; } + } - final int action = ev.getActionMasked(); - if (action == MotionEvent.ACTION_HOVER_ENTER - || action == MotionEvent.ACTION_HOVER_MOVE) { - final int position = pointToPosition((int) ev.getX(), (int) ev.getY()); - if (position != INVALID_POSITION && position != mSelectedPosition) { - final View hoveredItem = getChildAt(position - getFirstVisiblePosition()); - if (hoveredItem.isEnabled()) { - positionSelector(position, hoveredItem); - setSelectedPositionInt(position); - } else { - mHoveredOnDisabledItem = true; - } - updateSelectorState(); - } - } else { - // Do not cancel the selected position if the selection is visible by other reasons. - if (!super.shouldShowSelector()) { - setSelectedPositionInt(INVALID_POSITION); + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView(); + if (selectedItem != null && keyCode == mAdvanceKey) { + if (selectedItem.isEnabled() && + ((ListMenuItemView) selectedItem).getItemData().hasSubMenu()) { + performItemClick( + selectedItem, + getSelectedItemPosition(), + getSelectedItemId()); } + return true; + } else if (selectedItem != null && keyCode == mRetreatKey) { + setSelectedPositionInt(-1); + setNextSelectedPositionInt(-1); + + ((MenuAdapter) getAdapter()).getAdapterMenu().close(); + return true; } - return super.onHoverEvent(ev); + return super.onKeyDown(keyCode, event); } + } -} + +}
\ No newline at end of file diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java index 3b2d60d1e1a4..bd1fbb8f29c5 100644 --- a/core/java/android/widget/PopupMenu.java +++ b/core/java/android/widget/PopupMenu.java @@ -43,6 +43,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { private final MenuBuilder mMenu; private final View mAnchor; private final MenuPopupHelper mPopup; + private final boolean mShowCascadingMenus; private OnMenuItemClickListener mMenuItemClickListener; private OnDismissListener mDismissListener; @@ -107,6 +108,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr, int popupStyleRes) { mContext = context; + mShowCascadingMenus = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableCascadingSubmenus); mMenu = new MenuBuilder(context); mMenu.setCallback(this); mAnchor = anchor; @@ -273,8 +276,12 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { return true; } - // Current menu will be dismissed by the normal helper, submenu will be shown in its place. - new MenuPopupHelper(mContext, subMenu, mAnchor).show(); + if (!mShowCascadingMenus) { + // Current menu will be dismissed by the normal helper, submenu will be shown in its + // place. (If cascading menus are enabled, the cascading implementation will show the + // submenu itself). + new MenuPopupHelper(mContext, subMenu, mAnchor).show(); + } return true; } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 5fc8c7a12b26..5b19edfabbe6 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -104,6 +104,7 @@ public class ChooserActivity extends ResolverActivity { sri.resultTargets); } unbindService(sri.connection); + sri.connection.destroy(); mServiceConnections.remove(sri.connection); if (mServiceConnections.isEmpty()) { mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); @@ -208,6 +209,8 @@ public class ChooserActivity extends ResolverActivity { mRefinementResultReceiver.destroy(); mRefinementResultReceiver = null; } + unbindRemainingServices(); + mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT); } @Override @@ -265,6 +268,11 @@ public class ChooserActivity extends ResolverActivity { return true; } + @Override + boolean shouldAutoLaunchSingleChoice() { + return false; + } + private void modifyTargetIntent(Intent in) { final String action = in.getAction(); if (Intent.ACTION_SEND.equals(action) || @@ -371,7 +379,8 @@ public class ChooserActivity extends ResolverActivity { continue; } - final ChooserTargetServiceConnection conn = new ChooserTargetServiceConnection(dri); + final ChooserTargetServiceConnection conn = + new ChooserTargetServiceConnection(this, dri); if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND, UserHandle.CURRENT)) { if (DEBUG) { @@ -425,6 +434,7 @@ public class ChooserActivity extends ResolverActivity { final ChooserTargetServiceConnection conn = mServiceConnections.get(i); if (DEBUG) Log.d(TAG, "unbinding " + conn); unbindService(conn); + conn.destroy(); } mServiceConnections.clear(); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); @@ -1024,54 +1034,93 @@ public class ChooserActivity extends ResolverActivity { } } - class ChooserTargetServiceConnection implements ServiceConnection { + static class ChooserTargetServiceConnection implements ServiceConnection { private final DisplayResolveInfo mOriginalTarget; + private ComponentName mConnectedComponent; + private ChooserActivity mChooserActivity; + private final Object mLock = new Object(); private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { @Override public void sendResult(List<ChooserTarget> targets) throws RemoteException { - filterServiceTargets(mOriginalTarget.getResolveInfo().activityInfo.packageName, - targets); - final Message msg = Message.obtain(); - msg.what = CHOOSER_TARGET_SERVICE_RESULT; - msg.obj = new ServiceResultInfo(mOriginalTarget, targets, - ChooserTargetServiceConnection.this); - mChooserHandler.sendMessage(msg); + synchronized (mLock) { + if (mChooserActivity == null) { + Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from " + + mConnectedComponent + "; ignoring..."); + return; + } + mChooserActivity.filterServiceTargets( + mOriginalTarget.getResolveInfo().activityInfo.packageName, targets); + final Message msg = Message.obtain(); + msg.what = CHOOSER_TARGET_SERVICE_RESULT; + msg.obj = new ServiceResultInfo(mOriginalTarget, targets, + ChooserTargetServiceConnection.this); + mChooserActivity.mChooserHandler.sendMessage(msg); + } } }; - public ChooserTargetServiceConnection(DisplayResolveInfo dri) { + public ChooserTargetServiceConnection(ChooserActivity chooserActivity, + DisplayResolveInfo dri) { + mChooserActivity = chooserActivity; mOriginalTarget = dri; } @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); - final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); - try { - icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), - mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); - } catch (RemoteException e) { - Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); - unbindService(this); - mServiceConnections.remove(this); + synchronized (mLock) { + if (mChooserActivity == null) { + Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected"); + return; + } + + final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); + try { + icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), + mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); + } catch (RemoteException e) { + Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); + mChooserActivity.unbindService(this); + destroy(); + mChooserActivity.mServiceConnections.remove(this); + } } } @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); - unbindService(this); - mServiceConnections.remove(this); - if (mServiceConnections.isEmpty()) { - mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); - sendVoiceChoicesIfNeeded(); + synchronized (mLock) { + if (mChooserActivity == null) { + Log.e(TAG, + "destroyed ChooserTargetServiceConnection got onServiceDisconnected"); + return; + } + + mChooserActivity.unbindService(this); + destroy(); + mChooserActivity.mServiceConnections.remove(this); + if (mChooserActivity.mServiceConnections.isEmpty()) { + mChooserActivity.mChooserHandler.removeMessages( + CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); + mChooserActivity.sendVoiceChoicesIfNeeded(); + } + mConnectedComponent = null; + } + } + + public void destroy() { + synchronized (mLock) { + mChooserActivity = null; } } @Override public String toString() { - return mOriginalTarget.getResolveInfo().activityInfo.toString(); + return "ChooserTargetServiceConnection{service=" + + mConnectedComponent + ", activity=" + + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}"; } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 7dd3bed079fc..9272193af54a 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -234,7 +234,9 @@ public class ResolverActivity extends Activity { mResolverComparator = new ResolverComparator(this, getTargetIntent(), referrerPackage); - configureContentView(mIntents, initialIntents, rList, alwaysUseOption); + if (configureContentView(mIntents, initialIntents, rList, alwaysUseOption)) { + return; + } // Prevent the Resolver window from becoming the top fullscreen window and thus from taking // control of the system bars. @@ -794,6 +796,10 @@ public class ResolverActivity extends Activity { return false; } + boolean shouldAutoLaunchSingleChoice() { + return true; + } + void showAppDetails(ResolveInfo ri) { Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) @@ -808,7 +814,10 @@ public class ResolverActivity extends Activity { launchedFromUid, filterLastUsed); } - void configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, + /** + * Returns true if the activity is finishing and creation should halt + */ + boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean alwaysUseOption) { // The last argument of createAdapter is whether to do special handling // of the last used choice to highlight it in the list. We need to always @@ -828,7 +837,9 @@ public class ResolverActivity extends Activity { mAlwaysUseOption = alwaysUseOption; int count = mAdapter.getUnfilteredCount(); - if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) { + if ((!shouldAutoLaunchSingleChoice() && count > 0) + || count > 1 + || (count == 1 && mAdapter.getOtherProfile() != null)) { setContentView(layoutId); mAdapterView = (AbsListView) findViewById(R.id.resolver_list); onPrepareAdapterView(mAdapterView, mAdapter, alwaysUseOption); @@ -837,7 +848,7 @@ public class ResolverActivity extends Activity { mPackageMonitor.unregister(); mRegistered = false; finish(); - return; + return true; } else { setContentView(R.layout.resolver_list); @@ -847,6 +858,7 @@ public class ResolverActivity extends Activity { mAdapterView = (AbsListView) findViewById(R.id.resolver_list); mAdapterView.setVisibility(View.GONE); } + return false; } void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index 6da0f63f2b0d..b6240e48a1fd 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -44,6 +44,8 @@ public class MetricsLogger implements MetricsConstants { public static final int ACTION_FINGERPRINT_AUTH = 252; public static final int ACTION_FINGERPRINT_DELETE = 253; public static final int ACTION_FINGERPRINT_RENAME = 254; + public static final int ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE = 255; + public static final int ACTION_WIGGLE_CAMERA_GESTURE = 256; public static void visible(Context context, int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 55e23b14b7d0..7f01841d8afe 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -174,6 +174,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // buttons. The visibility of this decor depends on the workspace and the window type. // If the window type does not require such a view, this member might be null. NonClientDecorView mNonClientDecorView; + // The non client decor needs to adapt to the used workspace. Since querying and changing the // workspace is expensive, this is the workspace value the window is currently set up for. int mWorkspaceId; @@ -2495,6 +2496,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return onInterceptTouchEvent(event); } + private boolean isOutOfInnerBounds(int x, int y) { + return x < 0 || y < 0 || x > getWidth() || y > getHeight(); + } + private boolean isOutOfBounds(int x, int y) { return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5); @@ -2503,6 +2508,24 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); + if (mHasNonClientDecor && mNonClientDecorView.mVisible) { + // Don't dispatch ACTION_DOWN to the non client decor if the window is + // resizable and the event was (starting) outside the window. + // Window resizing events should be handled by WindowManager. + // TODO: Investigate how to handle the outside touch in window manager + // without generating these events. + // Currently we receive these because we need to enlarge the window's + // touch region so that the monitor channel receives the events + // in the outside touch area. + if (action == MotionEvent.ACTION_DOWN) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + if (isOutOfInnerBounds(x, y)) { + return true; + } + } + } + if (mFeatureId >= 0) { if (action == MotionEvent.ACTION_DOWN) { int x = (int)event.getX(); diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java new file mode 100644 index 000000000000..4c829a240e70 --- /dev/null +++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java @@ -0,0 +1,394 @@ +package com.android.internal.view.menu; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import android.annotation.IntDef; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Rect; +import android.os.Parcelable; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnKeyListener; +import android.widget.AdapterView; +import android.widget.DropDownListView; +import android.widget.ListView; +import android.widget.MenuPopupWindow; +import android.widget.PopupWindow; +import android.widget.PopupWindow.OnDismissListener; + +import com.android.internal.util.Preconditions; + +/** + * A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by + * side. + * @hide + */ +final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemClickListener, + MenuPresenter, OnKeyListener, PopupWindow.OnDismissListener { + @Retention(RetentionPolicy.SOURCE) + @IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT}) + public @interface HorizPosition {} + + private static final int HORIZ_POSITION_LEFT = 0; + private static final int HORIZ_POSITION_RIGHT = 1; + + private final Context mContext; + private final int mMenuMaxWidth; + private final int mPopupStyleAttr; + private final int mPopupStyleRes; + private final boolean mOverflowOnly; + private final int mLayoutDirection; + + private int mDropDownGravity = Gravity.NO_GRAVITY; + private View mAnchor; + private List<DropDownListView> mListViews; + private List<MenuPopupWindow> mPopupWindows; + private List<int[]> mOffsets; + private int mPreferredPosition; + private boolean mForceShowIcon; + private Callback mPresenterCallback; + private PopupWindow.OnDismissListener mOnDismissListener; + + /** + * Initializes a new cascading-capable menu popup. + * + * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token from. + */ + public CascadingMenuPopup(Context context, View anchor, int popupStyleAttr, + int popupStyleRes, boolean overflowOnly) { + mContext = Preconditions.checkNotNull(context); + mAnchor = Preconditions.checkNotNull(anchor); + mPopupStyleAttr = popupStyleAttr; + mPopupStyleRes = popupStyleRes; + mOverflowOnly = overflowOnly; + + mForceShowIcon = false; + + final Resources res = context.getResources(); + final Configuration config = res.getConfiguration(); + mLayoutDirection = config.getLayoutDirection(); + mPreferredPosition = mLayoutDirection == View.LAYOUT_DIRECTION_RTL ? HORIZ_POSITION_LEFT : + HORIZ_POSITION_RIGHT; + mMenuMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2, + res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth)); + + mPopupWindows = new ArrayList<MenuPopupWindow>(); + mListViews = new ArrayList<DropDownListView>(); + mOffsets = new ArrayList<int[]>(); + } + + @Override + public void setForceShowIcon(boolean forceShow) { + mForceShowIcon = forceShow; + } + + private MenuPopupWindow createPopupWindow() { + MenuPopupWindow popupWindow = new MenuPopupWindow( + mContext, null, mPopupStyleAttr, mPopupStyleRes); + popupWindow.setOnItemClickListener(this); + popupWindow.setOnDismissListener(this); + popupWindow.setAnchorView(mAnchor); + popupWindow.setDropDownGravity(mDropDownGravity); + popupWindow.setModal(true); + popupWindow.setTouchModal(false); + return popupWindow; + } + + @Override + public void show() { + if (isShowing()) { + return; + } + + // Show any menus that have been added via #addMenu(MenuBuilder) but which have not yet been + // shown. + // In a typical use case, #addMenu(MenuBuilder) would be called once, followed by a call to + // this #show() method -- which would actually show the popup on the screen. + for (int i = 0; i < mPopupWindows.size(); i++) { + MenuPopupWindow popupWindow = mPopupWindows.get(i); + popupWindow.show(); + mListViews.add((DropDownListView) popupWindow.getListView()); + } + } + + @Override + public void dismiss() { + // Need to make another list to avoid a concurrent modification exception, as #onDismiss + // may clear mPopupWindows while we are iterating. + List<MenuPopupWindow> popupWindows = new ArrayList<MenuPopupWindow>(mPopupWindows); + for (MenuPopupWindow popupWindow : popupWindows) { + if (popupWindow != null && popupWindow.isShowing()) { + popupWindow.dismiss(); + } + } + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + MenuAdapter adapter = (MenuAdapter) parent.getAdapter(); + adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0); + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { + dismiss(); + return true; + } + return false; + } + + /** + * Determines whether the next submenu (of the given width) should display on the right or on + * the left of the most recent menu. + * + * @param nextMenuWidth Width of the next submenu to display. + * @return The position to display it. + */ + @HorizPosition + private int getNextMenuPosition(int nextMenuWidth) { + ListView lastListView = mListViews.get(mListViews.size() - 1); + + final int[] screenLocation = new int[2]; + lastListView.getLocationOnScreen(screenLocation); + + final Rect displayFrame = new Rect(); + mAnchor.getWindowVisibleDisplayFrame(displayFrame); + + if (mPreferredPosition == HORIZ_POSITION_RIGHT) { + final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth; + if (right > displayFrame.right) { + return HORIZ_POSITION_LEFT; + } + return HORIZ_POSITION_RIGHT; + } else { // LEFT + final int left = screenLocation[0] - nextMenuWidth; + if (left < 0) { + return HORIZ_POSITION_RIGHT; + } + return HORIZ_POSITION_LEFT; + } + } + + @Override + public void addMenu(MenuBuilder menu) { + boolean addSubMenu = mListViews.size() > 0; + + menu.addMenuPresenter(this, mContext); + + MenuPopupWindow popupWindow = createPopupWindow(); + + MenuAdapter adapter = new MenuAdapter(menu, LayoutInflater.from(mContext), mOverflowOnly); + adapter.setForceShowIcon(mForceShowIcon); + + popupWindow.setAdapter(adapter); + + int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth); + + int x = 0; + int y = 0; + + if (addSubMenu) { + popupWindow.setEnterTransition(null); + + ListView lastListView = mListViews.get(mListViews.size() - 1); + @HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth); + boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT; + mPreferredPosition = nextMenuPosition; + + int[] lastLocation = new int[2]; + lastListView.getLocationOnScreen(lastLocation); + + int[] lastOffset = mOffsets.get(mOffsets.size() - 1); + + // Note: By now, mDropDownGravity is the absolute gravity, so this should work in both + // LTR and RTL. + if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) { + if (showOnRight) { + x = lastOffset[0] + menuWidth; + } else { + x = lastOffset[0] - lastListView.getWidth(); + } + } else { + if (showOnRight) { + x = lastOffset[0] + lastListView.getWidth(); + } else { + x = lastOffset[0] - menuWidth; + } + } + + y = lastOffset[1] + lastListView.getSelectedView().getTop() - + lastListView.getChildAt(0).getTop(); + } + + popupWindow.setWidth(menuWidth); + popupWindow.setHorizontalOffset(x); + popupWindow.setVerticalOffset(y); + mPopupWindows.add(popupWindow); + + // NOTE: This case handles showing submenus once the CascadingMenuPopup has already + // been shown via a call to its #show() method. If it hasn't yet been show()n, then + // we deliberately do not yet show the popupWindow, as #show() will do that later. + if (isShowing()) { + popupWindow.show(); + mListViews.add((DropDownListView) popupWindow.getListView()); + } + + int[] offsets = {x, y}; + mOffsets.add(offsets); + } + + /** + * @return {@code true} if the popup is currently showing, {@code false} otherwise. + */ + @Override + public boolean isShowing() { + return mPopupWindows.size() > 0 && mPopupWindows.get(0).isShowing(); + } + + /** + * Called when one or more of the popup windows was dismissed. + */ + @Override + public void onDismiss() { + int dismissedIndex = -1; + for (int i = 0; i < mPopupWindows.size(); i++) { + if (!mPopupWindows.get(i).isShowing()) { + dismissedIndex = i; + break; + } + } + + if (dismissedIndex != -1) { + for (int i = dismissedIndex; i < mListViews.size(); i++) { + ListView view = mListViews.get(i); + MenuAdapter adapter = (MenuAdapter) view.getAdapter(); + adapter.mAdapterMenu.close(); + } + } + } + + @Override + public void updateMenuView(boolean cleared) { + for (ListView view : mListViews) { + ((MenuAdapter) view.getAdapter()).notifyDataSetChanged(); + } + } + + @Override + public void setCallback(Callback cb) { + mPresenterCallback = cb; + } + + @Override + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + // Don't allow double-opening of the same submenu. + for (ListView view : mListViews) { + if (((MenuAdapter) view.getAdapter()).mAdapterMenu.equals(subMenu)) { + // Just re-focus that one. + view.requestFocus(); + return true; + } + } + + if (subMenu.hasVisibleItems()) { + this.addMenu(subMenu); + if (mPresenterCallback != null) { + mPresenterCallback.onOpenSubMenu(subMenu); + } + return true; + } + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + int menuIndex = -1; + boolean wasSelected = false; + + for (int i = 0; i < mListViews.size(); i++) { + ListView view = mListViews.get(i); + MenuAdapter adapter = (MenuAdapter) view.getAdapter(); + + if (menuIndex == -1 && menu == adapter.mAdapterMenu) { + menuIndex = i; + wasSelected = view.getSelectedItem() != null; + } + + // Once the menu has been found, remove it and all submenus beneath it from the + // container view. Also remove the presenter. + if (menuIndex != -1) { + adapter.mAdapterMenu.removeMenuPresenter(this); + } + } + + // Then, actually remove the views for these [sub]menu(s) from our list of views. + if (menuIndex != -1) { + for (int i = menuIndex; i < mPopupWindows.size(); i++) { + mPopupWindows.get(i).dismiss(); + } + mPopupWindows.subList(menuIndex, mPopupWindows.size()).clear(); + mListViews.subList(menuIndex, mListViews.size()).clear(); + mOffsets.subList(menuIndex, mOffsets.size()).clear(); + + // If there's still a menu open, refocus the new leaf [sub]menu. + if (mListViews.size() > 0) { + mListViews.get(mListViews.size() - 1).requestFocus(); + } + } + + if (mListViews.size() == 0 || wasSelected) { + dismiss(); + if (mPresenterCallback != null) { + mPresenterCallback.onCloseMenu(menu, allMenusAreClosing); + } + } + + if (mPopupWindows.size() == 0) { + // If every [sub]menu was dismissed, that means the whole thing was dismissed, so notify + // the owner. + mOnDismissListener.onDismiss(); + } + } + + @Override + public boolean flagActionItems() { + return false; + } + + @Override + public Parcelable onSaveInstanceState() { + return null; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + } + + @Override + public void setGravity(int dropDownGravity) { + mDropDownGravity = Gravity.getAbsoluteGravity(dropDownGravity, mLayoutDirection); + } + + @Override + public void setAnchorView(View anchor) { + mAnchor = anchor; + } + + @Override + public void setOnDismissListener(OnDismissListener listener) { + mOnDismissListener = listener; + } + + @Override + public ListView getListView() { + return mListViews.size() > 0 ? mListViews.get(mListViews.size() - 1) : null; + } +}
\ No newline at end of file diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index e8d1ead7d651..167392874173 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -64,6 +64,7 @@ public class MenuBuilder implements Menu { private final Context mContext; private final Resources mResources; + private final boolean mShowCascadingMenus; /** * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode() @@ -186,6 +187,8 @@ public class MenuBuilder implements Menu { public MenuBuilder(Context context) { mContext = context; mResources = context.getResources(); + mShowCascadingMenus = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableCascadingSubmenus); mItems = new ArrayList<MenuItemImpl>(); @@ -909,7 +912,9 @@ public class MenuBuilder implements Menu { invoked |= itemImpl.expandActionView(); if (invoked) close(true); } else if (itemImpl.hasSubMenu() || providerHasSubMenu) { - close(false); + if (!mShowCascadingMenus) { + close(false); + } if (!itemImpl.hasSubMenu()) { itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl)); diff --git a/core/java/com/android/internal/view/menu/MenuPopup.java b/core/java/com/android/internal/view/menu/MenuPopup.java index 91788cab49fa..b43e8adbc8ac 100644 --- a/core/java/com/android/internal/view/menu/MenuPopup.java +++ b/core/java/com/android/internal/view/menu/MenuPopup.java @@ -35,9 +35,11 @@ public abstract class MenuPopup implements ShowableListMenu, MenuPresenter { public abstract void setForceShowIcon(boolean forceShow); /** - * Adds the given menu to the popup. If this is the first menu shown it'll be displayed; if it's - * a submenu it will be displayed adjacent to the most recent menu (if supported by the - * implementation). + * Adds the given menu to the popup, if it is capable of displaying submenus within itself. + * If menu is the first menu shown, it won't be displayed until show() is called. + * If the popup was already showing, adding a submenu via this method will cause that new + * submenu to be shown immediately (that is, if this MenuPopup implementation is capable of + * showing its own submenus). * * @param menu */ diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 9a47fc1834bb..e0d7feefb1b0 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -17,10 +17,8 @@ package com.android.internal.view.menu; import android.content.Context; -import android.os.Parcelable; import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.PopupWindow; @@ -30,7 +28,7 @@ import android.widget.PopupWindow; * @hide */ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, - PopupWindow.OnDismissListener, View.OnAttachStateChangeListener, MenuPresenter { + PopupWindow.OnDismissListener, View.OnAttachStateChangeListener { private final Context mContext; private final MenuBuilder mMenu; private final boolean mOverflowOnly; @@ -70,9 +68,8 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, private MenuPopup createMenuPopup() { if (mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableCascadingSubmenus)) { - // TODO: Return a Cascading implementation of MenuPopup instead. - return new StandardMenuPopup( - mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly); + return new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr, mPopupStyleRes, + mOverflowOnly); } return new StandardMenuPopup( mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly); @@ -187,62 +184,7 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, v.removeOnAttachStateChangeListener(this); } - @Override - public void initForMenu(Context context, MenuBuilder menu) { - // Don't need to do anything; we added as a presenter in the constructor. - } - - @Override - public MenuView getMenuView(ViewGroup root) { - throw new UnsupportedOperationException("MenuPopupHelpers manage their own views"); - } - - @Override - public void updateMenuView(boolean cleared) { - mPopup.updateMenuView(cleared); - } - - @Override - public void setCallback(Callback cb) { + public void setCallback(MenuPresenter.Callback cb) { mPopup.setCallback(cb); } - - @Override - public boolean onSubMenuSelected(SubMenuBuilder subMenu) { - return mPopup.onSubMenuSelected(subMenu); - } - - @Override - public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { - mPopup.onCloseMenu(menu, allMenusAreClosing); - } - - @Override - public boolean flagActionItems() { - return false; - } - - @Override - public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { - return false; - } - - @Override - public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { - return false; - } - - @Override - public int getId() { - return 0; - } - - @Override - public Parcelable onSaveInstanceState() { - return null; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - } } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index d96a909fb039..6a98ec70f92e 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1178,7 +1178,7 @@ public class LockPatternUtils { * @param userId either an explicit user id or {@link android.os.UserHandle#USER_ALL} */ public void requireCredentialEntry(int userId) { - requireStrongAuth(StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_REQUEST, userId); + requireStrongAuth(StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId); } /** @@ -1260,7 +1260,7 @@ public class LockPatternUtils { value = { STRONG_AUTH_NOT_REQUIRED, STRONG_AUTH_REQUIRED_AFTER_BOOT, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW, - STRONG_AUTH_REQUIRED_AFTER_USER_REQUEST}) + SOME_AUTH_REQUIRED_AFTER_USER_REQUEST}) @Retention(RetentionPolicy.SOURCE) public @interface StrongAuthFlags {} @@ -1275,14 +1275,14 @@ public class LockPatternUtils { public static final int STRONG_AUTH_REQUIRED_AFTER_BOOT = 0x1; /** - * Strong authentication is required because a device admin has temporarily requested it. + * Strong authentication is required because a device admin has requested it. */ public static final int STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW = 0x2; /** - * Strong authentication is required because the user has temporarily requested it. + * Some authentication is required because the user has temporarily disabled trust. */ - public static final int STRONG_AUTH_REQUIRED_AFTER_USER_REQUEST = 0x4; + public static final int SOME_AUTH_REQUIRED_AFTER_USER_REQUEST = 0x4; /** * Strong authentication is required because the user has been locked out after too many @@ -1298,6 +1298,7 @@ public class LockPatternUtils { public static final int DEFAULT = STRONG_AUTH_REQUIRED_AFTER_BOOT; private static final int ALLOWING_FINGERPRINT = STRONG_AUTH_NOT_REQUIRED + | SOME_AUTH_REQUIRED_AFTER_USER_REQUEST | SOME_AUTH_REQUIRED_AFTER_WRONG_CREDENTIAL; private final SparseIntArray mStrongAuthRequiredForUser = new SparseIntArray(); diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java index 491b3233ac5d..6ab306cec730 100644 --- a/core/java/com/android/internal/widget/NonClientDecorView.java +++ b/core/java/com/android/internal/widget/NonClientDecorView.java @@ -26,7 +26,6 @@ import android.widget.LinearLayout; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.Window; -import android.view.WindowInsets; import android.util.Log; import android.util.TypedValue; @@ -71,16 +70,11 @@ public class NonClientDecorView extends LinearLayout implements View.OnClickList // True if the window is being dragged. private boolean mDragging = false; - // The bounds of the window and the absolute mouse pointer coordinates from before we started to - // drag the window. They will be used to determine the next window position. - private final Rect mWindowOriginalBounds = new Rect(); - private float mStartDragX; - private float mStartDragY; // True when the left mouse button got released while dragging. private boolean mLeftMouseButtonReleased; - // Avoiding re-creation of Rect's by keeping a temporary window drag bound. - private final Rect mWindowDragBounds = new Rect(); + // True if this window is resizable (which is currently only true when the decor is shown). + public boolean mVisible = false; // The current focus state of the window for updating the window elevation. private boolean mWindowHasFocus = true; @@ -128,18 +122,14 @@ public class NonClientDecorView extends LinearLayout implements View.OnClickList // When there is no decor we should not react to anything. return false; } - // Ensure that the activity is active. - activateActivity(); // A drag action is started if we aren't dragging already and the starting event is // either a left mouse button or any other input device. if (!mDragging && (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE || (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) { mDragging = true; - mWindowOriginalBounds.set(getActivityBounds()); mLeftMouseButtonReleased = false; - mStartDragX = e.getRawX(); - mStartDragY = e.getRawY(); + startMovingTask(e.getRawX(), e.getRawY()); } break; @@ -152,29 +142,17 @@ public class NonClientDecorView extends LinearLayout implements View.OnClickList mLeftMouseButtonReleased = true; break; } - mWindowDragBounds.set(mWindowOriginalBounds); - mWindowDragBounds.offset(Math.round(e.getRawX() - mStartDragX), - Math.round(e.getRawY() - mStartDragY)); - setActivityBounds(mWindowDragBounds); } break; case MotionEvent.ACTION_UP: - if (mDragging) { - // Since the window is already where it should be we don't have to do anything - // special at this time. - mDragging = false; - return true; - } - break; - case MotionEvent.ACTION_CANCEL: - if (mDragging) { - mDragging = false; - setActivityBounds(mWindowOriginalBounds); - return true; + if (!mDragging) { + break; } - break; + // Abort the ongoing dragging. + mDragging = false; + return true; } return mDragging; } @@ -246,6 +224,7 @@ public class NonClientDecorView extends LinearLayout implements View.OnClickList boolean invisible = isFillingScreen() || !mShowDecor; View caption = getChildAt(0); caption.setVisibility(invisible ? GONE : VISIBLE); + mVisible = !invisible; } /** @@ -312,54 +291,4 @@ public class NonClientDecorView extends LinearLayout implements View.OnClickList } } } - - /** - * Returns the bounds of this activity. - * @return Returns bounds of the activity. It will return null if either the window is - * fullscreen or the bounds could not be retrieved. - */ - private Rect getActivityBounds() { - Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback(); - if (callback != null) { - try { - return callback.getActivityBounds(); - } catch (RemoteException ex) { - Log.e(TAG, "Failed to get the activity bounds."); - } - } - return null; - } - - /** - * Sets the bounds of this Activity on the stack. - * @param newBounds The bounds of the activity. Passing null is not allowed. - */ - private void setActivityBounds(Rect newBounds) { - if (newBounds == null) { - Log.e(TAG, "Failed to set null bounds to the activity."); - return; - } - Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback(); - if (callback != null) { - try { - callback.setActivityBounds(newBounds); - } catch (RemoteException ex) { - Log.e(TAG, "Failed to set the activity bounds."); - } - } - } - - /** - * Activates the activity - means setting the focus and moving it to the top of the stack. - */ - private void activateActivity() { - Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback(); - if (callback != null) { - try { - callback.activateActivity(); - } catch (RemoteException ex) { - Log.e(TAG, "Failed to activate the activity."); - } - } - } } diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 9aa544fb4e9d..7ca0654f9656 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -510,7 +510,7 @@ static jlong android_content_AssetManager_getAssetRemainingLength(JNIEnv* env, j } static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz, - jstring path) + jstring path, jboolean appAsLib) { ScopedUtfChars path8(env, path); if (path8.c_str() == NULL) { @@ -523,7 +523,7 @@ static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz } int32_t cookie; - bool res = am->addAssetPath(String8(path8.c_str()), &cookie); + bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib); return (res) ? static_cast<jint>(cookie) : 0; } @@ -2138,7 +2138,7 @@ static JNINativeMethod gAssetManagerMethods[] = { (void*) android_content_AssetManager_getAssetLength }, { "getAssetRemainingLength", "(J)J", (void*) android_content_AssetManager_getAssetRemainingLength }, - { "addAssetPathNative", "(Ljava/lang/String;)I", + { "addAssetPathNative", "(Ljava/lang/String;Z)I", (void*) android_content_AssetManager_addAssetPath }, { "addOverlayPathNative", "(Ljava/lang/String;)I", (void*) android_content_AssetManager_addOverlayPath }, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d6657dd5ad8d..921385d98bc5 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1071,6 +1071,11 @@ <permission android:name="android.permission.CONNECTIVITY_INTERNAL" android:protectionLevel="signature|privileged" /> + <!-- Allows a system application to access hardware packet offload capabilities. + @hide --> + <permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide --> <permission android:name="android.permission.RECEIVE_DATA_ACTIVITY_CHANGE" diff --git a/core/res/res/layout/calendar_view.xml b/core/res/res/layout/calendar_view.xml index bccb05681e85..5b32a39275a2 100644 --- a/core/res/res/layout/calendar_view.xml +++ b/core/res/res/layout/calendar_view.xml @@ -28,7 +28,7 @@ android:layout_gravity="center_horizontal" android:paddingTop="10dip" android:paddingBottom="10dip" - style="@android:style/TextAppearance.Medium" /> + style="?android:attr/textAppearanceMedium" /> <LinearLayout android:id="@+android:id/day_names" android:orientation="horizontal" diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 9b6588221704..bb44ed302a58 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -890,7 +890,7 @@ <string name="whichHomeApplication" msgid="4307587691506919691">"Selecciona una aplicación de inicio"</string> <string name="whichHomeApplicationNamed" msgid="4493438593214760979">"Usar %1$s como aplicación de inicio"</string> <string name="alwaysUse" msgid="4583018368000610438">"Usar siempre para esta acción"</string> - <string name="use_a_different_app" msgid="8134926230585710243">"Uitliza otra aplicación"</string> + <string name="use_a_different_app" msgid="8134926230585710243">"Utiliza otra aplicación"</string> <string name="clearDefaultHintMsg" msgid="3252584689512077257">"Para borrar los valores predeterminados, accede a Ajustes del sistema > Aplicaciones > Descargadas."</string> <string name="chooseActivity" msgid="7486876147751803333">"Selecciona una acción"</string> <string name="chooseUsbActivity" msgid="6894748416073583509">"Elegir una aplicación para el dispositivo USB"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 2e3c56996b80..6c031593e156 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1436,7 +1436,7 @@ <string name="package_installed_device_owner" msgid="8420696545959087545">"توسط سرپرستتان نصب شد"</string> <string name="package_updated_device_owner" msgid="8856631322440187071">"توسط سرپرست شما بهروزرسانی شد"</string> <string name="package_deleted_device_owner" msgid="7650577387493101353">"توسط سرپرستتان حذف شد"</string> - <string name="battery_saver_description" msgid="1960431123816253034">"برای کمک به بهبود ماندگاری باتری، ذخیره کننده باتری عملکرد دستگاهتان را کاهش میدهد و لرزش، سرویسهای مبتنی بر مکان، و دسترسی به اکثر دادهها در پسزمینه را محدود میکند. ایمیل، پیامرسانی و برنامههای دیگری که به همگامسازی متکی هستند، تا زمانیکه آنها را باز نکنید نمیتوانند بهروز شوند.\n\nذخیره کننده باتری بهصورت خودکار در هنگام شارژ شدن دستگاه خاموش میشود."</string> + <string name="battery_saver_description" msgid="1960431123816253034">"برای کمک به بهبود عمر باتری، بهینهسازی باتری عملکرد دستگاهتان را کاهش میدهد و لرزش، سرویسهای مبتنی بر مکان، و دسترسی به اکثر دادهها در پسزمینه را محدود میکند. ایمیل، پیامرسانی و برنامههای دیگری که به همگامسازی وابستهاند، تا زمانیکه آنها را باز نکنید نمیتوانند بهروز شوند.\n\nبهینهسازی باتری بهصورت خودکار در هنگام شارژ شدن دستگاه خاموش میشود."</string> <plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848"> <item quantity="one">به مدت %1$d دقیقه (تا <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item> <item quantity="other">به مدت %1$d دقیقه (تا <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 37d9baceb6dd..bf046e3f62b9 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -350,8 +350,8 @@ <string name="permdesc_camera" msgid="8497216524735535009">"Uygulamaya kamerayla fotoğraf ve video çekme izni verir. Bu izin, uygulamanın sizin onayınız olmadan istediği zaman kamerayı kullanmasına olanak sağlar."</string> <string name="permlab_vibrate" msgid="7696427026057705834">"titreşimi denetleme"</string> <string name="permdesc_vibrate" msgid="6284989245902300945">"Uygulamaya, titreşimi denetleme izni verir."</string> - <string name="permlab_flashlight" msgid="2155920810121984215">"flaşı denetle"</string> - <string name="permdesc_flashlight" msgid="6522284794568368310">"Uygulamaya, flaş ışığını denetleme izni verir."</string> + <string name="permlab_flashlight" msgid="2155920810121984215">"el fenerini denetle"</string> + <string name="permdesc_flashlight" msgid="6522284794568368310">"Uygulamaya, el fenerini denetleme izni verir."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"telefon numaralarına doğrudan çağrı yap"</string> <string name="permdesc_callPhone" msgid="3740797576113760827">"Uygulamaya sizin müdahaleniz olmadan telefon numaralarına çağrı yapma izni verir. Bu durum beklenmeyen ödemelere veya çağrılara neden olabilir. Ancak bu iznin, uygulamanın acil numaralara çağrı yapmasına olanak sağlamadığını unutmayın. Kötü amaçlı uygulamalar onayınız olmadan çağrılar yaparak sizi zarara sokabilir."</string> <string name="permlab_accessImsCallService" msgid="3574943847181793918">"IMS çağrı hizmetine erişme"</string> diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index 90448023bbf8..41b05ea8757a 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -51,4 +51,7 @@ <!-- Scale factor threshold used by the screen magnifier to determine when to switch from panning to scaling the magnification viewport. --> <item name="config_screen_magnification_scaling_threshold" format="float" type="dimen">0.1</item> + + <!-- Do not show the message saying USB is connected in charging mode. --> + <bool name="config_usbChargingMessage">false</bool> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e43a8d3920a9..a5960a9571a0 100755..100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -252,7 +252,7 @@ <integer name="config_networkTransitionTimeout">60000</integer> <!-- List of regexpressions describing the interface (if any) that represent tetherable - USB interfaces. If the device doesn't want to support tething over USB this should + USB interfaces. If the device doesn't want to support tethering over USB this should be empty. An example would be "usb.*" --> <string-array translatable="false" name="config_tether_usb_regexs"> </string-array> @@ -406,6 +406,10 @@ <!-- Boolean indicating autojoin will prefer 5GHz and choose 5GHz BSSIDs --> <bool translatable="true" name="config_wifi_enable_5GHz_preference">true</bool> + <!-- Boolean indicating whether or not to revert to default country code when cellular + radio is unable to find any MCC information to infer wifi country code from --> + <bool translatable="false" name="config_wifi_revert_country_code_on_cellular_loss">false</bool> + <!-- Integer specifying the basic autojoin parameters --> <integer translatable="false" name="config_wifi_framework_5GHz_preference_boost_threshold">-65</integer> <integer translatable="false" name="config_wifi_framework_5GHz_preference_boost_factor">5</integer> @@ -539,6 +543,9 @@ <!-- If this is true, the screen will come on when you unplug usb/power/whatever. --> <bool name="config_unplugTurnsOnScreen">false</bool> + <!-- If this is true, the message that USB is only being used for charging will be shown. --> + <bool name="config_usbChargingMessage">true</bool> + <!-- Set this true only if the device has separate attention and notification lights. --> <bool name="config_useAttentionLight">false</bool> @@ -2293,6 +2300,9 @@ <string-array name="config_cell_retries_per_error_code"> </string-array> + <!-- Set initial MaxRetry value for operators --> + <integer name="config_mdc_initial_max_retry">1</integer> + <!-- The OEM specified sensor type for the gesture to launch the camear app. --> <integer name="config_cameraLaunchGestureSensorType">-1</integer> <!-- The OEM specified sensor string type for the gesture to launch camera app, this value @@ -2302,4 +2312,12 @@ <!-- Whether to open UI submenus side by side with the top menu (as opposed to replacing the top menu). --> <bool name="config_enableCascadingSubmenus">false</bool> + + <!-- Allow the gesture to double tap the power button twice to start the camera while the device + is non-interactive. --> + <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool> + + <!-- Name of the component to handle network policy notifications. If present, + disables NetworkPolicyManagerService's presentation of data-usage notifications. --> + <string translatable="false" name="config_networkPolicyNotificationComponent"></string> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 09c1e6fa49b9..8635a4fac7bc 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -37,7 +37,7 @@ <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height --> <dimen name="navigation_bar_height_landscape">48dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> - <dimen name="navigation_bar_width">42dp</dimen> + <dimen name="navigation_bar_width">48dp</dimen> <!-- Height of notification icons in the status bar --> <dimen name="status_bar_icon_size">24dip</dimen> <!-- Size of the giant number (unread count) in the notifications --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 48a7a1c565e9..16e44b861790 100755..100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -306,6 +306,7 @@ <java-symbol type="bool" name="config_wifi_only_link_same_credential_configurations" /> <java-symbol type="bool" name="config_wifi_enable_disconnection_debounce" /> <java-symbol type="bool" name="config_wifi_enable_5GHz_preference" /> + <java-symbol type="bool" name="config_wifi_revert_country_code_on_cellular_loss" /> <java-symbol type="bool" name="config_supportMicNearUltrasound" /> <java-symbol type="bool" name="config_supportSpeakerNearUltrasound" /> <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" /> @@ -407,6 +408,7 @@ <java-symbol type="integer" name="config_volte_replacement_rat"/> <java-symbol type="integer" name="config_valid_wappush_index" /> <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" /> + <java-symbol type="integer" name="config_mdc_initial_max_retry" /> <java-symbol type="bool" name="config_hasPermanentDpad" /> <java-symbol type="color" name="tab_indicator_text_v4" /> @@ -1631,6 +1633,7 @@ <java-symbol type="bool" name="config_enableNetworkLocationOverlay" /> <java-symbol type="bool" name="config_sf_limitedAlpha" /> <java-symbol type="bool" name="config_unplugTurnsOnScreen" /> + <java-symbol type="bool" name="config_usbChargingMessage" /> <java-symbol type="bool" name="config_allowAutoBrightnessWhileDozing" /> <java-symbol type="bool" name="config_allowTheaterModeWakeFromUnplug" /> <java-symbol type="bool" name="config_allowTheaterModeWakeFromGesture" /> @@ -2314,6 +2317,7 @@ <!-- Gesture --> <java-symbol type="integer" name="config_cameraLaunchGestureSensorType" /> <java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" /> + <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" /> <java-symbol type="drawable" name="platlogo_m" /> </resources> diff --git a/docs/html/guide/topics/renderscript/reference/rs_math.jd b/docs/html/guide/topics/renderscript/reference/rs_math.jd index 13513e9a0ca5..e1e78051df6e 100644 --- a/docs/html/guide/topics/renderscript/reference/rs_math.jd +++ b/docs/html/guide/topics/renderscript/reference/rs_math.jd @@ -3968,16 +3968,31 @@ In rs_fp_relaxed mode, mad() may not do the rounding after multiplicaiton. <td> </td> </tr> <tr> + <td><a href='rs_value_types.html#android_rs:float2'>float2</a> max(<a href='rs_value_types.html#android_rs:float2'>float2</a> a, float b); +</td> + <td> </td> + </tr> + <tr> <td><a href='rs_value_types.html#android_rs:float2'>float2</a> max(<a href='rs_value_types.html#android_rs:float2'>float2</a> a, <a href='rs_value_types.html#android_rs:float2'>float2</a> b); </td> <td> </td> </tr> <tr> + <td><a href='rs_value_types.html#android_rs:float3'>float3</a> max(<a href='rs_value_types.html#android_rs:float3'>float3</a> a, float b); +</td> + <td> </td> + </tr> + <tr> <td><a href='rs_value_types.html#android_rs:float3'>float3</a> max(<a href='rs_value_types.html#android_rs:float3'>float3</a> a, <a href='rs_value_types.html#android_rs:float3'>float3</a> b); </td> <td> </td> </tr> <tr> + <td><a href='rs_value_types.html#android_rs:float4'>float4</a> max(<a href='rs_value_types.html#android_rs:float4'>float4</a> a, float b); +</td> + <td> </td> + </tr> + <tr> <td><a href='rs_value_types.html#android_rs:float4'>float4</a> max(<a href='rs_value_types.html#android_rs:float4'>float4</a> a, <a href='rs_value_types.html#android_rs:float4'>float4</a> b); </td> <td> </td> @@ -4172,16 +4187,31 @@ In rs_fp_relaxed mode, mad() may not do the rounding after multiplicaiton. <td> </td> </tr> <tr> + <td><a href='rs_value_types.html#android_rs:float2'>float2</a> min(<a href='rs_value_types.html#android_rs:float2'>float2</a> a, float b); +</td> + <td> </td> + </tr> + <tr> <td><a href='rs_value_types.html#android_rs:float2'>float2</a> min(<a href='rs_value_types.html#android_rs:float2'>float2</a> a, <a href='rs_value_types.html#android_rs:float2'>float2</a> b); </td> <td> </td> </tr> <tr> + <td><a href='rs_value_types.html#android_rs:float3'>float3</a> min(<a href='rs_value_types.html#android_rs:float3'>float3</a> a, float b); +</td> + <td> </td> + </tr> + <tr> <td><a href='rs_value_types.html#android_rs:float3'>float3</a> min(<a href='rs_value_types.html#android_rs:float3'>float3</a> a, <a href='rs_value_types.html#android_rs:float3'>float3</a> b); </td> <td> </td> </tr> <tr> + <td><a href='rs_value_types.html#android_rs:float4'>float4</a> min(<a href='rs_value_types.html#android_rs:float4'>float4</a> a, float b); +</td> + <td> </td> + </tr> + <tr> <td><a href='rs_value_types.html#android_rs:float4'>float4</a> min(<a href='rs_value_types.html#android_rs:float4'>float4</a> a, <a href='rs_value_types.html#android_rs:float4'>float4</a> b); </td> <td> </td> diff --git a/docs/html/guide/topics/renderscript/reference/rs_value_types.jd b/docs/html/guide/topics/renderscript/reference/rs_value_types.jd index 85c7a5cfded2..d53caf92d74a 100644 --- a/docs/html/guide/topics/renderscript/reference/rs_value_types.jd +++ b/docs/html/guide/topics/renderscript/reference/rs_value_types.jd @@ -25,7 +25,7 @@ E.g. <a href='rs_value_types.html#android_rs:float4'>float4</a>, <a href='rs_val </p> <p> To create vector literals, use the vector type followed by the values enclosed -between parentheses, e.g. <code>(float3)(1.0f, 2.0f, 3.0f)</code>. +between curly braces, e.g. <code>(float3){1.0f, 2.0f, 3.0f}</code>. </p> <p> Entries of a vector can be accessed using different naming styles. diff --git a/docs/html/training/building-userinfo.jd b/docs/html/training/building-userinfo.jd index f9d77f762824..40e5b94c6d80 100644 --- a/docs/html/training/building-userinfo.jd +++ b/docs/html/training/building-userinfo.jd @@ -1,9 +1,9 @@ -page.title=Building Apps with User Info & Location +page.title=Building Apps with Contacts & Sign-In page.trainingcourse=true @jd:body -<p>These classes teach you how to add user personalization to your app. Some of the ways -you can do this is by identifying users, providing -information that's relevant to them, and providing information about the world around them.</p>
\ No newline at end of file +<p>These lessons teach you how to include contact information and authenticate users with the same +credentials they use for Google. These features allow your app to connect users with people they +care about and provide a personalized experience without creating new user accounts.</p> diff --git a/docs/html/training/contacts-provider/retrieve-names.jd b/docs/html/training/contacts-provider/retrieve-names.jd index 7106889a42c7..d97b81b1e340 100644 --- a/docs/html/training/contacts-provider/retrieve-names.jd +++ b/docs/html/training/contacts-provider/retrieve-names.jd @@ -731,7 +731,6 @@ public class ContactsFragment extends Fragment implements Define ListView and item layouts. </li> <li> - <li> Define a Fragment that displays the list of contacts. </li> <li> diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 7cffdd8f036d..296334520e50 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -811,7 +811,7 @@ include the action bar on devices running Android 2.1 or higher." <div class="nav-section-header"> <a href="<?cs var:toroot ?>training/building-userinfo.html"> <span class="small">Building Apps with</span><br/> - User Info & Sign-In + Contacts & Sign-In </a> </div> <ul> diff --git a/graphics/java/android/graphics/Interpolator.java b/graphics/java/android/graphics/Interpolator.java index f695a9ea2cf4..104546454fa9 100644 --- a/graphics/java/android/graphics/Interpolator.java +++ b/graphics/java/android/graphics/Interpolator.java @@ -147,11 +147,12 @@ public class Interpolator { @Override protected void finalize() throws Throwable { nativeDestructor(native_instance); + native_instance = 0; // Other finalizers can still call us. } private int mValueCount; private int mFrameCount; - private final long native_instance; + private long native_instance; private static native long nativeConstructor(int valueCount, int frameCount); private static native void nativeDestructor(long native_instance); diff --git a/graphics/java/android/graphics/MaskFilter.java b/graphics/java/android/graphics/MaskFilter.java index 27a7dda493da..d4743155729e 100644 --- a/graphics/java/android/graphics/MaskFilter.java +++ b/graphics/java/android/graphics/MaskFilter.java @@ -25,6 +25,7 @@ public class MaskFilter { protected void finalize() throws Throwable { nativeDestructor(native_instance); + native_instance = 0; // Other finalizers can still call us. } private static native void nativeDestructor(long native_filter); diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java index 90e5a4e487fa..1e8f11bbe3ed 100644 --- a/graphics/java/android/graphics/Matrix.java +++ b/graphics/java/android/graphics/Matrix.java @@ -827,6 +827,7 @@ public class Matrix { protected void finalize() throws Throwable { try { finalizer(native_instance); + native_instance = 0; // Other finalizers can still call us. } finally { super.finalize(); } diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index b9a1eda61fa2..5efc00c8fda5 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -71,7 +71,7 @@ public class NinePatch { * * @hide */ - public final long mNativeChunk; + public long mNativeChunk; private Paint mPaint; private String mSrcName; @@ -121,6 +121,7 @@ public class NinePatch { if (mNativeChunk != 0) { // only attempt to destroy correctly initilized chunks nativeFinalize(mNativeChunk); + mNativeChunk = 0; } } finally { super.finalize(); diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index ce35b871b8db..6582b7e9ef6b 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -2471,6 +2471,7 @@ public class Paint { protected void finalize() throws Throwable { try { finalizer(mNativePaint); + mNativePaint = 0; } finally { super.finalize(); } diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 91e704a901cb..da3deffdef40 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -27,7 +27,7 @@ public class Path { /** * @hide */ - public final long mNativePath; + public long mNativePath; /** * @hide @@ -746,6 +746,7 @@ public class Path { protected void finalize() throws Throwable { try { finalizer(mNativePath); + mNativePath = 0; // Other finalizers can still call us. } finally { super.finalize(); } diff --git a/graphics/java/android/graphics/PathEffect.java b/graphics/java/android/graphics/PathEffect.java index 617dfca6a966..3292501e6324 100644 --- a/graphics/java/android/graphics/PathEffect.java +++ b/graphics/java/android/graphics/PathEffect.java @@ -25,6 +25,7 @@ public class PathEffect { protected void finalize() throws Throwable { nativeDestructor(native_instance); + native_instance = 0; // Other finalizers can still call us. } private static native void nativeDestructor(long native_patheffect); diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java index 7cc97653c097..041615969d87 100644 --- a/graphics/java/android/graphics/PathMeasure.java +++ b/graphics/java/android/graphics/PathMeasure.java @@ -142,6 +142,7 @@ public class PathMeasure { protected void finalize() throws Throwable { native_destroy(native_instance); + native_instance = 0; // Other finalizers can still call us. } private static native long native_create(long native_path, boolean forceClosed); @@ -154,6 +155,6 @@ public class PathMeasure { private static native boolean native_nextContour(long native_instance); private static native void native_destroy(long native_instance); - /* package */private final long native_instance; + /* package */private long native_instance; } diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index 0e55089b795a..28d869063225 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -29,7 +29,7 @@ import java.io.OutputStream; */ public class Picture { private Canvas mRecordingCanvas; - private final long mNativePicture; + private long mNativePicture; private static final int WORKING_STREAM_STORAGE = 16 * 1024; @@ -60,6 +60,7 @@ public class Picture { protected void finalize() throws Throwable { try { nativeDestructor(mNativePicture); + mNativePicture = 0; } finally { super.finalize(); } diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java index 727723d7f675..de89ad07d873 100644 --- a/graphics/java/android/graphics/Region.java +++ b/graphics/java/android/graphics/Region.java @@ -30,7 +30,7 @@ public class Region implements Parcelable { /** * @hide */ - public final long mNativeRegion; + public long mNativeRegion; // the native values for these must match up with the enum in SkRegion.h public enum Op { @@ -380,6 +380,7 @@ public class Region implements Parcelable { protected void finalize() throws Throwable { try { nativeDestructor(mNativeRegion); + mNativeRegion = 0; } finally { super.finalize(); } diff --git a/graphics/java/android/graphics/RegionIterator.java b/graphics/java/android/graphics/RegionIterator.java index 8401adba09c4..443b23c1b5fc 100644 --- a/graphics/java/android/graphics/RegionIterator.java +++ b/graphics/java/android/graphics/RegionIterator.java @@ -43,12 +43,13 @@ public class RegionIterator { protected void finalize() throws Throwable { nativeDestructor(mNativeIter); + mNativeIter = 0; // Other finalizers can still call us. } private static native long nativeConstructor(long native_region); private static native void nativeDestructor(long native_iter); private static native boolean nativeNext(long native_iter, Rect r); - private final long mNativeIter; + private long mNativeIter; } diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index a96d2cbbe916..adb282fb4bf9 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -91,6 +91,7 @@ public class Shader { super.finalize(); } finally { nativeDestructor(native_instance); + native_instance = 0; // Other finalizers can still call us. } } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index db42314447ff..7eb5584a557a 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -358,6 +358,7 @@ public class Typeface { protected void finalize() throws Throwable { try { nativeUnref(native_instance); + native_instance = 0; // Other finalizers can still call us. } finally { super.finalize(); } diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h index 0cfd2b103d23..3d4e47d84c65 100644 --- a/include/androidfw/AssetManager.h +++ b/include/androidfw/AssetManager.h @@ -93,13 +93,14 @@ public: * look in multiple places for assets. It can be either a directory (for * finding assets as raw files on the disk) or a ZIP file. This newly * added asset path will be examined first when searching for assets, - * before any that were previously added. + * before any that were previously added, the assets are added as shared + * library if appAsLib is true. * * Returns "true" on success, "false" on failure. If 'cookie' is non-NULL, * then on success, *cookie is set to the value corresponding to the * newly-added asset source. */ - bool addAssetPath(const String8& path, int32_t* cookie); + bool addAssetPath(const String8& path, int32_t* cookie, bool appAsLib=false); bool addOverlayPath(const String8& path, int32_t* cookie); /* @@ -280,7 +281,7 @@ private: const ResTable* getResTable(bool required = true) const; void setLocaleLocked(const char* locale); void updateResourceParamsLocked() const; - bool appendPathToResTable(const asset_path& ap) const; + bool appendPathToResTable(const asset_path& ap, bool appAsLib=false) const; Asset* openIdmapLocked(const struct asset_path& ap) const; diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index eff1f5f4e7d4..49b6333e8a4d 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -1505,7 +1505,7 @@ struct ResTable_lib_entry class DynamicRefTable { public: - DynamicRefTable(uint8_t packageId); + DynamicRefTable(uint8_t packageId, bool appAsLib); // Loads an unmapped reference table from the package. status_t load(const ResTable_lib_header* const header); @@ -1530,6 +1530,7 @@ private: const uint8_t mAssignedPackageId; uint8_t mLookupTable[256]; KeyedVector<String16, uint8_t> mEntries; + bool mAppAsLib; }; bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue); @@ -1547,10 +1548,11 @@ public: status_t add(const void* data, size_t size, const int32_t cookie=-1, bool copyData=false); status_t add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, - const int32_t cookie=-1, bool copyData=false); + const int32_t cookie=-1, bool copyData=false, bool appAsLib=false); status_t add(Asset* asset, const int32_t cookie=-1, bool copyData=false); - status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false); + status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false, + bool appAsLib=false); status_t add(ResTable* src); status_t addEmpty(const int32_t cookie); @@ -1858,7 +1860,7 @@ private: typedef Vector<Type*> TypeList; status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, - const int32_t cookie, bool copyData); + bool appAsLib, const int32_t cookie, bool copyData); ssize_t getResourcePackageIndex(uint32_t resID) const; @@ -1871,7 +1873,7 @@ private: size_t nameLen, uint32_t* outTypeSpecFlags) const; status_t parsePackage( - const ResTable_package* const pkg, const Header* const header); + const ResTable_package* const pkg, const Header* const header, bool appAsLib); void print_value(const Package* pkg, const Res_value& value) const; diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index 5d777b0cb942..c8333c87c69c 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -217,13 +217,22 @@ public class Credentials { * Returns {@code true} if there was at least one of those types. */ public static boolean deleteAllTypesForAlias(KeyStore keystore, String alias) { + return deleteAllTypesForAlias(keystore, alias, KeyStore.UID_SELF); + } + + /** + * Delete all types (private key, certificate, CA certificate) for a + * particular {@code alias}. All three can exist for any given alias. + * Returns {@code true} if there was at least one of those types. + */ + public static boolean deleteAllTypesForAlias(KeyStore keystore, String alias, int uid) { /* * Make sure every type is deleted. There can be all three types, so * don't use a conditional here. */ - return keystore.delete(Credentials.USER_PRIVATE_KEY + alias) - | keystore.delete(Credentials.USER_SECRET_KEY + alias) - | deleteCertificateTypesForAlias(keystore, alias); + return keystore.delete(Credentials.USER_PRIVATE_KEY + alias, uid) + | keystore.delete(Credentials.USER_SECRET_KEY + alias, uid) + | deleteCertificateTypesForAlias(keystore, alias, uid); } /** @@ -232,12 +241,21 @@ public class Credentials { * Returns {@code true} if there was at least one of those types. */ public static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias) { + return deleteCertificateTypesForAlias(keystore, alias, KeyStore.UID_SELF); + } + + /** + * Delete all types (private key, certificate, CA certificate) for a + * particular {@code alias}. All three can exist for any given alias. + * Returns {@code true} if there was at least one of those types. + */ + public static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias, int uid) { /* * Make sure every certificate type is deleted. There can be two types, * so don't use a conditional here. */ - return keystore.delete(Credentials.USER_CERTIFICATE + alias) - | keystore.delete(Credentials.CA_CERTIFICATE + alias); + return keystore.delete(Credentials.USER_CERTIFICATE + alias, uid) + | keystore.delete(Credentials.CA_CERTIFICATE + alias, uid); } /** @@ -245,7 +263,15 @@ public class Credentials { * Returns {@code true} if an entry was was deleted. */ static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias) { - return keystore.delete(Credentials.USER_PRIVATE_KEY + alias); + return deletePrivateKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF); + } + + /** + * Delete private key for a particular {@code alias}. + * Returns {@code true} if an entry was was deleted. + */ + static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias, int uid) { + return keystore.delete(Credentials.USER_PRIVATE_KEY + alias, uid); } /** @@ -253,6 +279,14 @@ public class Credentials { * Returns {@code true} if an entry was was deleted. */ public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) { - return keystore.delete(Credentials.USER_SECRET_KEY + alias); + return deleteSecretKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF); + } + + /** + * Delete secret key for a particular {@code alias}. + * Returns {@code true} if an entry was was deleted. + */ + public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias, int uid) { + return keystore.delete(Credentials.USER_SECRET_KEY + alias, uid); } } diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 7de26d696538..5b2594dcc9e7 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -374,7 +374,7 @@ public final class KeyChain { throw new KeyChainException("keystore had a problem"); } return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( - KeyStore.getInstance(), keyId); + KeyStore.getInstance(), keyId, KeyStore.UID_SELF); } catch (RemoteException e) { throw new KeyChainException(e); } catch (RuntimeException e) { diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 98b44dc43144..1b87a419941d 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -155,15 +155,19 @@ public class KeyStore { return state() == State.UNLOCKED; } - public byte[] get(String key) { + public byte[] get(String key, int uid) { try { - return mBinder.get(key); + return mBinder.get(key, uid); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return null; } } + public byte[] get(String key) { + return get(key, UID_SELF); + } + public boolean put(String key, byte[] value, int uid, int flags) { return insert(key, value, uid, flags) == NO_ERROR; } @@ -348,9 +352,9 @@ public class KeyStore { * Returns the last modification time of the key in milliseconds since the * epoch. Will return -1L if the key could not be found or other error. */ - public long getmtime(String key) { + public long getmtime(String key, int uid) { try { - final long millis = mBinder.getmtime(key); + final long millis = mBinder.getmtime(key, uid); if (millis == -1L) { return -1L; } @@ -362,6 +366,10 @@ public class KeyStore { } } + public long getmtime(String key) { + return getmtime(key, UID_SELF); + } + public boolean duplicate(String srcKey, int srcUid, String destKey, int destUid) { try { return mBinder.duplicate(srcKey, srcUid, destKey, destUid) == NO_ERROR; @@ -423,15 +431,20 @@ public class KeyStore { } public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId, - KeyCharacteristics outCharacteristics) { + int uid, KeyCharacteristics outCharacteristics) { try { - return mBinder.getKeyCharacteristics(alias, clientId, appId, outCharacteristics); + return mBinder.getKeyCharacteristics(alias, clientId, appId, uid, outCharacteristics); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return SYSTEM_ERROR; } } + public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId, + KeyCharacteristics outCharacteristics) { + return getKeyCharacteristics(alias, clientId, appId, UID_SELF, outCharacteristics); + } + public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData, int uid, int flags, KeyCharacteristics outCharacteristics) { try { @@ -449,25 +462,34 @@ public class KeyStore { } public ExportResult exportKey(String alias, int format, KeymasterBlob clientId, - KeymasterBlob appId) { + KeymasterBlob appId, int uid) { try { - return mBinder.exportKey(alias, format, clientId, appId); + return mBinder.exportKey(alias, format, clientId, appId, uid); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return null; } } + public ExportResult exportKey(String alias, int format, KeymasterBlob clientId, + KeymasterBlob appId) { + return exportKey(alias, format, clientId, appId, UID_SELF); + } public OperationResult begin(String alias, int purpose, boolean pruneable, - KeymasterArguments args, byte[] entropy) { + KeymasterArguments args, byte[] entropy, int uid) { try { - return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy); + return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, uid); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return null; } } + public OperationResult begin(String alias, int purpose, boolean pruneable, + KeymasterArguments args, byte[] entropy) { + return begin(alias, purpose, pruneable, args, entropy, UID_SELF); + } + public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) { try { return mBinder.update(token, arguments, input); @@ -640,7 +662,7 @@ public class KeyStore { * {@link KeyStoreException}. */ public InvalidKeyException getInvalidKeyException( - String keystoreKeyAlias, KeyStoreException e) { + String keystoreKeyAlias, int uid, KeyStoreException e) { switch (e.getErrorCode()) { case LOCKED: return new UserNotAuthenticatedException(); @@ -658,7 +680,8 @@ public class KeyStore { // to authenticate. KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); int getKeyCharacteristicsErrorCode = - getKeyCharacteristics(keystoreKeyAlias, null, null, keyCharacteristics); + getKeyCharacteristics(keystoreKeyAlias, null, null, uid, + keyCharacteristics); if (getKeyCharacteristicsErrorCode != NO_ERROR) { return new InvalidKeyException( "Failed to obtained key characteristics", @@ -708,7 +731,8 @@ public class KeyStore { * Returns an {@link InvalidKeyException} corresponding to the provided keystore/keymaster error * code. */ - public InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) { - return getInvalidKeyException(keystoreKeyAlias, getKeyStoreException(errorCode)); + public InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int uid, + int errorCode) { + return getInvalidKeyException(keystoreKeyAlias, uid, getKeyStoreException(errorCode)); } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java index 38cacd0c43b4..042dc83d4aa9 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java @@ -249,7 +249,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor purpose, true, // permit aborting this operation if keystore runs out of resources keymasterInputArgs, - additionalEntropy); + additionalEntropy, + mKey.getUid()); if (opResult == null) { throw new KeyStoreConnectException(); } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java index 10aab7e59ff5..45f2110e0c77 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java @@ -155,9 +155,9 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); int errorCode = getKeyStore().getKeyCharacteristics( - key.getAlias(), null, null, keyCharacteristics); + key.getAlias(), null, null, key.getUid(), keyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { - throw getKeyStore().getInvalidKeyException(key.getAlias(), errorCode); + throw getKeyStore().getInvalidKeyException(key.getAlias(), key.getUid(), errorCode); } long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1); if (keySizeBits == -1) { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java index 5dbcd681fe8f..aa7bdffb9e07 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java @@ -28,8 +28,8 @@ import java.security.spec.ECParameterSpec; public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey { private final ECParameterSpec mParams; - public AndroidKeyStoreECPrivateKey(String alias, ECParameterSpec params) { - super(alias, KeyProperties.KEY_ALGORITHM_EC); + public AndroidKeyStoreECPrivateKey(String alias, int uid, ECParameterSpec params) { + super(alias, uid, KeyProperties.KEY_ALGORITHM_EC); mParams = params; } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreECPublicKey.java index 3ed396dedeb9..2efaeb6545a0 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreECPublicKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreECPublicKey.java @@ -30,15 +30,15 @@ public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey impleme private final ECParameterSpec mParams; private final ECPoint mW; - public AndroidKeyStoreECPublicKey(String alias, byte[] x509EncodedForm, ECParameterSpec params, + public AndroidKeyStoreECPublicKey(String alias, int uid, byte[] x509EncodedForm, ECParameterSpec params, ECPoint w) { - super(alias, KeyProperties.KEY_ALGORITHM_EC, x509EncodedForm); + super(alias, uid, KeyProperties.KEY_ALGORITHM_EC, x509EncodedForm); mParams = params; mW = w; } - public AndroidKeyStoreECPublicKey(String alias, ECPublicKey info) { - this(alias, info.getEncoded(), info.getParams(), info.getW()); + public AndroidKeyStoreECPublicKey(String alias, int uid, ECPublicKey info) { + this(alias, uid, info.getEncoded(), info.getParams(), info.getW()); if (!"X.509".equalsIgnoreCase(info.getFormat())) { throw new IllegalArgumentException( "Unsupported key export format: " + info.getFormat()); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java index d20e3afd2672..2e8ac3236463 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java @@ -168,7 +168,8 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC KeymasterDefs.KM_PURPOSE_SIGN, true, keymasterArgs, - null); // no additional entropy needed for HMAC because it's deterministic + null, // no additional entropy needed for HMAC because it's deterministic + mKey.getUid()); if (opResult == null) { throw new KeyStoreConnectException(); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java index e76802f1dca5..e8e63105925f 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java @@ -25,10 +25,12 @@ import java.security.Key; */ public class AndroidKeyStoreKey implements Key { private final String mAlias; + private final int mUid; private final String mAlgorithm; - public AndroidKeyStoreKey(String alias, String algorithm) { + public AndroidKeyStoreKey(String alias, int uid, String algorithm) { mAlias = alias; + mUid = uid; mAlgorithm = algorithm; } @@ -36,6 +38,10 @@ public class AndroidKeyStoreKey implements Key { return mAlias; } + int getUid() { + return mUid; + } + @Override public String getAlgorithm() { return mAlgorithm; @@ -59,6 +65,7 @@ public class AndroidKeyStoreKey implements Key { int result = 1; result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode()); result = prime * result + ((mAlias == null) ? 0 : mAlias.hashCode()); + result = prime * result + mUid; return result; } @@ -88,6 +95,9 @@ public class AndroidKeyStoreKey implements Key { } else if (!mAlias.equals(other.mAlias)) { return false; } + if (mUid != other.mUid) { + return false; + } return true; } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java index 5ce4fd2cde2d..303b0f2c05c2 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java @@ -62,7 +62,8 @@ public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { "Unsupported key type: " + key.getClass().getName() + ". KeyInfo can be obtained only for Android Keystore private keys"); } - String keyAliasInKeystore = ((AndroidKeyStorePrivateKey) key).getAlias(); + AndroidKeyStorePrivateKey keystorePrivateKey = (AndroidKeyStorePrivateKey) key; + String keyAliasInKeystore = keystorePrivateKey.getAlias(); String entryAlias; if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) { entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length()); @@ -71,7 +72,7 @@ public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { } @SuppressWarnings("unchecked") T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo( - mKeyStore, entryAlias, keyAliasInKeystore); + mKeyStore, entryAlias, keyAliasInKeystore, keystorePrivateKey.getUid()); return result; } else if (X509EncodedKeySpec.class.equals(keySpecClass)) { if (!(key instanceof AndroidKeyStorePublicKey)) { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index 4c174f13a27a..e6276a46bc3a 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -297,11 +297,12 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); boolean success = false; try { - Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias()); + Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias(), spec.getUid()); int errorCode = mKeyStore.generateKey( keyAliasInKeystore, args, additionalEntropy, + spec.getUid(), flags, resultingKeyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { @@ -315,12 +316,14 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { } catch (IllegalArgumentException e) { throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); } - SecretKey result = new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); + SecretKey result = new AndroidKeyStoreSecretKey( + keyAliasInKeystore, spec.getUid(), keyAlgorithmJCA); success = true; return result; } finally { if (!success) { - Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias()); + Credentials.deleteAllTypesForAlias( + mKeyStore, spec.getKeystoreAlias(), spec.getUid()); } } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 79095f42cd06..65460b5ceb29 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -147,6 +147,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private KeyGenParameterSpec mSpec; private String mEntryAlias; + private int mEntryUid; private boolean mEncryptionAtRestRequired; private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm; private int mKeymasterAlgorithm = -1; @@ -283,6 +284,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } mEntryAlias = spec.getKeystoreAlias(); + mEntryUid = spec.getUid(); mSpec = spec; mKeymasterAlgorithm = keymasterAlgorithm; mEncryptionAtRestRequired = encryptionAtRestRequired; @@ -352,6 +354,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private void resetAll() { mEntryAlias = null; + mEntryUid = KeyStore.UID_SELF; mJcaKeyAlgorithm = null; mKeymasterAlgorithm = -1; mKeymasterPurposes = null; @@ -470,12 +473,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias; boolean success = false; try { - Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias); + Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); int errorCode = mKeyStore.generateKey( privateKeyAlias, args, additionalEntropy, + mEntryUid, flags, resultingKeyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { @@ -486,7 +490,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato KeyPair result; try { result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( - mKeyStore, privateKeyAlias); + mKeyStore, privateKeyAlias, mEntryUid); } catch (UnrecoverableKeyException e) { throw new ProviderException("Failed to load generated key pair from keystore", e); } @@ -515,7 +519,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato int insertErrorCode = mKeyStore.insert( Credentials.USER_CERTIFICATE + mEntryAlias, certBytes, - KeyStore.UID_SELF, + mEntryUid, flags); if (insertErrorCode != KeyStore.NO_ERROR) { throw new ProviderException("Failed to store self-signed certificate", @@ -526,7 +530,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato return result; } finally { if (!success) { - Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias); + Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); } } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore/AndroidKeyStoreLoadStoreParameter.java new file mode 100644 index 000000000000..45d579e371c6 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreLoadStoreParameter.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import java.security.KeyStore; +import java.security.KeyStore.ProtectionParameter; + +class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter { + + private final int mUid; + + AndroidKeyStoreLoadStoreParameter(int uid) { + mUid = uid; + } + + @Override + public ProtectionParameter getProtectionParameter() { + return null; + } + + int getUid() { + return mUid; + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java index b586ad4f921c..06e4c88fa632 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java @@ -25,7 +25,7 @@ import java.security.PrivateKey; */ public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey { - public AndroidKeyStorePrivateKey(String alias, String algorithm) { - super(alias, algorithm); + public AndroidKeyStorePrivateKey(String alias, int uid, String algorithm) { + super(alias, uid, algorithm); } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index ba39ba70f4d3..c31a8b7bf2bf 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -22,15 +22,19 @@ import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterDefs; +import java.io.IOException; import java.security.KeyFactory; import java.security.KeyPair; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.Provider; import java.security.ProviderException; import java.security.PublicKey; import java.security.Security; import java.security.Signature; import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.security.interfaces.ECKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAKey; @@ -167,6 +171,7 @@ public class AndroidKeyStoreProvider extends Provider { @NonNull public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey( @NonNull String alias, + int uid, @NonNull @KeyProperties.KeyAlgorithmEnum String keyAlgorithm, @NonNull byte[] x509EncodedForm) { PublicKey publicKey; @@ -180,9 +185,9 @@ public class AndroidKeyStoreProvider extends Provider { throw new ProviderException("Invalid X.509 encoding of public key", e); } if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { - return new AndroidKeyStoreECPublicKey(alias, (ECPublicKey) publicKey); + return new AndroidKeyStoreECPublicKey(alias, uid, (ECPublicKey) publicKey); } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { - return new AndroidKeyStoreRSAPublicKey(alias, (RSAPublicKey) publicKey); + return new AndroidKeyStoreRSAPublicKey(alias, uid, (RSAPublicKey) publicKey); } else { throw new ProviderException("Unsupported Android Keystore public key algorithm: " + keyAlgorithm); @@ -195,10 +200,10 @@ public class AndroidKeyStoreProvider extends Provider { String keyAlgorithm = publicKey.getAlgorithm(); if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { return new AndroidKeyStoreECPrivateKey( - publicKey.getAlias(), ((ECKey) publicKey).getParams()); + publicKey.getAlias(), publicKey.getUid(), ((ECKey) publicKey).getParams()); } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { return new AndroidKeyStoreRSAPrivateKey( - publicKey.getAlias(), ((RSAKey) publicKey).getModulus()); + publicKey.getAlias(), publicKey.getUid(), ((RSAKey) publicKey).getModulus()); } else { throw new ProviderException("Unsupported Android Keystore public key algorithm: " + keyAlgorithm); @@ -207,18 +212,18 @@ public class AndroidKeyStoreProvider extends Provider { @NonNull public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias) + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) throws UnrecoverableKeyException { KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); int errorCode = keyStore.getKeyCharacteristics( - privateKeyAlias, null, null, keyCharacteristics); + privateKeyAlias, null, null, uid, keyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Failed to obtain information about private key") .initCause(KeyStore.getKeyStoreException(errorCode)); } ExportResult exportResult = keyStore.exportKey( - privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null); + privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null, uid); if (exportResult.resultCode != KeyStore.NO_ERROR) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Failed to obtain X.509 form of public key") @@ -242,15 +247,15 @@ public class AndroidKeyStoreProvider extends Provider { } return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( - privateKeyAlias, jcaKeyAlgorithm, x509EncodedPublicKey); + privateKeyAlias, uid, jcaKeyAlgorithm, x509EncodedPublicKey); } @NonNull public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias) + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) throws UnrecoverableKeyException { AndroidKeyStorePublicKey publicKey = - loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias); + loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid); AndroidKeyStorePrivateKey privateKey = AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey); return new KeyPair(publicKey, privateKey); @@ -258,19 +263,19 @@ public class AndroidKeyStoreProvider extends Provider { @NonNull public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore( - @NonNull KeyStore keyStore, @NonNull String privateKeyAlias) + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid) throws UnrecoverableKeyException { - KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias); + KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid); return (AndroidKeyStorePrivateKey) keyPair.getPrivate(); } @NonNull public static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore( - @NonNull KeyStore keyStore, @NonNull String secretKeyAlias) + @NonNull KeyStore keyStore, @NonNull String secretKeyAlias, int uid) throws UnrecoverableKeyException { KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); int errorCode = keyStore.getKeyCharacteristics( - secretKeyAlias, null, null, keyCharacteristics); + secretKeyAlias, null, null, uid, keyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Failed to obtain information about key") @@ -301,6 +306,29 @@ public class AndroidKeyStoreProvider extends Provider { new UnrecoverableKeyException("Unsupported secret key type").initCause(e); } - return new AndroidKeyStoreSecretKey(secretKeyAlias, keyAlgorithmString); + return new AndroidKeyStoreSecretKey(secretKeyAlias, uid, keyAlgorithmString); + } + + /** + * Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID. + * The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID + * access is permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN) + * all of which are system. + * + * <p>Note: the returned {@code KeyStore} is already initialized/loaded. Thus, there is + * no need to invoke {@code load} on it. + */ + @NonNull + public static java.security.KeyStore getKeyStoreForUid(int uid) + throws KeyStoreException, NoSuchProviderException { + java.security.KeyStore result = + java.security.KeyStore.getInstance("AndroidKeyStore", PROVIDER_NAME); + try { + result.load(new AndroidKeyStoreLoadStoreParameter(uid)); + } catch (NoSuchAlgorithmException | CertificateException | IOException e) { + throw new KeyStoreException( + "Failed to load AndroidKeyStore KeyStore for UID " + uid, e); + } + return result; } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java index 9fea30d23a00..4194780906b7 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java @@ -28,8 +28,8 @@ public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements Publ private final byte[] mEncoded; - public AndroidKeyStorePublicKey(String alias, String algorithm, byte[] x509EncodedForm) { - super(alias, algorithm); + public AndroidKeyStorePublicKey(String alias, int uid, String algorithm, byte[] x509EncodedForm) { + super(alias, uid, algorithm); mEncoded = ArrayUtils.cloneIfNotEmpty(x509EncodedForm); } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java index 56cc44cc7cc1..2ae68fa80117 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java @@ -415,9 +415,10 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); int errorCode = getKeyStore().getKeyCharacteristics( - keystoreKey.getAlias(), null, null, keyCharacteristics); + keystoreKey.getAlias(), null, null, keystoreKey.getUid(), keyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { - throw getKeyStore().getInvalidKeyException(keystoreKey.getAlias(), errorCode); + throw getKeyStore().getInvalidKeyException( + keystoreKey.getAlias(), keystoreKey.getUid(), errorCode); } long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1); if (keySizeBits == -1) { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java index 179ffd8c1efd..adb39222e076 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java @@ -29,8 +29,8 @@ public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey impl private final BigInteger mModulus; - public AndroidKeyStoreRSAPrivateKey(String alias, BigInteger modulus) { - super(alias, KeyProperties.KEY_ALGORITHM_RSA); + public AndroidKeyStoreRSAPrivateKey(String alias, int uid, BigInteger modulus) { + super(alias, uid, KeyProperties.KEY_ALGORITHM_RSA); mModulus = modulus; } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java index 08a173e4a0b0..d85aaceb98e3 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java @@ -28,15 +28,15 @@ public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implem private final BigInteger mModulus; private final BigInteger mPublicExponent; - public AndroidKeyStoreRSAPublicKey(String alias, byte[] x509EncodedForm, BigInteger modulus, + public AndroidKeyStoreRSAPublicKey(String alias, int uid, byte[] x509EncodedForm, BigInteger modulus, BigInteger publicExponent) { - super(alias, KeyProperties.KEY_ALGORITHM_RSA, x509EncodedForm); + super(alias, uid, KeyProperties.KEY_ALGORITHM_RSA, x509EncodedForm); mModulus = modulus; mPublicExponent = publicExponent; } - public AndroidKeyStoreRSAPublicKey(String alias, RSAPublicKey info) { - this(alias, info.getEncoded(), info.getModulus(), info.getPublicExponent()); + public AndroidKeyStoreRSAPublicKey(String alias, int uid, RSAPublicKey info) { + this(alias, uid, info.getEncoded(), info.getModulus(), info.getPublicExponent()); if (!"X.509".equalsIgnoreCase(info.getFormat())) { throw new IllegalArgumentException( "Unsupported key export format: " + info.getFormat()); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java index af354ab560ba..b8e6af7d936e 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java @@ -25,7 +25,7 @@ import javax.crypto.SecretKey; */ public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey { - public AndroidKeyStoreSecretKey(String alias, String algorithm) { - super(alias, algorithm); + public AndroidKeyStoreSecretKey(String alias, int uid, String algorithm) { + super(alias, uid, algorithm); } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index 11c22a9f827a..8d606bf97d0c 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -59,7 +59,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { if (!KeyInfo.class.equals(keySpecClass)) { throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); } - String keyAliasInKeystore = ((AndroidKeyStoreKey) key).getAlias(); + AndroidKeyStoreKey keystoreKey = (AndroidKeyStoreKey) key; + String keyAliasInKeystore = keystoreKey.getAlias(); String entryAlias; if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); @@ -67,13 +68,14 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); } - return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore); + return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore, keystoreKey.getUid()); } - static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore) { + static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore, + int keyUid) { KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); - int errorCode = - keyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics); + int errorCode = keyStore.getKeyCharacteristics( + keyAliasInKeystore, null, null, keyUid, keyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { throw new ProviderException("Failed to obtain information about key." + " Keystore error: " + errorCode); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java index 76240dd06265..da47b6bde69a 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java @@ -204,8 +204,8 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY, true, // permit aborting this operation if keystore runs out of resources keymasterInputArgs, - null // no additional entropy for begin -- only finish might need some - ); + null, // no additional entropy for begin -- only finish might need some + mKey.getUid()); if (opResult == null) { throw new KeyStoreConnectException(); } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index d300a9297054..cdcc7a2db5b2 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -17,7 +17,6 @@ package android.security.keystore; import libcore.util.EmptyArray; - import android.security.Credentials; import android.security.KeyStore; import android.security.KeyStoreParameter; @@ -34,6 +33,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.Key; import java.security.KeyStore.Entry; +import java.security.KeyStore.LoadStoreParameter; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStore.ProtectionParameter; import java.security.KeyStore.SecretKeyEntry; @@ -84,6 +84,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { public static final String NAME = "AndroidKeyStore"; private KeyStore mKeyStore; + private int mUid = KeyStore.UID_SELF; @Override public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, @@ -91,11 +92,11 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { if (isPrivateKeyEntry(alias)) { String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( - mKeyStore, privateKeyAlias); + mKeyStore, privateKeyAlias, mUid); } else if (isSecretKeyEntry(alias)) { String secretKeyAlias = Credentials.USER_SECRET_KEY + alias; return AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore( - mKeyStore, secretKeyAlias); + mKeyStore, secretKeyAlias, mUid); } else { // Key not found return null; @@ -115,7 +116,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { final Certificate[] caList; - final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); + final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid); if (caBytes != null) { final Collection<X509Certificate> caChain = toCertificates(caBytes); @@ -141,12 +142,12 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new NullPointerException("alias == null"); } - byte[] encodedCert = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); + byte[] encodedCert = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid); if (encodedCert != null) { return getCertificateForPrivateKeyEntry(alias, encodedCert); } - encodedCert = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); + encodedCert = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid); if (encodedCert != null) { return getCertificateForTrustedCertificateEntry(encodedCert); } @@ -183,13 +184,13 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - if (mKeyStore.contains(privateKeyAlias)) { + if (mKeyStore.contains(privateKeyAlias, mUid)) { // As expected, keystore contains the private key corresponding to this public key. Wrap // the certificate so that its getPublicKey method returns an Android Keystore // PublicKey. This key will delegate crypto operations involving this public key to // Android Keystore when higher-priority providers do not offer these crypto // operations for this key. - return wrapIntoKeyStoreCertificate(privateKeyAlias, cert); + return wrapIntoKeyStoreCertificate(privateKeyAlias, mUid, cert); } else { // This KeyStore entry/alias is supposed to contain the private key corresponding to // the public key in this certificate, but it does not for some reason. It's probably a @@ -206,9 +207,9 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { * find out which key alias to use. These operations cannot work without an alias. */ private static KeyStoreX509Certificate wrapIntoKeyStoreCertificate( - String privateKeyAlias, X509Certificate certificate) { + String privateKeyAlias, int uid, X509Certificate certificate) { return (certificate != null) - ? new KeyStoreX509Certificate(privateKeyAlias, certificate) : null; + ? new KeyStoreX509Certificate(privateKeyAlias, uid, certificate) : null; } private static X509Certificate toCertificate(byte[] bytes) { @@ -235,7 +236,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } private Date getModificationDate(String alias) { - final long epochMillis = mKeyStore.getmtime(alias); + final long epochMillis = mKeyStore.getmtime(alias, mUid); if (epochMillis == -1L) { return null; } @@ -516,13 +517,14 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { if (shouldReplacePrivateKey) { // Delete the stored private key and any related entries before importing the // provided key - Credentials.deleteAllTypesForAlias(mKeyStore, alias); + Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); int errorCode = mKeyStore.importKey( Credentials.USER_PRIVATE_KEY + alias, importArgs, KeymasterDefs.KM_KEY_FORMAT_PKCS8, pkcs8EncodedPrivateKeyBytes, + mUid, flags, resultingKeyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { @@ -531,13 +533,13 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } } else { // Keep the stored private key around -- delete all other entry types - Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); - Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid); + Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias, mUid); } // Store the leaf certificate int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes, - KeyStore.UID_SELF, flags); + mUid, flags); if (errorCode != KeyStore.NO_ERROR) { throw new KeyStoreException("Failed to store certificate #0", KeyStore.getKeyStoreException(errorCode)); @@ -545,7 +547,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { // Store the certificate chain errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes, - KeyStore.UID_SELF, flags); + mUid, flags); if (errorCode != KeyStore.NO_ERROR) { throw new KeyStoreException("Failed to store certificate chain", KeyStore.getKeyStoreException(errorCode)); @@ -554,10 +556,10 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } finally { if (!success) { if (shouldReplacePrivateKey) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); + Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); } else { - Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); - Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid); + Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias, mUid); } } } @@ -712,13 +714,14 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new KeyStoreException(e); } - Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias); + Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias, mUid); String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias; int errorCode = mKeyStore.importKey( keyAliasInKeystore, args, KeymasterDefs.KM_KEY_FORMAT_RAW, keyMaterial, + mUid, 0, // flags new KeyCharacteristics()); if (errorCode != KeyStore.NO_ERROR) { @@ -751,8 +754,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new KeyStoreException(e); } - if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, - KeyStore.UID_SELF, KeyStore.FLAG_NONE)) { + if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, mUid, KeyStore.FLAG_NONE)) { throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); } } @@ -764,13 +766,13 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } // At least one entry corresponding to this alias exists in keystore - if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { + if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid)) { throw new KeyStoreException("Failed to delete entry: " + alias); } } private Set<String> getUniqueAliases() { - final String[] rawAliases = mKeyStore.list(""); + final String[] rawAliases = mKeyStore.list("", mUid); if (rawAliases == null) { return new HashSet<String>(); } @@ -800,10 +802,10 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new NullPointerException("alias == null"); } - return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias) - || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias) - || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias) - || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); + return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) + || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid) + || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias, mUid) + || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid); } @Override @@ -825,7 +827,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new NullPointerException("alias == null"); } - return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias); + return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid); } private boolean isSecretKeyEntry(String alias) { @@ -833,7 +835,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new NullPointerException("alias == null"); } - return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias); + return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid); } private boolean isCertificateEntry(String alias) { @@ -841,7 +843,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new NullPointerException("alias == null"); } - return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); + return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid); } @Override @@ -876,10 +878,10 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { * equivalent to the USER_CERTIFICATE prefix for the Android keystore * convention. */ - final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE); + final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE, mUid); if (certAliases != null) { for (String alias : certAliases) { - final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); + final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid); if (certBytes == null) { continue; } @@ -896,14 +898,14 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { * Look at all the TrustedCertificateEntry types. Skip all the * PrivateKeyEntry we looked at above. */ - final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE); + final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE, mUid); if (certAliases != null) { for (String alias : caAliases) { if (nonCaEntries.contains(alias)) { continue; } - final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); + final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid); if (certBytes == null) { continue; } @@ -936,6 +938,23 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { // Unfortunate name collision. mKeyStore = KeyStore.getInstance(); + mUid = KeyStore.UID_SELF; + } + + @Override + public void engineLoad(LoadStoreParameter param) throws IOException, + NoSuchAlgorithmException, CertificateException { + int uid = KeyStore.UID_SELF; + if (param != null) { + if (param instanceof AndroidKeyStoreLoadStoreParameter) { + uid = ((AndroidKeyStoreLoadStoreParameter) param).getUid(); + } else { + throw new IllegalArgumentException( + "Unsupported param type: " + param.getClass()); + } + } + mKeyStore = KeyStore.getInstance(); + mUid = uid; } @Override @@ -945,7 +964,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new KeyStoreException("entry == null"); } - Credentials.deleteAllTypesForAlias(mKeyStore, alias); + Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid); if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { java.security.KeyStore.TrustedCertificateEntry trE = @@ -976,16 +995,20 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { */ static class KeyStoreX509Certificate extends DelegatingX509Certificate { private final String mPrivateKeyAlias; - KeyStoreX509Certificate(String privateKeyAlias, X509Certificate delegate) { + private final int mPrivateKeyUid; + KeyStoreX509Certificate(String privateKeyAlias, int privateKeyUid, + X509Certificate delegate) { super(delegate); mPrivateKeyAlias = privateKeyAlias; + mPrivateKeyUid = privateKeyUid; } @Override public PublicKey getPublicKey() { PublicKey original = super.getPublicKey(); return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( - mPrivateKeyAlias, original.getAlgorithm(), original.getEncoded()); + mPrivateKeyAlias, mPrivateKeyUid, + original.getAlgorithm(), original.getEncoded()); } } } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index f42d75081899..add199f139a0 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.KeyguardManager; import android.hardware.fingerprint.FingerprintManager; +import android.security.KeyStore; import android.text.TextUtils; import java.math.BigInteger; @@ -231,6 +232,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048 private final String mKeystoreAlias; + private final int mUid; private final int mKeySize; private final AlgorithmParameterSpec mSpec; private final X500Principal mCertificateSubject; @@ -254,6 +256,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { */ public KeyGenParameterSpec( String keyStoreAlias, + int uid, int keySize, AlgorithmParameterSpec spec, X500Principal certificateSubject, @@ -293,6 +296,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } mKeystoreAlias = keyStoreAlias; + mUid = uid; mKeySize = keySize; mSpec = spec; mCertificateSubject = certificateSubject; @@ -323,6 +327,16 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } /** + * Returns the UID which will own the key. {@code -1} is an alias for the UID of the current + * process. + * + * @hide + */ + public int getUid() { + return mUid; + } + + /** * Returns the requested key size. If {@code -1}, the size should be looked up from * {@link #getAlgorithmParameterSpec()}, if provided, otherwise an algorithm-specific default * size should be used. @@ -531,6 +545,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { private final String mKeystoreAlias; private @KeyProperties.PurposeEnum int mPurposes; + private int mUid = KeyStore.UID_SELF; private int mKeySize = -1; private AlgorithmParameterSpec mSpec; private X500Principal mCertificateSubject; @@ -575,6 +590,19 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } /** + * Sets the UID which will own the key. + * + * @param uid UID or {@code -1} for the UID of the current process. + * + * @hide + */ + @NonNull + public Builder setUid(int uid) { + mUid = uid; + return this; + } + + /** * Sets the size (in bits) of the key to be generated. For instance, for RSA keys this sets * the modulus size, for EC keys this selects a curve with a matching field size, and for * symmetric keys this sets the size of the bitstring which is their key material. @@ -936,6 +964,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { public KeyGenParameterSpec build() { return new KeyGenParameterSpec( mKeystoreAlias, + mUid, mKeySize, mSpec, mCertificateSubject, diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java index 27c1b2ac2182..773729e7e7df 100644 --- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java @@ -51,7 +51,7 @@ abstract class KeyStoreCryptoOperationUtils { // An error occured. However, some errors should not lead to init throwing an exception. // See below. InvalidKeyException e = - keyStore.getInvalidKeyException(key.getAlias(), beginOpResultCode); + keyStore.getInvalidKeyException(key.getAlias(), key.getUid(), beginOpResultCode); switch (beginOpResultCode) { case KeyStore.OP_AUTH_NEEDED: // Operation needs to be authorized by authenticating the user. Don't throw an diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java index e5c15c50377b..1af0b7d4212a 100644 --- a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java +++ b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java @@ -384,6 +384,7 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { pubKey, AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( Credentials.USER_PRIVATE_KEY + alias, + KeyStore.UID_SELF, x509userCert.getPublicKey().getAlgorithm(), x509userCert.getPublicKey().getEncoded())); diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java index c3b731b19010..aa718dca168e 100644 --- a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java +++ b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java @@ -1918,7 +1918,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; KeyPair keyPair = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( - keyStore, privateKeyAlias); + keyStore, privateKeyAlias, KeyStore.UID_SELF); final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setPublicKey(keyPair.getPublic()); diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 623ea896626b..8a03b94492d8 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -176,7 +176,7 @@ AssetManager::~AssetManager(void) delete[] mVendor; } -bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) +bool AssetManager::addAssetPath(const String8& path, int32_t* cookie, bool appAsLib) { AutoMutex _l(mLock); @@ -238,7 +238,7 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) #endif if (mResources != NULL) { - appendPathToResTable(ap); + appendPathToResTable(ap, appAsLib); } return true; @@ -610,7 +610,7 @@ FileType AssetManager::getFileType(const char* fileName) return kFileTypeRegular; } -bool AssetManager::appendPathToResTable(const asset_path& ap) const { +bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const { // skip those ap's that correspond to system overlays if (ap.isSystemOverlay) { return true; @@ -685,7 +685,7 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const { mResources->add(sharedRes); } else { ALOGV("Parsing resources for %s", ap.path.string()); - mResources->add(ass, idmap, nextEntryIdx + 1, !shared); + mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib); } onlyEmptyResources = false; diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 37de89ac53b6..21b543eefa01 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -3080,13 +3080,13 @@ struct ResTable::Package // table that defined the package); the ones after are skins on top of it. struct ResTable::PackageGroup { - PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id) + PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id, bool appAsLib) : owner(_owner) , name(_name) , id(_id) , largestTypeId(0) , bags(NULL) - , dynamicRefTable(static_cast<uint8_t>(_id)) + , dynamicRefTable(static_cast<uint8_t>(_id), appAsLib) { } ~PackageGroup() { @@ -3532,7 +3532,7 @@ ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool cop { memset(&mParams, 0, sizeof(mParams)); memset(mPackageMap, 0, sizeof(mPackageMap)); - addInternal(data, size, NULL, 0, cookie, copyData); + addInternal(data, size, NULL, 0, false, cookie, copyData); LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table"); if (kDebugTableSuperNoisy) { ALOGI("Creating ResTable %p\n", this); @@ -3553,12 +3553,12 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const } status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) { - return addInternal(data, size, NULL, 0, cookie, copyData); + return addInternal(data, size, NULL, 0, false, cookie, copyData); } status_t ResTable::add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, - const int32_t cookie, bool copyData) { - return addInternal(data, size, idmapData, idmapDataSize, cookie, copyData); + const int32_t cookie, bool copyData, bool appAsLib) { + return addInternal(data, size, idmapData, idmapDataSize, appAsLib, cookie, copyData); } status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) { @@ -3568,10 +3568,12 @@ status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) { return UNKNOWN_ERROR; } - return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, 0, cookie, copyData); + return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, false, 0, cookie, + copyData); } -status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) { +status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData, + bool appAsLib) { const void* data = asset->getBuffer(true); if (data == NULL) { ALOGW("Unable to get buffer of resource asset file"); @@ -3590,7 +3592,7 @@ status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bo } return addInternal(data, static_cast<size_t>(asset->getLength()), - idmapData, idmapSize, cookie, copyData); + idmapData, idmapSize, appAsLib, cookie, copyData); } status_t ResTable::add(ResTable* src) @@ -3603,7 +3605,7 @@ status_t ResTable::add(ResTable* src) for (size_t i=0; i<src->mPackageGroups.size(); i++) { PackageGroup* srcPg = src->mPackageGroups[i]; - PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id); + PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id, false); for (size_t j=0; j<srcPg->packages.size(); j++) { pg->packages.add(srcPg->packages[j]); } @@ -3644,7 +3646,7 @@ status_t ResTable::addEmpty(const int32_t cookie) { } status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize, - const int32_t cookie, bool copyData) + bool appAsLib, const int32_t cookie, bool copyData) { if (!data) { return NO_ERROR; @@ -3747,7 +3749,7 @@ status_t ResTable::addInternal(const void* data, size_t dataSize, const void* id return (mError=BAD_TYPE); } - if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) { + if (parsePackage((ResTable_package*)chunk, header, appAsLib) != NO_ERROR) { return mError; } curPackage++; @@ -5935,7 +5937,7 @@ status_t ResTable::getEntry( } status_t ResTable::parsePackage(const ResTable_package* const pkg, - const Header* const header) + const Header* const header, bool appAsLib) { const uint8_t* base = (const uint8_t*)pkg; status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset), @@ -5983,7 +5985,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, if (id >= 256) { LOG_ALWAYS_FATAL("Package id out of range"); return NO_ERROR; - } else if (id == 0) { + } else if (id == 0 || appAsLib) { // This is a library so assign an ID id = mNextPackageId++; } @@ -6016,7 +6018,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])]; strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0])); - group = new PackageGroup(this, String16(tmpName), id); + group = new PackageGroup(this, String16(tmpName), id, appAsLib); if (group == NULL) { delete package; return (mError=NO_MEMORY); @@ -6228,8 +6230,9 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, return NO_ERROR; } -DynamicRefTable::DynamicRefTable(uint8_t packageId) +DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib) : mAssignedPackageId(packageId) + , mAppAsLib(appAsLib) { memset(mLookupTable, 0, sizeof(mLookupTable)); @@ -6314,16 +6317,18 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { uint32_t res = *resId; size_t packageId = Res_GETPACKAGE(res) + 1; - if (packageId == APP_PACKAGE_ID) { + if (packageId == APP_PACKAGE_ID && !mAppAsLib) { // No lookup needs to be done, app package IDs are absolute. return NO_ERROR; } - if (packageId == 0) { + if (packageId == 0 || (packageId == APP_PACKAGE_ID && mAppAsLib)) { // The package ID is 0x00. That means that a shared library is accessing - // its own local resource, so we fix up the resource with the calling - // package ID. - *resId |= ((uint32_t) mAssignedPackageId) << 24; + // its own local resource. + // Or if app resource is loaded as shared library, the resource which has + // app package Id is local resources. + // so we fix up those resources with the calling package ID. + *resId = (0xFFFFFF & (*resId)) | (((uint32_t) mAssignedPackageId) << 24); return NO_ERROR; } @@ -6345,7 +6350,10 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { } status_t DynamicRefTable::lookupResourceValue(Res_value* value) const { - if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE) { + if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE && + (value->dataType != Res_value::TYPE_REFERENCE || !mAppAsLib)) { + // If the package is loaded as shared library, the resource reference + // also need to be fixed. return NO_ERROR; } diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk index a353575b4073..2bc026b79ca2 100644 --- a/libs/androidfw/tests/Android.mk +++ b/libs/androidfw/tests/Android.mk @@ -21,6 +21,7 @@ LOCAL_PATH:= $(call my-dir) testFiles := \ + AppAsLib_test.cpp \ AttributeFinder_test.cpp \ ByteBucketArray_test.cpp \ Config_test.cpp \ diff --git a/libs/androidfw/tests/AppAsLib_test.cpp b/libs/androidfw/tests/AppAsLib_test.cpp new file mode 100644 index 000000000000..bdb0c3d38f6f --- /dev/null +++ b/libs/androidfw/tests/AppAsLib_test.cpp @@ -0,0 +1,53 @@ +/* + * 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 <androidfw/ResourceTypes.h> + +#include "data/basic/R.h" +#include "data/appaslib/R.h" + +#include <gtest/gtest.h> + +using namespace android; + +namespace { + +#include "data/basic/basic_arsc.h" + +TEST(AppAsLibTest, loadedAsApp) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + Res_value val; + ssize_t block = table.getResource(base::R::integer::number2, &val); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + ASSERT_EQ(base::R::array::integerArray1, val.data); +} + +TEST(AppAsLibTest, loadedAsSharedLib) { + ResTable table; + // Load as shared library. + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len, NULL, 0, -1, false, true)); + + Res_value val; + ssize_t block = table.getResource(appaslib::R::integer::number2, &val); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + ASSERT_EQ(appaslib::R::array::integerArray1, val.data); +} + +} diff --git a/libs/androidfw/tests/data/appaslib/R.h b/libs/androidfw/tests/data/appaslib/R.h new file mode 100644 index 000000000000..f89d4bfdd15e --- /dev/null +++ b/libs/androidfw/tests/data/appaslib/R.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __APPASLIB_R_H +#define __APPASLIB_R_H + +namespace appaslib { +namespace R { + +namespace integer { + enum { + number2 = 0x02040001, // default + }; +} + +namespace array { + enum { + integerArray1 = 0x02060000, // default + }; +} + +} // namespace R +} // namespace appaslib + +#endif // __APPASLIB_R_H diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp index e307ad91885f..c128ca775155 100644 --- a/libs/hwui/CanvasState.cpp +++ b/libs/hwui/CanvasState.cpp @@ -34,12 +34,17 @@ CanvasState::CanvasState(CanvasStateClient& renderer) } -CanvasState::~CanvasState() { - -} - -void CanvasState::initializeSaveStack(float clipLeft, float clipTop, +void CanvasState::initializeSaveStack( + int viewportWidth, int viewportHeight, + float clipLeft, float clipTop, float clipRight, float clipBottom, const Vector3& lightCenter) { + if (mWidth != viewportWidth || mHeight != viewportHeight) { + mWidth = viewportWidth; + mHeight = viewportHeight; + mFirstSnapshot->initializeViewport(viewportWidth, viewportHeight); + mCanvas.onViewportInitialized(); + } + mSnapshot = new Snapshot(mFirstSnapshot, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); @@ -48,20 +53,6 @@ void CanvasState::initializeSaveStack(float clipLeft, float clipTop, mSaveCount = 1; } -void CanvasState::setViewport(int width, int height) { - mWidth = width; - mHeight = height; - mFirstSnapshot->initializeViewport(width, height); - mCanvas.onViewportInitialized(); - - // create a temporary 1st snapshot, so old snapshots are released, - // and viewport can be queried safely. - // TODO: remove, combine viewport + save stack initialization - mSnapshot = new Snapshot(mFirstSnapshot, - SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); - mSaveCount = 1; -} - /////////////////////////////////////////////////////////////////////////////// // Save (layer) /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h index b35db28eaf82..f0fb9ba8b324 100644 --- a/libs/hwui/CanvasState.h +++ b/libs/hwui/CanvasState.h @@ -71,20 +71,18 @@ public: * (getClip/Matrix), but so that quickRejection can also be used. */ -class ANDROID_API CanvasState { +class CanvasState { public: CanvasState(CanvasStateClient& renderer); - ~CanvasState(); /** * Initializes the first snapshot, computing the projection matrix, * and stores the dimensions of the render target. */ - void initializeSaveStack(float clipLeft, float clipTop, float clipRight, float clipBottom, + void initializeSaveStack(int viewportWidth, int viewportHeight, + float clipLeft, float clipTop, float clipRight, float clipBottom, const Vector3& lightCenter); - void setViewport(int width, int height); - bool hasRectToRectTransform() const { return CC_LIKELY(currentTransform()->rectToRect()); } @@ -159,16 +157,11 @@ public: int getHeight() const { return mHeight; } bool clipIsSimple() const { return currentSnapshot()->clipIsSimple(); } - inline const Snapshot* currentSnapshot() const { - return mSnapshot != nullptr ? mSnapshot.get() : mFirstSnapshot.get(); - } + inline const Snapshot* currentSnapshot() const { return mSnapshot.get(); } inline Snapshot* writableSnapshot() { return mSnapshot.get(); } inline const Snapshot* firstSnapshot() const { return mFirstSnapshot.get(); } private: - /// No default constructor - must supply a CanvasStateClient (mCanvas). - CanvasState(); - /// indicates that the clip has been changed since the last time it was consumed bool mDirtyClip; diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index 506bfad08c1b..77bde863af51 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -54,8 +54,8 @@ void DisplayListCanvas::reset(int width, int height) { "prepareDirty called a second time during a recording!"); mDisplayListData = new DisplayListData(); - mState.setViewport(width, height); - mState.initializeSaveStack(0, 0, mState.getWidth(), mState.getHeight(), Vector3()); + mState.initializeSaveStack(width, height, + 0, 0, width, height, Vector3()); mDeferredBarrierType = kBarrier_InOrder; mState.setDirtyClip(false); diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index e7482211b399..8d8528961794 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -236,8 +236,7 @@ void Layer::defer(const OpenGLRenderer& rootRenderer) { DeferStateStruct deferredState(*deferredList, *renderer, RenderNode::kReplayFlag_ClipChildren); - renderer->setViewport(width, height); - renderer->setupFrameState(dirtyRect.left, dirtyRect.top, + renderer->setupFrameState(width, height, dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); renderNode->computeOrdering(); @@ -258,9 +257,8 @@ void Layer::flush() { ATRACE_LAYER_WORK("Issue"); renderer->startMark((renderNode.get() != nullptr) ? renderNode->getName() : "Layer"); - renderer->setViewport(layer.getWidth(), layer.getHeight()); - renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, - !isBlend()); + renderer->prepareDirty(layer.getWidth(), layer.getHeight(), + dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); deferredList->flush(*renderer, dirtyRect); @@ -277,9 +275,8 @@ void Layer::render(const OpenGLRenderer& rootRenderer) { ATRACE_LAYER_WORK("Direct-Issue"); updateLightPosFromRenderer(rootRenderer); - renderer->setViewport(layer.getWidth(), layer.getHeight()); - renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, - !isBlend()); + renderer->prepareDirty(layer.getWidth(), layer.getHeight(), + dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); renderer->drawRenderNode(renderNode.get(), dirtyRect, RenderNode::kReplayFlag_ClipChildren); diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index d8e6392bd07e..c63b5597f284 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -43,8 +43,8 @@ LayerRenderer::LayerRenderer(RenderState& renderState, Layer* layer) LayerRenderer::~LayerRenderer() { } -void LayerRenderer::prepareDirty(float left, float top, float right, float bottom, - bool opaque) { +void LayerRenderer::prepareDirty(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque) { LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->getFbo()); mRenderState.bindFramebuffer(mLayer->getFbo()); @@ -64,7 +64,8 @@ void LayerRenderer::prepareDirty(float left, float top, float right, float botto } mLayer->clipRect.set(dirty); - OpenGLRenderer::prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, opaque); + OpenGLRenderer::prepareDirty(viewportWidth, viewportHeight, + dirty.left, dirty.top, dirty.right, dirty.bottom, opaque); } void LayerRenderer::clear(float left, float top, float right, float bottom, bool opaque) { @@ -430,9 +431,8 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* { LayerRenderer renderer(renderState, layer); - renderer.setViewport(bitmap->width(), bitmap->height()); - renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f, - bitmap->width(), bitmap->height(), !layer->isBlend()); + renderer.OpenGLRenderer::prepareDirty(bitmap->width(), bitmap->height(), + 0.0f, 0.0f, bitmap->width(), bitmap->height(), !layer->isBlend()); renderState.scissor().setEnabled(false); renderer.translate(0.0f, bitmap->height()); diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h index 47ded7e7e0bf..e4a54b0a0520 100644 --- a/libs/hwui/LayerRenderer.h +++ b/libs/hwui/LayerRenderer.h @@ -50,8 +50,8 @@ public: virtual ~LayerRenderer(); virtual void onViewportInitialized() override { /* do nothing */ } - virtual void prepareDirty(float left, float top, float right, float bottom, - bool opaque) override; + virtual void prepareDirty(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque) override; virtual void clear(float left, float top, float right, float bottom, bool opaque) override; virtual bool finish() override; diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 5692d7e120ff..e06e34849a91 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -113,10 +113,11 @@ void OpenGLRenderer::onViewportInitialized() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); } -void OpenGLRenderer::setupFrameState(float left, float top, - float right, float bottom, bool opaque) { +void OpenGLRenderer::setupFrameState(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque) { mCaches.clearGarbage(); - mState.initializeSaveStack(left, top, right, bottom, mLightCenter); + mState.initializeSaveStack(viewportWidth, viewportHeight, + left, top, right, bottom, mLightCenter); mOpaque = opaque; mTilingClip.set(left, top, right, bottom); } @@ -137,10 +138,10 @@ void OpenGLRenderer::startFrame() { mTilingClip.right, mTilingClip.bottom, mOpaque); } -void OpenGLRenderer::prepareDirty(float left, float top, - float right, float bottom, bool opaque) { +void OpenGLRenderer::prepareDirty(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque) { - setupFrameState(left, top, right, bottom, opaque); + setupFrameState(viewportWidth, viewportHeight, left, top, right, bottom, opaque); // Layer renderers will start the frame immediately // The framebuffer renderer will first defer the display list diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index af85e8c72619..910af5705705 100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -119,15 +119,6 @@ public: OpenGLRenderer(RenderState& renderState); virtual ~OpenGLRenderer(); - /** - * Sets the dimension of the underlying drawing surface. This method must - * be called at least once every time the drawing surface changes size. - * - * @param width The width in pixels of the underlysing surface - * @param height The height in pixels of the underlysing surface - */ - void setViewport(int width, int height) { mState.setViewport(width, height); } - void initProperties(); void initLight(float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); @@ -143,21 +134,8 @@ public: * and will not be cleared. If false, the target surface * will be cleared */ - virtual void prepareDirty(float left, float top, float right, float bottom, - bool opaque); - - /** - * Prepares the renderer to draw a frame. This method must be invoked - * at the beginning of each frame. When this method is invoked, the - * entire drawing surface is assumed to be redrawn. - * - * @param opaque If true, the target surface is considered opaque - * and will not be cleared. If false, the target surface - * will be cleared - */ - void prepare(bool opaque) { - prepareDirty(0.0f, 0.0f, mState.getWidth(), mState.getHeight(), opaque); - } + virtual void prepareDirty(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque); /** * Indicates the end of a frame. This method must be invoked whenever @@ -430,7 +408,8 @@ protected: * Perform the setup specific to a frame. This method does not * issue any OpenGL commands. */ - void setupFrameState(float left, float top, float right, float bottom, bool opaque); + void setupFrameState(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque); /** * Indicates the start of rendering. This method will setup the diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 167380205e67..b74b5088c14f 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -248,7 +248,7 @@ void CanvasContext::draw() { Frame frame = mEglManager.beginFrame(mEglSurface); if (frame.width() != mCanvas->getViewportWidth() || frame.height() != mCanvas->getViewportHeight()) { - mCanvas->setViewport(frame.width(), frame.height()); + // can't rely on prior content of window if viewport size changes dirty.setEmpty(); } else if (mHaveNewSurface || frame.bufferAge() == 0) { // New surface needs a full draw @@ -295,8 +295,8 @@ void CanvasContext::draw() { mDamageHistory.next() = screenDirty; mEglManager.damageFrame(frame, dirty); - mCanvas->prepareDirty(dirty.fLeft, dirty.fTop, - dirty.fRight, dirty.fBottom, mOpaque); + mCanvas->prepareDirty(frame.width(), frame.height(), + dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque); Rect outBounds; mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds); diff --git a/libs/hwui/unit_tests/CanvasStateTests.cpp b/libs/hwui/unit_tests/CanvasStateTests.cpp index 79852bebdd08..dfbf6d358ce5 100644 --- a/libs/hwui/unit_tests/CanvasStateTests.cpp +++ b/libs/hwui/unit_tests/CanvasStateTests.cpp @@ -47,8 +47,8 @@ static bool approxEqual(const Matrix4& a, const Matrix4& b) { TEST(CanvasState, gettersAndSetters) { CanvasState state(sNullClient); - state.setViewport(200, 200); - state.initializeSaveStack(0, 0, state.getWidth(), state.getHeight(), Vector3()); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); ASSERT_EQ(state.getWidth(), 200); ASSERT_EQ(state.getHeight(), 200); @@ -65,8 +65,8 @@ TEST(CanvasState, gettersAndSetters) { TEST(CanvasState, simpleClipping) { CanvasState state(sNullClient); - state.setViewport(200, 200); - state.initializeSaveStack(0, 0, state.getWidth(), state.getHeight(), Vector3()); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); state.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(0, 0, 100, 100)); @@ -80,8 +80,8 @@ TEST(CanvasState, simpleClipping) { TEST(CanvasState, complexClipping) { CanvasState state(sNullClient); - state.setViewport(200, 200); - state.initializeSaveStack(0, 0, state.getWidth(), state.getHeight(), Vector3()); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); state.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); { @@ -116,8 +116,8 @@ TEST(CanvasState, complexClipping) { TEST(CanvasState, saveAndRestore) { CanvasState state(sNullClient); - state.setViewport(200, 200); - state.initializeSaveStack(0, 0, state.getWidth(), state.getHeight(), Vector3()); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); state.save(SkCanvas::kClip_SaveFlag); { @@ -140,10 +140,10 @@ TEST(CanvasState, saveAndRestore) { TEST(CanvasState, saveAndRestoreButNotTooMuch) { CanvasState state(sNullClient); - state.setViewport(200, 200); - state.initializeSaveStack(0, 0, state.getWidth(), state.getHeight(), Vector3()); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); - state.save(SkCanvas::kMatrix_SaveFlag); // Note: clip not saved + state.save(SkCanvas::kMatrix_SaveFlag); // NOTE: clip not saved { state.clipRect(0, 0, 10, 10, SkRegion::kIntersect_Op); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(0, 0, 10, 10)); diff --git a/media/java/android/media/midi/IBluetoothMidiService.aidl b/media/java/android/media/midi/IBluetoothMidiService.aidl new file mode 100644 index 000000000000..fe5566d6e662 --- /dev/null +++ b/media/java/android/media/midi/IBluetoothMidiService.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.midi; + +import android.bluetooth.BluetoothDevice; +import android.os.IBinder; + +/** @hide */ +interface IBluetoothMidiService +{ + IBinder addBluetoothDevice(in BluetoothDevice bluetoothDevice); +} diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 218a11761b5a..93a44261281b 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -440,6 +440,12 @@ status_t JMediaCodec::createByteBufferFromABuffer( // if this is an ABuffer that doesn't actually hold any accessible memory, // use a null ByteBuffer *buf = NULL; + + if (buffer == NULL) { + ALOGV("createByteBufferFromABuffer - given NULL, returning NULL"); + return OK; + } + if (buffer->base() == NULL) { return OK; } diff --git a/media/packages/BluetoothMidiService/Android.mk b/media/packages/BluetoothMidiService/Android.mk index 2c9c3c5615e2..05659251f7aa 100644 --- a/media/packages/BluetoothMidiService/Android.mk +++ b/media/packages/BluetoothMidiService/Android.mk @@ -3,7 +3,8 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_SRC_FILES += \ + $(call all-java-files-under,src) LOCAL_PACKAGE_NAME := BluetoothMidiService LOCAL_CERTIFICATE := platform diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml index b0b389a59aa3..1cfd55d556a9 100644 --- a/media/packages/BluetoothMidiService/AndroidManifest.xml +++ b/media/packages/BluetoothMidiService/AndroidManifest.xml @@ -8,7 +8,7 @@ <application android:label="@string/app_name"> - <service android:name="BluetoothMidiService" + <service android:name=".BluetoothMidiService" android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"> <intent-filter> <action android:name="android.media.midi.BluetoothMidiService" /> diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java index 1f81a05c6387..5541f9f81f31 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java @@ -19,6 +19,7 @@ package com.android.bluetoothmidiservice; import android.app.Service; import android.bluetooth.BluetoothDevice; import android.content.Intent; +import android.media.midi.IBluetoothMidiService; import android.media.midi.MidiManager; import android.os.IBinder; import android.util.Log; @@ -34,25 +35,31 @@ public class BluetoothMidiService extends Service { @Override public IBinder onBind(Intent intent) { - if (MidiManager.BLUETOOTH_MIDI_SERVICE_INTENT.equals(intent.getAction())) { - BluetoothDevice bluetoothDevice = (BluetoothDevice)intent.getParcelableExtra("device"); + // Return the interface + return mBinder; + } + + + private final IBluetoothMidiService.Stub mBinder = new IBluetoothMidiService.Stub() { + + public IBinder addBluetoothDevice(BluetoothDevice bluetoothDevice) { + BluetoothMidiDevice device; if (bluetoothDevice == null) { - Log.e(TAG, "no BluetoothDevice in onBind intent"); + Log.e(TAG, "no BluetoothDevice in addBluetoothDevice()"); return null; } - - BluetoothMidiDevice device; synchronized (mDeviceServerMap) { device = mDeviceServerMap.get(bluetoothDevice); if (device == null) { - device = new BluetoothMidiDevice(this, bluetoothDevice, this); + device = new BluetoothMidiDevice(BluetoothMidiService.this, + bluetoothDevice, BluetoothMidiService.this); mDeviceServerMap.put(bluetoothDevice, device); } } return device.getBinder(); } - return null; - } + + }; void deviceClosed(BluetoothDevice device) { synchronized (mDeviceServerMap) { diff --git a/packages/BackupRestoreConfirmation/res/values-nl/strings.xml b/packages/BackupRestoreConfirmation/res/values-nl/strings.xml index f483b14f5867..81f2712e8cb3 100644 --- a/packages/BackupRestoreConfirmation/res/values-nl/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-nl/strings.xml @@ -18,18 +18,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="backup_confirm_title" msgid="827563724209303345">"Volledige back-up"</string> <string name="restore_confirm_title" msgid="5469365809567486602">"Volledig herstel"</string> - <string name="backup_confirm_text" msgid="1878021282758896593">"Er is een volledige back-up van alle gegevens naar een verbonden desktopcomputer aangevraagd. Wilt u dit toestaan?\n\nAls u de back-up zelf niet heeft aangevraagd, moet u niet toestaan dat de bewerking wordt uitgevoerd."</string> + <string name="backup_confirm_text" msgid="1878021282758896593">"Er is een volledige back-up van alle gegevens naar een verbonden desktopcomputer aangevraagd. Wil je dit toestaan?\n\nAls je de back-up niet zelf hebt aangevraagd, moet je niet toestaan dat de bewerking wordt uitgevoerd."</string> <string name="allow_backup_button_label" msgid="4217228747769644068">"Back-up maken van mijn gegevens"</string> <string name="deny_backup_button_label" msgid="6009119115581097708">"Geen back-up maken"</string> - <string name="restore_confirm_text" msgid="7499866728030461776">"Er is volledig herstel van alle gegevens van een verbonden desktopcomputer aangevraagd. Wilt u dit toestaan?\n\nAls u het herstel zelf niet heeft aangevraagd, moet u niet toestaan dat de bewerking wordt uitgevoerd. Bij herstel worden alle gegevens op het apparaat vervangen."</string> + <string name="restore_confirm_text" msgid="7499866728030461776">"Er is volledig herstel van alle gegevens van een verbonden desktopcomputer aangevraagd. Wil je dit toestaan?\n\nAls je het herstel niet zelf hebt aangevraagd, moet je niet toestaan dat de bewerking wordt uitgevoerd. Bij herstel worden alle gegevens op het apparaat vervangen."</string> <string name="allow_restore_button_label" msgid="3081286752277127827">"Mijn gegevens herstellen"</string> <string name="deny_restore_button_label" msgid="1724367334453104378">"Niet herstellen"</string> - <string name="current_password_text" msgid="8268189555578298067">"Geef hieronder uw huidige back-upwachtwoord op:"</string> - <string name="device_encryption_restore_text" msgid="1570864916855208992">"Geef hieronder uw wachtwoord voor apparaatcodering op."</string> - <string name="device_encryption_backup_text" msgid="5866590762672844664">"Geef hieronder uw wachtwoord voor apparaatversleuteling op. Dit wordt ook gebruikt om het back-uparchief te versleutelen."</string> - <string name="backup_enc_password_text" msgid="4981585714795233099">"Geef een wachtwoord op dat u wilt gebruiken voor het coderen van de gegevens van de volledige back-up. Als u dit leeg laat, wordt uw huidige back-upwachtwoord gebruikt:"</string> + <string name="current_password_text" msgid="8268189555578298067">"Geef hieronder je huidige back-upwachtwoord op:"</string> + <string name="device_encryption_restore_text" msgid="1570864916855208992">"Geef hieronder je wachtwoord voor apparaatcodering op."</string> + <string name="device_encryption_backup_text" msgid="5866590762672844664">"Geef hieronder je wachtwoord voor apparaatversleuteling op. Dit wordt ook gebruikt om het back-uparchief te versleutelen."</string> + <string name="backup_enc_password_text" msgid="4981585714795233099">"Geef een wachtwoord op dat u wilt gebruiken voor het coderen van de gegevens van de volledige back-up. Als u dit leeg laat, wordt je huidige back-upwachtwoord gebruikt:"</string> <string name="backup_enc_password_optional" msgid="1350137345907579306">"Als u de gegevens van de volledige back-up wilt versleutelen, geeft u daarvoor hieronder een wachtwoord op:"</string> - <string name="backup_enc_password_required" msgid="7889652203371654149">"Aangezien uw apparaat is gecodeerd, moet u uw back-up coderen. Geef hieronder een wachtwoord op:"</string> + <string name="backup_enc_password_required" msgid="7889652203371654149">"Aangezien je apparaat is gecodeerd, moet u je back-up coderen. Geef hieronder een wachtwoord op:"</string> <string name="restore_enc_password_text" msgid="6140898525580710823">"Als deze herstelgegevens zijn gecodeerd, geeft u hieronder het wachtwoord op:"</string> <string name="toast_backup_started" msgid="550354281452756121">"Back-up starten..."</string> <string name="toast_backup_ended" msgid="3818080769548726424">"Back-up voltooid"</string> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 5c530e5ff4c3..cfd4ecb96b59 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -1857,7 +1857,7 @@ public class DirectoryFragment extends Fragment { // position by 1. final int originalPos = position; final int size = mMarkedForDeletion.size(); - for (int i = 0; i <= size; ++i) { + for (int i = 0; i < size; ++i) { // It'd be more concise, but less efficient, to iterate over positions while calling // mMarkedForDeletion.get. Instead, iterate over deleted entries. if (mMarkedForDeletion.keyAt(i) <= position && mMarkedForDeletion.valueAt(i)) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java index f9f308d9dfa1..5930056f111c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java @@ -73,10 +73,10 @@ public final class MultiSelectManager { private final List<MultiSelectManager.Callback> mCallbacks = new ArrayList<>(1); private Adapter<?> mAdapter; - private MultiSelectHelper mHelper; + private ItemFinder mHelper; private boolean mSingleSelect; - @Nullable private BandSelectManager mBandManager; + @Nullable private BandController mBandManager; /** * @param recyclerView @@ -90,11 +90,13 @@ public final class MultiSelectManager { this( recyclerView.getAdapter(), - new RuntimeRecyclerViewHelper(recyclerView), + new RuntimeItemFinder(recyclerView), mode); if (mode == MODE_MULTIPLE) { - mBandManager = new BandSelectManager((RuntimeRecyclerViewHelper) mHelper); + mBandManager = new BandController( + mHelper, + new RuntimeBandEnvironment(recyclerView)); } GestureDetector.SimpleOnGestureListener listener = @@ -167,7 +169,7 @@ public final class MultiSelectManager { * @hide */ @VisibleForTesting - MultiSelectManager(Adapter<?> adapter, MultiSelectHelper helper, int mode) { + MultiSelectManager(Adapter<?> adapter, ItemFinder helper, int mode) { checkNotNull(adapter, "'adapter' cannot be null."); checkNotNull(helper, "'helper' cannot be null."); @@ -730,16 +732,6 @@ public final class MultiSelectManager { mTotalSelection = mSavedSelection.clone(); } - private boolean flip(int position) { - if (contains(position)) { - remove(position); - return false; - } else { - add(position); - return true; - } - } - /** @hide */ @VisibleForTesting boolean add(int position) { @@ -873,109 +865,114 @@ public final class MultiSelectManager { } /** - * Provides functionality for MultiSelectManager. In practice, use RuntimeRecyclerViewHelper; - * this interface exists only for mocking in tests. + * Provides functionality for MultiSelectManager. Exists primarily to tests that are + * fully isolated from RecyclerView. */ - interface MultiSelectHelper { - int findEventPosition(MotionEvent e); + interface ItemFinder { + int findItemPosition(MotionEvent e); + } + + /** ItemFinder implementation backed by good ol' RecyclerView. */ + private static final class RuntimeItemFinder implements ItemFinder { + + private final RecyclerView mView; + + RuntimeItemFinder(RecyclerView view) { + mView = view; + } + + @Override + public int findItemPosition(MotionEvent e) { + View view = mView.findChildViewUnder(e.getX(), e.getY()); + return view != null + ? mView.getChildAdapterPosition(view) + : RecyclerView.NO_POSITION; + } } /** - * Provides functionality for BandSelectManager. In practice, use RuntimeRecyclerViewHelper; - * this interface exists only for mocking in tests. + * Provides functionality for BandController. Exists primarily to tests that are + * fully isolated from RecyclerView. */ - interface BandManagerHelper { - void drawBand(Rect rect); + interface BandEnvironment { + void showBand(Rect rect); + void hideBand(); void addOnScrollListener(RecyclerView.OnScrollListener listener); - int findEventPosition(MotionEvent e); + void removeOnScrollListener(RecyclerView.OnScrollListener listener); + void scrollBy(int dy); int getHeight(); - void hideBand(); void invalidateView(); - void postRunnable(Runnable r); + void runAtNextFrame(Runnable r); void removeCallback(Runnable r); - void scrollBy(int dy); - } - - /** - * Provides functionality for BandSelectModel. In practice, use RuntimeRecyclerViewHelper; - * this interface exists only for mocking in tests. - */ - interface BandModelHelper { - void addOnScrollListener(RecyclerView.OnScrollListener listener); Point createAbsolutePoint(Point relativePoint); Rect getAbsoluteRectForChildViewAt(int index); int getAdapterPositionAt(int index); - int getNumColumns(); - int getNumRows(); - int getTotalChildCount(); + int getColumnCount(); + int getRowCount(); + int getChildCount(); int getVisibleChildCount(); - void removeOnScrollListener(RecyclerView.OnScrollListener listener); } - /** - * Concrete RecyclerViewHelper implementation for use within the Files app. - */ - private static final class RuntimeRecyclerViewHelper implements MultiSelectHelper, - BandManagerHelper, BandModelHelper { + /** RvFacade implementation backed by good ol' RecyclerView. */ + private static final class RuntimeBandEnvironment implements BandEnvironment { - private final RecyclerView mRecyclerView; - private final Drawable mBandSelectRect; + private final RecyclerView mView; + private final Drawable mBand; private boolean mIsOverlayShown = false; - RuntimeRecyclerViewHelper(RecyclerView rv) { - mRecyclerView = rv; - mBandSelectRect = mRecyclerView.getContext().getTheme().getDrawable( - R.drawable.band_select_overlay); + RuntimeBandEnvironment(RecyclerView rv) { + mView = rv; + mBand = mView.getContext().getTheme().getDrawable(R.drawable.band_select_overlay); } @Override public int getAdapterPositionAt(int index) { - View child = mRecyclerView.getChildAt(index); - return mRecyclerView.getChildViewHolder(child).getAdapterPosition(); + View child = mView.getChildAt(index); + return mView.getChildViewHolder(child).getAdapterPosition(); } @Override public void addOnScrollListener(OnScrollListener listener) { - mRecyclerView.addOnScrollListener(listener); + mView.addOnScrollListener(listener); } @Override public void removeOnScrollListener(OnScrollListener listener) { - mRecyclerView.removeOnScrollListener(listener); + mView.removeOnScrollListener(listener); } @Override public Point createAbsolutePoint(Point relativePoint) { - return new Point(relativePoint.x + mRecyclerView.computeHorizontalScrollOffset(), - relativePoint.y + mRecyclerView.computeVerticalScrollOffset()); + return new Point(relativePoint.x + mView.computeHorizontalScrollOffset(), + relativePoint.y + mView.computeVerticalScrollOffset()); } @Override public Rect getAbsoluteRectForChildViewAt(int index) { - final View child = mRecyclerView.getChildAt(index); + final View child = mView.getChildAt(index); final Rect childRect = new Rect(); child.getHitRect(childRect); - childRect.left += mRecyclerView.computeHorizontalScrollOffset(); - childRect.right += mRecyclerView.computeHorizontalScrollOffset(); - childRect.top += mRecyclerView.computeVerticalScrollOffset(); - childRect.bottom += mRecyclerView.computeVerticalScrollOffset(); + childRect.left += mView.computeHorizontalScrollOffset(); + childRect.right += mView.computeHorizontalScrollOffset(); + childRect.top += mView.computeVerticalScrollOffset(); + childRect.bottom += mView.computeVerticalScrollOffset(); return childRect; } @Override - public int getVisibleChildCount() { - return mRecyclerView.getChildCount(); + public int getChildCount() { + return mView.getAdapter().getItemCount(); } @Override - public int getTotalChildCount() { - return mRecyclerView.getAdapter().getItemCount(); + public int getVisibleChildCount() { + return mView.getChildCount(); } @Override - public int getNumColumns() { - LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + public int getColumnCount() { + LayoutManager layoutManager = mView.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { return ((GridLayoutManager) layoutManager).getSpanCount(); } @@ -985,57 +982,49 @@ public final class MultiSelectManager { } @Override - public int getNumRows() { - int numFullColumns = getTotalChildCount() / getNumColumns(); - boolean hasPartiallyFullColumn = getTotalChildCount() % getNumColumns() != 0; + public int getRowCount() { + int numFullColumns = getChildCount() / getColumnCount(); + boolean hasPartiallyFullColumn = getChildCount() % getColumnCount() != 0; return numFullColumns + (hasPartiallyFullColumn ? 1 : 0); } @Override - public int findEventPosition(MotionEvent e) { - View view = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); - return view != null - ? mRecyclerView.getChildAdapterPosition(view) - : RecyclerView.NO_POSITION; - } - - @Override public int getHeight() { - return mRecyclerView.getHeight(); + return mView.getHeight(); } @Override public void invalidateView() { - mRecyclerView.invalidate(); + mView.invalidate(); } @Override - public void postRunnable(Runnable r) { - mRecyclerView.postOnAnimation(r); + public void runAtNextFrame(Runnable r) { + mView.postOnAnimation(r); } @Override public void removeCallback(Runnable r) { - mRecyclerView.removeCallbacks(r); + mView.removeCallbacks(r); } @Override public void scrollBy(int dy) { - mRecyclerView.scrollBy(0, dy); + mView.scrollBy(0, dy); } @Override - public void drawBand(Rect rect) { - mBandSelectRect.setBounds(rect); + public void showBand(Rect rect) { + mBand.setBounds(rect); if (!mIsOverlayShown) { - mRecyclerView.getOverlay().add(mBandSelectRect); + mView.getOverlay().add(mBand); } } @Override public void hideBand() { - mRecyclerView.getOverlay().remove(mBandSelectRect); + mView.getOverlay().remove(mBand); } } @@ -1172,34 +1161,35 @@ public final class MultiSelectManager { * and {@link MultiSelectManager}. This class is responsible for rendering the band select * overlay and selecting overlaid items via MultiSelectManager. */ - public class BandSelectManager extends RecyclerView.OnScrollListener - implements BandSelectModel.OnSelectionChangedListener { + public class BandController extends RecyclerView.OnScrollListener + implements GridModel.OnSelectionChangedListener { private static final int NOT_SET = -1; - private final BandManagerHelper mHelper; + private final ItemFinder mItemFinder; + private final BandEnvironment mEnvironment; private final Runnable mModelBuilder; @Nullable private Rect mBounds; @Nullable private Point mCurrentPosition; @Nullable private Point mOrigin; - @Nullable private BandSelectModel mModel; + @Nullable private GridModel mModel; // The time at which the current band selection-induced scroll began. If no scroll is in // progress, the value is NOT_SET. private long mScrollStartTime = NOT_SET; private final Runnable mViewScroller = new ViewScroller(); - public <T extends BandManagerHelper & BandModelHelper> - BandSelectManager(final T helper) { - mHelper = helper; - mHelper.addOnScrollListener(this); + public BandController(ItemFinder finder, final BandEnvironment environment) { + mItemFinder = finder; + mEnvironment = environment; + mEnvironment.addOnScrollListener(this); mModelBuilder = new Runnable() { @Override public void run() { - mModel = new BandSelectModel(helper); - mModel.addOnSelectionChangedListener(BandSelectManager.this); + mModel = new GridModel(environment); + mModel.addOnSelectionChangedListener(BandController.this); } }; } @@ -1226,7 +1216,8 @@ public final class MultiSelectManager { return !isActive() && Events.isMouseEvent(e) // a mouse && Events.isActionDown(e) // the initial button press - && mHelper.findEventPosition(e) == RecyclerView.NO_ID; // in empty space + && mAdapter.getItemCount() > 0 + && mItemFinder.findItemPosition(e) == RecyclerView.NO_ID; // in empty space } boolean shouldStop(InputEvent input) { @@ -1274,9 +1265,9 @@ public final class MultiSelectManager { * Scrolls the view if necessary. */ private void scrollViewIfNecessary() { - mHelper.removeCallback(mViewScroller); + mEnvironment.removeCallback(mViewScroller); mViewScroller.run(); - mHelper.invalidateView(); + mEnvironment.invalidateView(); } /** @@ -1288,7 +1279,7 @@ public final class MultiSelectManager { Math.min(mOrigin.y, mCurrentPosition.y), Math.max(mOrigin.x, mCurrentPosition.x), Math.max(mOrigin.y, mCurrentPosition.y)); - mHelper.drawBand(mBounds); + mEnvironment.showBand(mBounds); } /** @@ -1297,14 +1288,14 @@ public final class MultiSelectManager { private void endBandSelect() { if (DEBUG) Log.d(TAG, "Ending band select."); - mHelper.hideBand(); + mEnvironment.hideBand(); mSelection.applyProvisionalSelection(); mModel.endSelection(); int firstSelected = mModel.getPositionNearestOrigin(); if (!mSelection.contains(firstSelected)) { Log.w(TAG, "First selected by band is NOT in selection!"); // Sadly this is really happening. Need to figure out what's going on. - } else if (firstSelected != BandSelectModel.NOT_SET) { + } else if (firstSelected != GridModel.NOT_SET) { setSelectionFocusBegin(firstSelected); } @@ -1340,8 +1331,8 @@ public final class MultiSelectManager { int pixelsPastView = 0; if (mCurrentPosition.y <= 0) { pixelsPastView = mCurrentPosition.y - 1; - } else if (mCurrentPosition.y >= mHelper.getHeight() - 1) { - pixelsPastView = mCurrentPosition.y - mHelper.getHeight() + 1; + } else if (mCurrentPosition.y >= mEnvironment.getHeight() - 1) { + pixelsPastView = mCurrentPosition.y - mEnvironment.getHeight() + 1; } if (!isActive() || pixelsPastView == 0) { @@ -1360,10 +1351,10 @@ public final class MultiSelectManager { // Compute the number of pixels to scroll, and scroll that many pixels. final int numPixels = computeScrollDistance( pixelsPastView, System.currentTimeMillis() - mScrollStartTime); - mHelper.scrollBy(numPixels); + mEnvironment.scrollBy(numPixels); - mHelper.removeCallback(mViewScroller); - mHelper.postRunnable(this); + mEnvironment.removeCallback(mViewScroller); + mEnvironment.runAtNextFrame(this); } /** @@ -1376,14 +1367,14 @@ public final class MultiSelectManager { * @return */ private int computeScrollDistance(int pixelsPastView, long scrollDuration) { - final int maxScrollStep = mHelper.getHeight(); + final int maxScrollStep = mEnvironment.getHeight(); final int direction = (int) Math.signum(pixelsPastView); final int absPastView = Math.abs(pixelsPastView); // Calculate the ratio of how far out of the view the pointer currently resides to // the entire height of the view. final float outOfBoundsRatio = Math.min( - 1.0f, (float) absPastView / mHelper.getHeight()); + 1.0f, (float) absPastView / mEnvironment.getHeight()); // Interpolate this ratio and use it to compute the maximum scroll that should be // possible for this step. final float cappedScrollStep = @@ -1446,7 +1437,7 @@ public final class MultiSelectManager { * RecyclerView to determine where its items are placed; then, once band selection is underway, * it alerts listeners of which items are covered by the selections. */ - public static final class BandSelectModel extends RecyclerView.OnScrollListener { + public static final class GridModel extends RecyclerView.OnScrollListener { public static final int NOT_SET = -1; @@ -1460,8 +1451,9 @@ public final class MultiSelectManager { private static final int LOWER_LEFT = LOWER | LEFT; private static final int LOWER_RIGHT = LOWER | RIGHT; - private final BandModelHelper mHelper; - private final List<OnSelectionChangedListener> mOnSelectionChangedListeners = new ArrayList<>(); + private final BandEnvironment mHelper; + private final List<OnSelectionChangedListener> mOnSelectionChangedListeners = + new ArrayList<>(); // Map from the x-value of the left side of a SparseBooleanArray of adapter positions, keyed // by their y-offset. For example, if the first column of the view starts at an x-value of 5, @@ -1469,15 +1461,13 @@ public final class MultiSelectManager { // value for key y is the adapter position for the item whose y-offset is y. private final SparseArray<SparseIntArray> mColumns = new SparseArray<>(); - // List of limits along the x-axis. For example, if the view has two columns, this list will - // have two elements, each of which lists the lower- and upper-limits of the x-values of the - // view items. This list is sorted from furthest left to furthest right. - private final List<Limits> mXLimitsList = new ArrayList<>(); + // List of limits along the x-axis (columns). + // This list is sorted from furthest left to furthest right. + private final List<Limits> mColumnBounds = new ArrayList<>(); - // Like mXLimitsList, but for y-coordinates. Note that this list only contains items which - // have been in the viewport. Thus, limits which exist in an area of the view to which the - // view has not scrolled are not present in the list. - private final List<Limits> mYLimitsList = new ArrayList<>(); + // List of limits along the y-axis (rows). Note that this list only contains items which + // have been in the viewport. + private final List<Limits> mRowBounds = new ArrayList<>(); // The adapter positions which have been recorded so far. private final SparseBooleanArray mKnownPositions = new SparseBooleanArray(); @@ -1499,7 +1489,7 @@ public final class MultiSelectManager { // should expand from when Shift+click is used. private int mPositionNearestOrigin = NOT_SET; - BandSelectModel(BandModelHelper helper) { + GridModel(BandEnvironment helper) { mHelper = helper; mHelper.addOnScrollListener(this); } @@ -1590,16 +1580,16 @@ public final class MultiSelectManager { * @param adapterPosition The position of the child view being processed. */ private void recordItemData(Rect absoluteChildRect, int adapterPosition) { - if (mXLimitsList.size() != mHelper.getNumColumns()) { + if (mColumnBounds.size() != mHelper.getColumnCount()) { // If not all x-limits have been recorded, record this one. recordLimits( - mXLimitsList, new Limits(absoluteChildRect.left, absoluteChildRect.right)); + mColumnBounds, new Limits(absoluteChildRect.left, absoluteChildRect.right)); } - if (mYLimitsList.size() != mHelper.getNumRows()) { + if (mRowBounds.size() != mHelper.getRowCount()) { // If not all y-limits have been recorded, record this one. recordLimits( - mYLimitsList, new Limits(absoluteChildRect.top, absoluteChildRect.bottom)); + mRowBounds, new Limits(absoluteChildRect.top, absoluteChildRect.bottom)); } SparseIntArray columnList = mColumns.get(absoluteChildRect.left); @@ -1640,7 +1630,7 @@ public final class MultiSelectManager { * Computes the currently-selected items. */ private void computeCurrentSelection() { - if (areItemsCoveredBySelection(mRelativePointer, mRelativeOrigin)) { + if (areItemsCoveredByBand(mRelativePointer, mRelativeOrigin)) { updateSelection(computeBounds()); } else { mSelection.clear(); @@ -1664,17 +1654,17 @@ public final class MultiSelectManager { */ private void updateSelection(Rect rect) { int columnStartIndex = - Collections.binarySearch(mXLimitsList, new Limits(rect.left, rect.left)); + Collections.binarySearch(mColumnBounds, new Limits(rect.left, rect.left)); checkState(columnStartIndex >= 0); int columnEndIndex = columnStartIndex; - for (int i = columnStartIndex; - i < mXLimitsList.size() && mXLimitsList.get(i).lowerLimit <= rect.right; i++) { + for (int i = columnStartIndex; i < mColumnBounds.size() + && mColumnBounds.get(i).lowerLimit <= rect.right; i++) { columnEndIndex = i; } SparseIntArray firstColumn = - mColumns.get(mXLimitsList.get(columnStartIndex).lowerLimit); + mColumns.get(mColumnBounds.get(columnStartIndex).lowerLimit); int rowStartIndex = firstColumn.indexOfKey(rect.top); if (rowStartIndex < 0) { mPositionNearestOrigin = NOT_SET; @@ -1698,7 +1688,7 @@ public final class MultiSelectManager { int columnStartIndex, int columnEndIndex, int rowStartIndex, int rowEndIndex) { mSelection.clear(); for (int column = columnStartIndex; column <= columnEndIndex; column++) { - SparseIntArray items = mColumns.get(mXLimitsList.get(column).lowerLimit); + SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit); for (int row = rowStartIndex; row <= rowEndIndex; row++) { int position = items.get(items.keyAt(row)); mSelection.append(position, true); @@ -1760,7 +1750,7 @@ public final class MultiSelectManager { * of item columns and the top- and bottom sides of item rows so that it can be determined * whether the pointer is located within the bounds of an item. */ - private class Limits implements Comparable<Limits> { + private static class Limits implements Comparable<Limits> { int lowerLimit; int upperLimit; @@ -1798,7 +1788,7 @@ public final class MultiSelectManager { * selection of items within those Limits as opposed to a search through every item to see if a * given coordinate value falls within those Limits. */ - private class RelativeCoordinate + private static class RelativeCoordinate implements Comparable<RelativeCoordinate> { /** * Location describing points after the last known item. @@ -1849,8 +1839,7 @@ public final class MultiSelectManager { * @param value The coordinate value. */ RelativeCoordinate(List<Limits> limitsList, int value) { - Limits dummyLimits = new Limits(value, value); - int index = Collections.binarySearch(limitsList, dummyLimits); + int index = Collections.binarySearch(limitsList, new Limits(value, value)); if (index >= 0) { this.type = WITHIN_LIMITS; @@ -1917,8 +1906,8 @@ public final class MultiSelectManager { final RelativeCoordinate yLocation; RelativePoint(Point point) { - this.xLocation = new RelativeCoordinate(mXLimitsList, point.x); - this.yLocation = new RelativeCoordinate(mYLimitsList, point.y); + this.xLocation = new RelativeCoordinate(mColumnBounds, point.x); + this.yLocation = new RelativeCoordinate(mRowBounds, point.y); } @Override @@ -1940,19 +1929,19 @@ public final class MultiSelectManager { Rect rect = new Rect(); rect.left = getCoordinateValue( min(mRelativeOrigin.xLocation, mRelativePointer.xLocation), - mXLimitsList, + mColumnBounds, true); rect.right = getCoordinateValue( max(mRelativeOrigin.xLocation, mRelativePointer.xLocation), - mXLimitsList, + mColumnBounds, false); rect.top = getCoordinateValue( min(mRelativeOrigin.yLocation, mRelativePointer.yLocation), - mYLimitsList, + mRowBounds, true); rect.bottom = getCoordinateValue( max(mRelativeOrigin.yLocation, mRelativePointer.yLocation), - mYLimitsList, + mRowBounds, false); return rect; } @@ -2013,7 +2002,7 @@ public final class MultiSelectManager { throw new RuntimeException("Invalid coordinate value."); } - private boolean areItemsCoveredBySelection( + private boolean areItemsCoveredByBand( RelativePoint first, RelativePoint second) { return doesCoordinateLocationCoverItems(first.xLocation, second.xLocation) && doesCoordinateLocationCoverItems(first.yLocation, second.yLocation); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java index 337064c28359..25d4ed4183f0 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java @@ -285,10 +285,10 @@ public class MultiSelectManagerTest { assertEquals(selection.toString(), expected, selection.size()); } - private static final class EventHelper implements MultiSelectManager.MultiSelectHelper { + private static final class EventHelper implements MultiSelectManager.ItemFinder { @Override - public int findEventPosition(MotionEvent e) { + public int findItemPosition(MotionEvent e) { throw new UnsupportedOperationException(); } } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/BandSelectModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java index 20c4548f1f1e..87d7e15a4072 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/BandSelectModelTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java @@ -16,25 +16,26 @@ package com.android.documentsui; -import static org.junit.Assert.*; - -import com.android.documentsui.MultiSelectManager.BandSelectModel; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import android.graphics.Point; import android.graphics.Rect; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.util.SparseBooleanArray; +import com.android.documentsui.MultiSelectManager.GridModel; + import org.junit.After; import org.junit.Test; -public class BandSelectModelTest { +public class MultiSelectManager_GridModelTest { private static final int VIEW_PADDING_PX = 5; private static final int CHILD_VIEW_EDGE_PX = 100; private static final int VIEWPORT_HEIGHT = 500; - private static BandSelectModel model; + private static GridModel model; private static TestHelper helper; private static SparseBooleanArray lastSelection; private static int viewWidth; @@ -42,14 +43,14 @@ public class BandSelectModelTest { private static void setUp(int numChildren, int numColumns) { helper = new TestHelper(numChildren, numColumns); viewWidth = VIEW_PADDING_PX + numColumns * (VIEW_PADDING_PX + CHILD_VIEW_EDGE_PX); - model = new BandSelectModel(helper); - model.addOnSelectionChangedListener(new BandSelectModel.OnSelectionChangedListener() { - - @Override - public void onSelectionChanged(SparseBooleanArray updatedSelection) { - lastSelection = updatedSelection; - } - }); + model = new GridModel(helper); + model.addOnSelectionChangedListener( + new GridModel.OnSelectionChangedListener() { + @Override + public void onSelectionChanged(SparseBooleanArray updatedSelection) { + lastSelection = updatedSelection; + } + }); } @After @@ -65,7 +66,7 @@ public class BandSelectModelTest { model.startSelection(new Point(0, 10)); model.resizeSelection(new Point(1, 11)); assertSelected(new int[0]); - assertEquals(BandSelectModel.NOT_SET, model.getPositionNearestOrigin()); + assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin()); } @Test @@ -74,7 +75,7 @@ public class BandSelectModelTest { model.startSelection(new Point(viewWidth - 1, 10)); model.resizeSelection(new Point(viewWidth - 2, 11)); assertSelected(new int[0]); - assertEquals(BandSelectModel.NOT_SET, model.getPositionNearestOrigin()); + assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin()); } @Test @@ -83,7 +84,7 @@ public class BandSelectModelTest { model.startSelection(new Point(10, 0)); model.resizeSelection(new Point(11, 1)); assertSelected(new int[0]); - assertEquals(BandSelectModel.NOT_SET, model.getPositionNearestOrigin()); + assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin()); } @Test @@ -92,7 +93,7 @@ public class BandSelectModelTest { model.startSelection(new Point(10, VIEWPORT_HEIGHT - 1)); model.resizeSelection(new Point(11, VIEWPORT_HEIGHT - 2)); assertSelected(new int[0]); - assertEquals(BandSelectModel.NOT_SET, model.getPositionNearestOrigin()); + assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin()); } @Test @@ -101,7 +102,7 @@ public class BandSelectModelTest { model.startSelection(new Point(106, 0)); model.resizeSelection(new Point(107, 200)); assertSelected(new int[0]); - assertEquals(BandSelectModel.NOT_SET, model.getPositionNearestOrigin()); + assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin()); } @Test @@ -110,7 +111,7 @@ public class BandSelectModelTest { model.startSelection(new Point(0, 105)); model.resizeSelection(new Point(200, 106)); assertSelected(new int[0]); - assertEquals(BandSelectModel.NOT_SET, model.getPositionNearestOrigin()); + assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin()); } @Test @@ -141,7 +142,7 @@ public class BandSelectModelTest { assertSelected(new int[] {0}); model.resizeSelection(new Point(0, 0)); assertSelected(new int[0]); - assertEquals(BandSelectModel.NOT_SET, model.getPositionNearestOrigin()); + assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin()); } @Test @@ -191,7 +192,7 @@ public class BandSelectModelTest { model.onScrolled(null, 0, dy); } - private static final class TestHelper implements MultiSelectManager.BandModelHelper { + private static final class TestHelper implements MultiSelectManager.BandEnvironment { public int horizontalOffset = 0; public int verticalOffset = 0; @@ -269,18 +270,53 @@ public class BandSelectModelTest { } @Override - public int getTotalChildCount() { + public int getChildCount() { return mNumChildren; } @Override - public int getNumColumns() { + public int getColumnCount() { return mNumColumns; } @Override - public int getNumRows() { + public int getRowCount() { return mNumRows; } + + @Override + public void showBand(Rect rect) { + throw new UnsupportedOperationException(); + } + + @Override + public void hideBand() { + throw new UnsupportedOperationException(); + } + + @Override + public void scrollBy(int dy) { + throw new UnsupportedOperationException(); + } + + @Override + public int getHeight() { + throw new UnsupportedOperationException(); + } + + @Override + public void invalidateView() { + throw new UnsupportedOperationException(); + } + + @Override + public void runAtNextFrame(Runnable r) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeCallback(Runnable r) { + throw new UnsupportedOperationException(); + } } } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UnitTests.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UnitTests.java index d90130f4bd5a..be3f2515057e 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/UnitTests.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UnitTests.java @@ -22,7 +22,7 @@ import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ - BandSelectModelTest.class, + MultiSelectManager_GridModelTest.class, MultiSelectManager_SelectionTest.class, MultiSelectManagerTest.class }) diff --git a/packages/Keyguard/res/values-nl/strings.xml b/packages/Keyguard/res/values-nl/strings.xml index 541389524330..35caf779e6f6 100644 --- a/packages/Keyguard/res/values-nl/strings.xml +++ b/packages/Keyguard/res/values-nl/strings.xml @@ -42,7 +42,7 @@ <string name="keyguard_missing_sim_instructions" msgid="5210891509995942250">"Plaats een simkaart."</string> <string name="keyguard_missing_sim_instructions_long" msgid="5968985489463870358">"De simkaart ontbreekt of kan niet worden gelezen. Plaats een simkaart."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="8340813989586622356">"Onbruikbare simkaart."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="5892940909699723544">"Uw simkaart is permanent uitgeschakeld.\n Neem contact op met uw mobiele serviceprovider voor een nieuwe simkaart."</string> + <string name="keyguard_permanent_disabled_sim_instructions" msgid="5892940909699723544">"Je simkaart is permanent uitgeschakeld.\n Neem contact op met je mobiele serviceprovider voor een nieuwe simkaart."</string> <string name="keyguard_sim_locked_message" msgid="6875773413306380902">"Simkaart is vergrendeld."</string> <string name="keyguard_sim_puk_locked_message" msgid="3747232467471801633">"Simkaart is vergrendeld met PUK-code."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="7975221805033614426">"Simkaart ontgrendelen…"</string> @@ -62,13 +62,13 @@ <string name="kg_wrong_password" msgid="2333281762128113157">"Onjuist wachtwoord"</string> <string name="kg_wrong_pin" msgid="1131306510833563801">"Onjuiste pincode"</string> <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Probeer het over <xliff:g id="NUMBER">%d</xliff:g> seconden opnieuw."</string> - <string name="kg_pattern_instructions" msgid="398978611683075868">"Teken uw patroon"</string> + <string name="kg_pattern_instructions" msgid="398978611683075868">"Teken je patroon"</string> <string name="kg_sim_pin_instructions" msgid="2319508550934557331">"Geef de pincode van de simkaart op"</string> <string name="kg_sim_pin_instructions_multi" msgid="7818515973197201434">"Voer de pincode in voor de simkaart van \'<xliff:g id="CARRIER">%1$s</xliff:g>\'"</string> <string name="kg_pin_instructions" msgid="2377242233495111557">"Pincode opgeven"</string> <string name="kg_password_instructions" msgid="5753646556186936819">"Wachtwoord invoeren"</string> <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"De simkaart is nu uitgeschakeld. Geef de PUK-code op om door te gaan. Neem contact op met de provider voor informatie."</string> - <string name="kg_puk_enter_puk_hint_multi" msgid="363822494559783025">"Simkaart van \'<xliff:g id="CARRIER">%1$s</xliff:g>\' is nu uitgeschakeld. Voer de PUK-code in om door te gaan. Neem contact op met uw provider voor meer informatie."</string> + <string name="kg_puk_enter_puk_hint_multi" msgid="363822494559783025">"Simkaart van \'<xliff:g id="CARRIER">%1$s</xliff:g>\' is nu uitgeschakeld. Voer de PUK-code in om door te gaan. Neem contact op met je provider voor meer informatie."</string> <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"Gewenste pincode opgeven"</string> <string name="kg_enter_confirm_pin_hint" msgid="325676184762529976">"Gewenste pincode bevestigen"</string> <string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Simkaart ontgrendelen..."</string> @@ -77,32 +77,32 @@ <string name="kg_invalid_puk" msgid="3638289409676051243">"Geef de juiste PUK-code opnieuw op. Bij herhaalde pogingen wordt de simkaart permanent uitgeschakeld."</string> <string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Pincodes komen niet overeen"</string> <string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Te veel patroonpogingen"</string> - <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"U heeft uw pincode <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getypt. \n\nProbeer het opnieuw over <xliff:g id="NUMBER_1">%d</xliff:g> seconden."</string> - <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"U heeft uw wachtwoord <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getypt. \n\nProbeer het opnieuw over <xliff:g id="NUMBER_1">%d</xliff:g> seconden."</string> - <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"U heeft uw ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. \n\nProbeer het opnieuw over <xliff:g id="NUMBER_1">%d</xliff:g> seconden."</string> - <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="8774056606869646621">"U heeft <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt deze tablet gereset, waardoor alle gegevens worden verwijderd."</string> - <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="1843331751334128428">"U heeft <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt deze telefoon gereset, waardoor alle gegevens worden verwijderd."</string> - <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="258925501999698032">"U heeft <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Deze tablet wordt gereset, waardoor alle gegevens worden verwijderd."</string> - <string name="kg_failed_attempts_now_wiping" product="default" msgid="7154028908459817066">"U heeft <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Deze telefoon wordt gereset, waardoor alle gegevens worden verwijderd."</string> - <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="6159955099372112688">"U heeft <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt deze gebruiker verwijderd, waardoor alle gebruikersgegevens worden verwijderd."</string> - <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="6945823186629369880">"U heeft <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt deze gebruiker verwijderd, waardoor alle gebruikersgegevens worden verwijderd."</string> - <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="3963486905355778734">"U heeft <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Deze gebruiker wordt verwijderd, waardoor alle gebruikersgegevens worden verwijderd."</string> - <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="7729009752252111673">"U heeft <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Deze gebruiker wordt verwijderd, waardoor alle gebruikersgegevens worden verwijderd."</string> - <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="4621778507387853694">"U heeft <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt het werkprofiel verwijderd, waardoor alle profielgegevens worden verwijderd."</string> - <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="6853071165802933545">"U heeft <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt het werkprofiel verwijderd, waardoor alle profielgegevens worden verwijderd."</string> - <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4686386497449912146">"U heeft <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Het werkprofiel wordt verwijderd, waardoor alle profielgegevens worden verwijderd."</string> - <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4951507352869831265">"U heeft <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Het werkprofiel wordt verwijderd, waardoor alle profielgegevens worden verwijderd."</string> - <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"U heeft uw ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt u gevraagd uw tablet te ontgrendelen via een e-mailaccount.\n\n Probeer het over <xliff:g id="NUMBER_2">%d</xliff:g> seconden opnieuw."</string> - <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"U heeft uw ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt u gevraagd uw telefoon te ontgrendelen via een e-mailaccount.\n\n Probeer het over <xliff:g id="NUMBER_2">%d</xliff:g> seconden opnieuw."</string> - <string name="kg_password_wrong_pin_code_pukked" msgid="30531039455764924">"Onjuiste pincode voor simkaart. U moet nu contact opnemen met uw provider om uw apparaat te ontgrendelen."</string> + <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Je hebt je pincode <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getypt. \n\nProbeer het opnieuw over <xliff:g id="NUMBER_1">%d</xliff:g> seconden."</string> + <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Je hebt je wachtwoord <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getypt. \n\nProbeer het opnieuw over <xliff:g id="NUMBER_1">%d</xliff:g> seconden."</string> + <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Je hebt je ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. \n\nProbeer het opnieuw over <xliff:g id="NUMBER_1">%d</xliff:g> seconden."</string> + <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="8774056606869646621">"Je hebt <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt deze tablet gereset, waardoor alle gegevens worden verwijderd."</string> + <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="1843331751334128428">"Je hebt <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt deze telefoon gereset, waardoor alle gegevens worden verwijderd."</string> + <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="258925501999698032">"Je hebt <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Deze tablet wordt gereset, waardoor alle gegevens worden verwijderd."</string> + <string name="kg_failed_attempts_now_wiping" product="default" msgid="7154028908459817066">"Je hebt <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Deze telefoon wordt gereset, waardoor alle gegevens worden verwijderd."</string> + <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="6159955099372112688">"Je hebt <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt deze gebruiker verwijderd, waardoor alle gebruikersgegevens worden verwijderd."</string> + <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="6945823186629369880">"Je hebt <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt deze gebruiker verwijderd, waardoor alle gebruikersgegevens worden verwijderd."</string> + <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="3963486905355778734">"Je hebt <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Deze gebruiker wordt verwijderd, waardoor alle gebruikersgegevens worden verwijderd."</string> + <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="7729009752252111673">"Je hebt <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Deze gebruiker wordt verwijderd, waardoor alle gebruikersgegevens worden verwijderd."</string> + <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="4621778507387853694">"Je hebt <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt het werkprofiel verwijderd, waardoor alle profielgegevens worden verwijderd."</string> + <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="6853071165802933545">"Je hebt <xliff:g id="NUMBER_0">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt het werkprofiel verwijderd, waardoor alle profielgegevens worden verwijderd."</string> + <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4686386497449912146">"Je hebt <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de tablet te ontgrendelen. Het werkprofiel wordt verwijderd, waardoor alle profielgegevens worden verwijderd."</string> + <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4951507352869831265">"Je hebt <xliff:g id="NUMBER">%d</xliff:g> mislukte pogingen ondernomen om de telefoon te ontgrendelen. Het werkprofiel wordt verwijderd, waardoor alle profielgegevens worden verwijderd."</string> + <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Je hebt je ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt u gevraagd je tablet te ontgrendelen via een e-mailaccount.\n\n Probeer het over <xliff:g id="NUMBER_2">%d</xliff:g> seconden opnieuw."</string> + <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Je hebt je ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen wordt u gevraagd je telefoon te ontgrendelen via een e-mailaccount.\n\n Probeer het over <xliff:g id="NUMBER_2">%d</xliff:g> seconden opnieuw."</string> + <string name="kg_password_wrong_pin_code_pukked" msgid="30531039455764924">"Onjuiste pincode voor simkaart. U moet nu contact opnemen met je provider om je apparaat te ontgrendelen."</string> <plurals name="kg_password_wrong_pin_code" formatted="false" msgid="6721575017538162249"> - <item quantity="other">Onjuiste pincode voor simkaart. U heeft nog <xliff:g id="NUMBER_1">%d</xliff:g> pogingen over.</item> - <item quantity="one">Onjuiste pincode voor simkaart. U heeft nog <xliff:g id="NUMBER_0">%d</xliff:g> poging over voordat u contact met uw provider moet opnemen om uw apparaat te ontgrendelen.</item> + <item quantity="other">Onjuiste pincode voor simkaart. Je hebt nog <xliff:g id="NUMBER_1">%d</xliff:g> pogingen over.</item> + <item quantity="one">Onjuiste pincode voor simkaart. Je hebt nog <xliff:g id="NUMBER_0">%d</xliff:g> poging over voordat u contact met je provider moet opnemen om je apparaat te ontgrendelen.</item> </plurals> - <string name="kg_password_wrong_puk_code_dead" msgid="7077536808291316208">"Simkaart is onbruikbaar. Neem contact op met uw provider."</string> + <string name="kg_password_wrong_puk_code_dead" msgid="7077536808291316208">"Simkaart is onbruikbaar. Neem contact op met je provider."</string> <plurals name="kg_password_wrong_puk_code" formatted="false" msgid="7576227366999858780"> - <item quantity="other">Onjuiste pukcode voor simkaart. U heeft nog <xliff:g id="NUMBER_1">%d</xliff:g> pogingen over voordat de simkaart definitief onbruikbaar wordt.</item> - <item quantity="one">Onjuiste pukcode voor simkaart. U heeft nog <xliff:g id="NUMBER_0">%d</xliff:g> poging over voordat de simkaart definitief onbruikbaar wordt.</item> + <item quantity="other">Onjuiste pukcode voor simkaart. Je hebt nog <xliff:g id="NUMBER_1">%d</xliff:g> pogingen over voordat de simkaart definitief onbruikbaar wordt.</item> + <item quantity="one">Onjuiste pukcode voor simkaart. Je hebt nog <xliff:g id="NUMBER_0">%d</xliff:g> poging over voordat de simkaart definitief onbruikbaar wordt.</item> </plurals> <string name="kg_password_pin_failed" msgid="6268288093558031564">"Bewerking met pincode voor simkaart mislukt."</string> <string name="kg_password_puk_failed" msgid="2838824369502455984">"Bewerking met pukcode voor simkaart is mislukt."</string> diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java index b752c9be466e..47d8e283257b 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1032,7 +1032,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } private boolean shouldListenForFingerprint() { - return (mKeyguardIsVisible || !mDeviceInteractive) && !mSwitchingUser + return (mKeyguardIsVisible || !mDeviceInteractive || mBouncer) && !mSwitchingUser && !mFingerprintAlreadyAuthenticated && !isFingerprintDisabled(getCurrentUser()); } @@ -1365,6 +1365,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { cb.onKeyguardBouncerChanged(isBouncer); } } + updateFingerprintListeningState(); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 632a867bd32c..cff8c236378e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -771,6 +771,10 @@ public class AccessPoint implements Comparable<AccessPoint> { } } + void setRssi(int rssi) { + mRssi = rssi; + } + public static String getSummary(Context context, String ssid, DetailedState state, boolean isEphemeral, String passpointProvider) { if (state == DetailedState.CONNECTED && ssid == null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index c28288eb91b1..d7dfdf857eaf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -312,6 +312,8 @@ public class WifiTracker { connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId()); } + final Collection<ScanResult> results = fetchScanResults(); + final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); if (configs != null) { mSavedNetworksExist = configs.size() != 0; @@ -326,8 +328,20 @@ public class WifiTracker { } } if (mIncludeSaved) { - if (!config.isPasspoint() || mIncludePasspoints) + if (!config.isPasspoint() || mIncludePasspoints) { + // If saved network not present in scan result then set its Rssi to MAX_VALUE + boolean apFound = false; + for (ScanResult result : results) { + if (result.SSID.equals(accessPoint.getSsidStr())) { + apFound = true; + break; + } + } + if (!apFound) { + accessPoint.setRssi(Integer.MAX_VALUE); + } accessPoints.add(accessPoint); + } if (config.isPasspoint() == false) { apMap.put(accessPoint.getSsidStr(), accessPoint); @@ -340,7 +354,6 @@ public class WifiTracker { } } - final Collection<ScanResult> results = fetchScanResults(); if (results != null) { for (ScanResult result : results) { // Ignore hidden and ad-hoc networks. diff --git a/packages/Shell/res/values-nl/strings.xml b/packages/Shell/res/values-nl/strings.xml index 2936387964bc..b0dba4d7cf43 100644 --- a/packages/Shell/res/values-nl/strings.xml +++ b/packages/Shell/res/values-nl/strings.xml @@ -18,8 +18,8 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="3701846017049540910">"Shell"</string> <string name="bugreport_finished_title" msgid="2293711546892863898">"Foutenrapport vastgelegd"</string> - <string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Veeg naar links om uw bugmelding te delen"</string> - <string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Raak aan om uw foutenrapport te delen"</string> + <string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Veeg naar links om je bugmelding te delen"</string> + <string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Raak aan om je foutenrapport te delen"</string> <string name="bugreport_confirm" msgid="5130698467795669780">"Foutenrapporten bevatten gegevens uit de verschillende logbestanden van het systeem, waaronder persoonlijke en privégegevens. Deel foutenrapporten alleen met apps en mensen die u vertrouwt."</string> <string name="bugreport_confirm_repeat" msgid="4926842460688645058">"Dit bericht de volgende keer weergeven"</string> <string name="bugreport_storage_title" msgid="5332488144740527109">"Foutenrapporten"</string> diff --git a/packages/SystemUI/res/anim/fab_elevation.xml b/packages/SystemUI/res/anim/fab_elevation.xml new file mode 100644 index 000000000000..2c76a865a470 --- /dev/null +++ b/packages/SystemUI/res/anim/fab_elevation.xml @@ -0,0 +1,35 @@ +<?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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="true" android:state_pressed="true"> + <set> + <objectAnimator + android:duration="@android:integer/config_shortAnimTime" + android:propertyName="translationZ" + android:valueTo="@dimen/fab_press_translation_z" + android:valueType="floatType" /> + </set> + </item> + <item> + <set> + <objectAnimator + android:duration="@android:integer/config_shortAnimTime" + android:propertyName="translationZ" + android:valueTo="0" + android:valueType="floatType" /> + </set> + </item> +</selector> diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_ime_land.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_ime_land.png Binary files differdeleted file mode 100644 index 165ef4fb8a0f..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_ime_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index f95f09fab9f8..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_home_land.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_home_land.png Binary files differdeleted file mode 100644 index 860a906aa337..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_home_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_menu_land.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_menu_land.png Binary files differdeleted file mode 100644 index bcb203e40f06..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_menu_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_recent_land.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_recent_land.png Binary files differdeleted file mode 100644 index bab268e6a34d..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_recent_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index 2f4dbbee5741..000000000000 --- a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index d04d84f5d76b..000000000000 --- a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index 1500ae58969b..000000000000 --- a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index a7fec49f76a1..000000000000 --- a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_ime_land.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_ime_land.png Binary files differdeleted file mode 100644 index 0feb405f4c28..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_ime_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index cabab0da4c36..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home_land.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home_land.png Binary files differdeleted file mode 100644 index 16e1bf532cfa..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_menu_land.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_menu_land.png Binary files differdeleted file mode 100644 index 94c9743f293f..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_menu_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_recent_land.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_recent_land.png Binary files differdeleted file mode 100644 index 40375dec70f8..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_recent_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_ime_land.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_ime_land.png Binary files differdeleted file mode 100644 index b7b8f989c4e8..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_ime_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index 69b7449b78f5..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_home_land.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_home_land.png Binary files differdeleted file mode 100644 index 57d243c94c1a..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_home_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_menu_land.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_menu_land.png Binary files differdeleted file mode 100644 index 8a7ac4f9ab31..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_menu_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_recent_land.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_recent_land.png Binary files differdeleted file mode 100644 index e53eaffa862f..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_recent_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_ime_land.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_ime_land.png Binary files differdeleted file mode 100644 index 695e7a464bfe..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_ime_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index 88294c05fcb2..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_home_land.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_home_land.png Binary files differdeleted file mode 100644 index 09d684a14e8e..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_home_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_menu_land.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_menu_land.png Binary files differdeleted file mode 100644 index 62f44e892858..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_menu_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_recent_land.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_recent_land.png Binary files differdeleted file mode 100644 index e31ea3282681..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_recent_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_ime_land.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_ime_land.png Binary files differdeleted file mode 100644 index 24f12d717603..000000000000 --- a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_ime_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_land.png Binary files differdeleted file mode 100644 index 51482f505e9f..000000000000 --- a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_home_land.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_home_land.png Binary files differdeleted file mode 100644 index 46c7b189ddb7..000000000000 --- a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_home_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_recent_land.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_recent_land.png Binary files differdeleted file mode 100644 index 396ad7d2e39b..000000000000 --- a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_recent_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable/fab_background.xml b/packages/SystemUI/res/drawable/fab_background.xml new file mode 100644 index 000000000000..7f23f2b63e41 --- /dev/null +++ b/packages/SystemUI/res/drawable/fab_background.xml @@ -0,0 +1,23 @@ +<?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. +--> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/fab_ripple"> + <item> + <shape> + <solid android:color="@color/fab_shape" /> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/drawable/ic_add.xml b/packages/SystemUI/res/drawable/ic_add.xml new file mode 100644 index 000000000000..c573592f82dd --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_add.xml @@ -0,0 +1,24 @@ +<!-- + 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="24.0dp" + android:height="24.0dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_close_white.xml b/packages/SystemUI/res/drawable/ic_close_white.xml new file mode 100644 index 000000000000..ce64047e5de4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_close_white.xml @@ -0,0 +1,24 @@ +<!-- + 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="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z" + android:fillColor="#FFFFFFFF"/> +</vector> diff --git a/packages/SystemUI/res/layout/horizontal_divider.xml b/packages/SystemUI/res/layout/horizontal_divider.xml new file mode 100644 index 000000000000..a060f08039e1 --- /dev/null +++ b/packages/SystemUI/res/layout/horizontal_divider.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. +--> +<View + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginTop="10dp" + android:layout_marginBottom="10dp" + android:layout_marginStart="40dp" + android:layout_marginEnd="40dp" + android:background="#4dffffff" /> diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index c92ba450be37..d58664f76ab4 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -213,7 +213,7 @@ android:layout_width="match_parent" android:layout_height="40dp" android:contentDescription="@string/accessibility_menu" - android:src="@drawable/ic_sysbar_menu_land" + android:src="@drawable/ic_sysbar_menu" android:scaleType="centerInside" android:layout_gravity="top" android:visibility="invisible" @@ -223,7 +223,7 @@ <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps" android:layout_height="@dimen/navigation_key_width" android:layout_width="match_parent" - android:src="@drawable/ic_sysbar_recent_land" + android:src="@drawable/ic_sysbar_recent" android:scaleType="center" android:layout_weight="0" android:contentDescription="@string/accessibility_recent" @@ -237,7 +237,7 @@ <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home" android:layout_height="@dimen/navigation_key_width" android:layout_width="match_parent" - android:src="@drawable/ic_sysbar_home_land" + android:src="@drawable/ic_sysbar_home" android:scaleType="center" systemui:keyCode="3" systemui:keyRepeat="false" @@ -253,7 +253,7 @@ <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back" android:layout_height="@dimen/navigation_key_width" android:layout_width="match_parent" - android:src="@drawable/ic_sysbar_back_land" + android:src="@drawable/ic_sysbar_back" android:scaleType="center" systemui:keyCode="4" android:layout_weight="0" diff --git a/packages/SystemUI/res/layout/qs_customize_layout.xml b/packages/SystemUI/res/layout/qs_customize_layout.xml new file mode 100644 index 000000000000..91cf89483c96 --- /dev/null +++ b/packages/SystemUI/res/layout/qs_customize_layout.xml @@ -0,0 +1,39 @@ +<?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. +--> +<com.android.systemui.qs.customize.NonPagedTileLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tiles_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <com.android.systemui.qs.QuickTileLayout + android:id="@+id/quick_tile_layout" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_quick_actions_height" + android:orientation="horizontal" + android:paddingStart="@dimen/qs_quick_actions_padding" + android:paddingEnd="@dimen/qs_quick_actions_padding" /> + + <view + class="com.android.systemui.qs.PagedTileLayout$TilePage" + android:id="@+id/tile_page" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</com.android.systemui.qs.customize.NonPagedTileLayout> + diff --git a/packages/SystemUI/res/layout/qs_customize_panel.xml b/packages/SystemUI/res/layout/qs_customize_panel.xml new file mode 100644 index 000000000000..dc928c77baaa --- /dev/null +++ b/packages/SystemUI/res/layout/qs_customize_panel.xml @@ -0,0 +1,105 @@ +<?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. +--> +<com.android.systemui.qs.customize.QSCustomizer + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="@dimen/navigation_bar_size" + android:background="?android:attr/windowBackground"> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/colorPrimary"> + + <LinearLayout + android:id="@+id/drag_buttons" + android:layout_width="match_parent" + android:layout_height="fill_parent" + android:orientation="horizontal"> + <FrameLayout + android:layout_width="0dp" + android:layout_height="fill_parent" + android:layout_weight="1"> + <com.android.systemui.qs.customize.DropButton + android:id="@+id/info_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:drawableStart="@drawable/ic_info" + android:drawablePadding="10dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="@android:color/white" + android:text="@string/qs_customize_info" /> + </FrameLayout> + <FrameLayout + android:layout_width="0dp" + android:layout_height="fill_parent" + android:layout_weight="1"> + <com.android.systemui.qs.customize.DropButton + android:id="@+id/remove_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:drawableStart="@drawable/ic_close_white" + android:drawablePadding="10dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="@android:color/white" + android:text="@string/qs_customize_remove" /> + </FrameLayout> + </LinearLayout> + + <Toolbar + android:id="@*android:id/action_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:navigationContentDescription="@*android:string/action_bar_up_description" + style="?android:attr/toolbarStyle" + android:background="?android:attr/colorPrimary" /> + </FrameLayout> + + <com.android.systemui.tuner.AutoScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:elevation="2dp"> + + <com.android.systemui.qs.customize.CustomQSPanel + android:id="@+id/quick_settings_panel" + android:background="#0000" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </com.android.systemui.tuner.AutoScrollView> + + <com.android.systemui.qs.customize.FloatingActionButton + android:id="@+id/fab" + android:clickable="true" + android:layout_width="@dimen/fab_size" + android:layout_height="@dimen/fab_size" + android:layout_gravity="bottom|end" + android:layout_marginEnd="@dimen/fab_margin" + android:layout_marginBottom="@dimen/fab_margin" + android:elevation="@dimen/fab_elevation" + android:background="@drawable/fab_background" /> + +</com.android.systemui.qs.customize.QSCustomizer> diff --git a/packages/SystemUI/res/layout/shelf_menu_anchor.xml b/packages/SystemUI/res/layout/shelf_menu_anchor.xml new file mode 100644 index 000000000000..984f65539f97 --- /dev/null +++ b/packages/SystemUI/res/layout/shelf_menu_anchor.xml @@ -0,0 +1,24 @@ +<?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. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:alpha="0"> + <ImageView android:id="@+id/shelf_menu_anchor_anchor" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:alpha="0"/> +</FrameLayout> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 7def415f9bdb..cc37162f99c1 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -354,7 +354,7 @@ <string name="user_remove_user_title" msgid="4681256956076895559">"Vols suprimir l\'usuari?"</string> <string name="user_remove_user_message" msgid="1453218013959498039">"Totes les aplicacions i les dades d\'aquest usuari se suprimiran."</string> <string name="user_remove_user_remove" msgid="7479275741742178297">"Suprimeix"</string> - <string name="battery_saver_notification_title" msgid="237918726750955859">"Estalvi de bateria activada"</string> + <string name="battery_saver_notification_title" msgid="237918726750955859">"Estalvi de bateria activat"</string> <string name="battery_saver_notification_text" msgid="820318788126672692">"Redueix el rendiment i l\'ús de les dades en segon pla."</string> <string name="battery_saver_notification_action_text" msgid="109158658238110382">"Desactiva l\'estalvi de bateria"</string> <string name="notification_hidden_text" msgid="1135169301897151909">"Contingut amagat"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index fa01c47df918..7593f46e6c70 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -34,14 +34,14 @@ <string name="status_bar_latest_events_title" msgid="6594767438577593172">"اعلانها"</string> <string name="battery_low_title" msgid="6456385927409742437">"شارژ باتری کم است"</string> <string name="battery_low_percent_format" msgid="2900940511201380775">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است"</string> - <string name="battery_low_percent_format_saver_started" msgid="6859235584035338833">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است. ذخیره کننده باتری روشن است."</string> + <string name="battery_low_percent_format_saver_started" msgid="6859235584035338833">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است. بهینهسازی باتری روشن است."</string> <string name="invalid_charger" msgid="4549105996740522523">"شارژ USB پشتیبانی نمیشود.\nفقط از شارژر ارائه شده استفاده کنید."</string> <string name="invalid_charger_title" msgid="3515740382572798460">"شارژ با USB پشتیبانی نمیشود."</string> <string name="invalid_charger_text" msgid="5474997287953892710">"فقط از شارژر ارائه شده استفاده کنید."</string> <string name="battery_low_why" msgid="4553600287639198111">"تنظیمات"</string> - <string name="battery_saver_confirmation_title" msgid="5299585433050361634">"ذخیرهکننده باتری روشن شود؟"</string> + <string name="battery_saver_confirmation_title" msgid="5299585433050361634">"بهینهسازی باتری روشن شود؟"</string> <string name="battery_saver_confirmation_ok" msgid="7507968430447930257">"روشن کردن"</string> - <string name="battery_saver_start_action" msgid="5576697451677486320">"ذخیرهکننده باتری را روشن کنید"</string> + <string name="battery_saver_start_action" msgid="5576697451677486320">"بهینهسازی باتری را روشن کنید"</string> <string name="status_bar_settings_settings_button" msgid="3023889916699270224">"تنظیمات"</string> <string name="status_bar_settings_wifi_button" msgid="1733928151698311923">"Wi-Fi"</string> <string name="status_bar_settings_auto_rotation" msgid="3790482541357798421">"چرخش خودکار صفحه"</string> @@ -352,9 +352,9 @@ <string name="user_remove_user_title" msgid="4681256956076895559">"کاربر حذف شود؟"</string> <string name="user_remove_user_message" msgid="1453218013959498039">"همه برنامهها و دادههای این کاربر حذف میشود."</string> <string name="user_remove_user_remove" msgid="7479275741742178297">"حذف"</string> - <string name="battery_saver_notification_title" msgid="237918726750955859">"ذخیره کننده باتری روشن است."</string> + <string name="battery_saver_notification_title" msgid="237918726750955859">"بهینهسازی باتری روشن است."</string> <string name="battery_saver_notification_text" msgid="820318788126672692">"عملکرد و اطلاعات پسزمینه را کاهش میدهد"</string> - <string name="battery_saver_notification_action_text" msgid="109158658238110382">"خاموش کردن ذخیرهکننده باتری"</string> + <string name="battery_saver_notification_action_text" msgid="109158658238110382">"بهینهسازی باتری را خاموش کنید"</string> <string name="notification_hidden_text" msgid="1135169301897151909">"محتواها پنهان هستند"</string> <string name="media_projection_dialog_text" msgid="3071431025448218928">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> شروع به ضبط هر چیزی میکند که در صفحهنمایش شما نمایش داده میشود."</string> <string name="media_projection_remember_text" msgid="3103510882172746752">"دوباره نشان داده نشود"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 13f02d59fae0..79f3333ead53 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -23,7 +23,7 @@ <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wissen"</string> <string name="status_bar_recent_remove_item_title" msgid="6026395868129852968">"Verwijderen uit lijst"</string> <string name="status_bar_recent_inspect_item_title" msgid="7793624864528818569">"App-info"</string> - <string name="status_bar_no_recent_apps" msgid="7374907845131203189">"Uw recente schermen worden hier weergegeven"</string> + <string name="status_bar_no_recent_apps" msgid="7374907845131203189">"Je recente schermen worden hier weergegeven"</string> <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"Recente apps negeren"</string> <plurals name="status_bar_accessibility_recent_apps" formatted="false" msgid="9138535907802238759"> <item quantity="other">%d schermen in Overzicht</item> @@ -71,9 +71,9 @@ <string name="screenshot_saving_title" msgid="8242282144535555697">"Screenshot opslaan..."</string> <string name="screenshot_saving_text" msgid="2419718443411738818">"Screenshot wordt opgeslagen."</string> <string name="screenshot_saved_title" msgid="6461865960961414961">"Screenshot gemaakt."</string> - <string name="screenshot_saved_text" msgid="1152839647677558815">"Raak aan om uw screenshot te bekijken."</string> + <string name="screenshot_saved_text" msgid="1152839647677558815">"Raak aan om je screenshot te bekijken."</string> <string name="screenshot_failed_title" msgid="705781116746922771">"Screenshot is niet gemaakt."</string> - <string name="screenshot_failed_text" msgid="1260203058661337274">"Kan geen screenshot maken wegens beperkte opslagruimte of omdat de app of uw organisatie dit niet toestaat."</string> + <string name="screenshot_failed_text" msgid="1260203058661337274">"Kan geen screenshot maken wegens beperkte opslagruimte of omdat de app of je organisatie dit niet toestaat."</string> <string name="usb_preference_title" msgid="6551050377388882787">"Opties voor USB-bestandsoverdracht"</string> <string name="use_mtp_button_title" msgid="4333504413563023626">"Koppelen als mediaspeler (MTP)"</string> <string name="use_ptp_button_title" msgid="7517127540301625751">"Koppelen als camera (PTP)"</string> @@ -88,7 +88,7 @@ <string name="accessibility_voice_assist_button" msgid="487611083884852965">"Spraakassistent"</string> <string name="accessibility_unlock_button" msgid="128158454631118828">"Ontgrendelen"</string> <string name="accessibility_unlock_button_fingerprint" msgid="8214125623493923751">"Knop Ontgrendelen, wacht op vingerafdruk"</string> - <string name="accessibility_unlock_without_fingerprint" msgid="7541705575183694446">"Ontgrendelen zonder uw vingerafdruk te gebruiken"</string> + <string name="accessibility_unlock_without_fingerprint" msgid="7541705575183694446">"Ontgrendelen zonder je vingerafdruk te gebruiken"</string> <string name="unlock_label" msgid="8779712358041029439">"ontgrendelen"</string> <string name="phone_label" msgid="2320074140205331708">"telefoon openen"</string> <string name="voice_assist_label" msgid="3956854378310019854">"spraakassistent openen"</string> @@ -217,7 +217,7 @@ <string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G-data zijn onderbroken"</string> <string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobiele gegevens zijn onderbroken"</string> <string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Gegevens zijn onderbroken"</string> - <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Omdat de ingestelde gegevenslimiet is bereikt, heeft het apparaat het gegevensverbruik onderbroken voor de rest van deze cyclus.\n\nAls u het gegevensverbruik hervat, kan uw provider kosten in rekening brengen."</string> + <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Omdat de ingestelde gegevenslimiet is bereikt, heeft het apparaat het gegevensverbruik onderbroken voor de rest van deze cyclus.\n\nAls u het gegevensverbruik hervat, kan je provider kosten in rekening brengen."</string> <string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Hervatten"</string> <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Geen internetverbinding"</string> <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Verbonden via wifi"</string> @@ -289,7 +289,7 @@ <string name="quick_settings_cellular_detail_data_used" msgid="1476810587475761478">"<xliff:g id="DATA_USED">%s</xliff:g> gebruikt"</string> <string name="quick_settings_cellular_detail_data_limit" msgid="56011158504994128">"Limiet van <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string> <string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Waarschuwing voor <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string> - <string name="recents_empty_message" msgid="8682129509540827999">"Uw recente schermen worden hier weergegeven"</string> + <string name="recents_empty_message" msgid="8682129509540827999">"Je recente schermen worden hier weergegeven"</string> <string name="recents_app_info_button_label" msgid="2890317189376000030">"App-informatie"</string> <string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"scherm vastzetten"</string> <string name="recents_search_bar_label" msgid="8074997400187836677">"zoeken"</string> @@ -338,7 +338,7 @@ <string name="guest_exit_guest_dialog_message" msgid="4155503224769676625">"Alle apps en gegevens in deze sessie worden verwijderd."</string> <string name="guest_exit_guest_dialog_remove" msgid="7402231963862520531">"Verwijderen"</string> <string name="guest_wipe_session_title" msgid="6419439912885956132">"Welkom terug, gast!"</string> - <string name="guest_wipe_session_message" msgid="8476238178270112811">"Wilt u doorgaan met uw sessie?"</string> + <string name="guest_wipe_session_message" msgid="8476238178270112811">"Wilt u doorgaan met je sessie?"</string> <string name="guest_wipe_session_wipe" msgid="5065558566939858884">"Opnieuw starten"</string> <string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"Ja, doorgaan"</string> <string name="guest_notification_title" msgid="1585278533840603063">"Gastgebruiker"</string> @@ -356,7 +356,7 @@ <string name="battery_saver_notification_text" msgid="820318788126672692">"Vermindert de prestaties en achtergrondgegevens"</string> <string name="battery_saver_notification_action_text" msgid="109158658238110382">"Accubesparing uitschakelen"</string> <string name="notification_hidden_text" msgid="1135169301897151909">"Inhoud verborgen"</string> - <string name="media_projection_dialog_text" msgid="3071431025448218928">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> gaat alles vastleggen dat wordt weergegeven op uw scherm."</string> + <string name="media_projection_dialog_text" msgid="3071431025448218928">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> gaat alles vastleggen dat wordt weergegeven op je scherm."</string> <string name="media_projection_remember_text" msgid="3103510882172746752">"Niet opnieuw weergeven"</string> <string name="clear_all_notifications_text" msgid="814192889771462828">"Alles wissen"</string> <string name="media_projection_action_text" msgid="8470872969457985954">"Nu starten"</string> @@ -369,16 +369,16 @@ <string name="monitoring_title" msgid="169206259253048106">"Netwerkcontrole"</string> <string name="disable_vpn" msgid="4435534311510272506">"VPN uitschakelen"</string> <string name="disconnect_vpn" msgid="1324915059568548655">"Verbinding met VPN verbreken"</string> - <string name="monitoring_description_device_owned" msgid="5780988291898461883">"Uw apparaat wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nUw beheerder kan instellingen, bedrijfstoegang, apps, gegevens voor uw apparaat en locatiegegevens voor uw apparaat controleren en beheren. Neem voor meer informatie contact op met uw beheerder."</string> - <string name="monitoring_description_vpn" msgid="4445150119515393526">"U heeft een app toestemming gegeven voor het instellen van een VPN-verbinding.\n\nMet deze app kan uw apparaat- en netwerkactiviteit worden gecontroleerd, inclusief e-mails, apps en websites."</string> - <string name="monitoring_description_vpn_device_owned" msgid="3090670777499161246">"Uw apparaat wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nUw beheerder kan instellingen, bedrijfstoegang, apps, gegevens voor uw apparaat en locatiegegevens voor uw apparaat controleren en beheren.\n\nU bent verbonden met een VPN, die uw netwerkactiviteit kan controleren, waaronder e-mails, apps en websites.\n\nNeem voor meer informatie contact op met uw beheerder."</string> - <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Uw werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nUw beheerder kan uw netwerkactiviteit controleren, inclusief e-mails, apps en websites.\n\nNeem contact op met uw beheerder voor meer informatie.\n\nU bent ook verbonden met een VPN waarmee uw netwerkactiviteit kan worden gecontroleerd."</string> + <string name="monitoring_description_device_owned" msgid="5780988291898461883">"Je apparaat wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nJe beheerder kan instellingen, bedrijfstoegang, apps, gegevens voor je apparaat en locatiegegevens voor je apparaat controleren en beheren. Neem voor meer informatie contact op met je beheerder."</string> + <string name="monitoring_description_vpn" msgid="4445150119515393526">"Je hebt een app toestemming gegeven voor het instellen van een VPN-verbinding.\n\nMet deze app kan je apparaat- en netwerkactiviteit worden gecontroleerd, inclusief e-mails, apps en websites."</string> + <string name="monitoring_description_vpn_device_owned" msgid="3090670777499161246">"Je apparaat wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nJe beheerder kan instellingen, bedrijfstoegang, apps, gegevens voor je apparaat en locatiegegevens voor je apparaat controleren en beheren.\n\nU bent verbonden met een VPN, die je netwerkactiviteit kan controleren, waaronder e-mails, apps en websites.\n\nNeem voor meer informatie contact op met je beheerder."</string> + <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nJe beheerder kan je netwerkactiviteit controleren, inclusief e-mails, apps en websites.\n\nNeem contact op met je beheerder voor meer informatie.\n\nU bent ook verbonden met een VPN waarmee je netwerkactiviteit kan worden gecontroleerd."</string> <string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string> - <string name="monitoring_description_app" msgid="6259179342284742878">"U bent verbonden met <xliff:g id="APPLICATION">%1$s</xliff:g>, waarmee uw netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites."</string> - <string name="monitoring_description_app_personal" msgid="484599052118316268">"U bent verbonden met <xliff:g id="APPLICATION">%1$s</xliff:g>, waarmee uw persoonlijke netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites."</string> - <string name="monitoring_description_app_work" msgid="1754325860918060897">"Uw werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Deze is verbonden met <xliff:g id="APPLICATION">%2$s</xliff:g>, waarmee uw werkgerelateerde netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nNeem contact op met uw beheerder voor meer informatie."</string> - <string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Uw werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Deze is verbonden met <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, waarmee uw werkgerelateerde netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nU bent ook verbonden met <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, waarmee uw persoonlijke netwerkactiviteit kan worden gecontroleerd."</string> - <string name="monitoring_description_vpn_app_device_owned" msgid="4970443827043261703">"Uw apparaat wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nUw beheerder kan instellingen, zakelijke toegang, apps, gekoppelde apparaatgegevens en locatiegegevens voor uw apparaat controleren en beheren.\n\nU bent verbonden met <xliff:g id="APPLICATION">%2$s</xliff:g> waarmee uw netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nNeem contact op met uw beheerder voor meer informatie."</string> + <string name="monitoring_description_app" msgid="6259179342284742878">"U bent verbonden met <xliff:g id="APPLICATION">%1$s</xliff:g>, waarmee je netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites."</string> + <string name="monitoring_description_app_personal" msgid="484599052118316268">"U bent verbonden met <xliff:g id="APPLICATION">%1$s</xliff:g>, waarmee je persoonlijke netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites."</string> + <string name="monitoring_description_app_work" msgid="1754325860918060897">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Deze is verbonden met <xliff:g id="APPLICATION">%2$s</xliff:g>, waarmee je werkgerelateerde netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nNeem contact op met je beheerder voor meer informatie."</string> + <string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Deze is verbonden met <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, waarmee je werkgerelateerde netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nU bent ook verbonden met <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, waarmee je persoonlijke netwerkactiviteit kan worden gecontroleerd."</string> + <string name="monitoring_description_vpn_app_device_owned" msgid="4970443827043261703">"Je apparaat wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nJe beheerder kan instellingen, zakelijke toegang, apps, gekoppelde apparaatgegevens en locatiegegevens voor je apparaat controleren en beheren.\n\nU bent verbonden met <xliff:g id="APPLICATION">%2$s</xliff:g> waarmee je netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nNeem contact op met je beheerder voor meer informatie."</string> <string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Het apparaat blijft vergrendeld totdat u het handmatig ontgrendelt"</string> <string name="hidden_notifications_title" msgid="7139628534207443290">"Sneller meldingen ontvangen"</string> <string name="hidden_notifications_text" msgid="2326409389088668981">"Weergeven voordat u ontgrendelt"</string> @@ -403,7 +403,7 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Afwijzen"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is het volumedialoogvenster"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Tik hierop om het origineel te herstellen."</string> - <string name="managed_profile_foreground_toast" msgid="5421487114739245972">"U gebruikt uw werkprofiel"</string> + <string name="managed_profile_foreground_toast" msgid="5421487114739245972">"U gebruikt je werkprofiel"</string> <string name="system_ui_tuner" msgid="708224127392452018">"Systeem-UI-tuner"</string> <string name="show_battery_percentage" msgid="5444136600512968798">"Percentage ingebouwde accu weergeven"</string> <string name="show_battery_percentage_summary" msgid="3215025775576786037">"Accupercentage weergeven in het pictogram op de statusbalk wanneer er niet wordt opgeladen"</string> @@ -418,8 +418,8 @@ <string name="status_bar_airplane" msgid="7057575501472249002">"Vliegtuigmodus"</string> <string name="add_tile" msgid="2995389510240786221">"Tegel toevoegen"</string> <string name="broadcast_tile" msgid="3894036511763289383">"Tegel \'Uitzenden\'"</string> - <string name="zen_alarm_warning_indef" msgid="3482966345578319605">"U hoort uw volgende alarm niet <xliff:g id="WHEN">%1$s</xliff:g> tenzij u dit voor die tijd uitschakelt"</string> - <string name="zen_alarm_warning" msgid="444533119582244293">"U hoort uw volgende alarm niet <xliff:g id="WHEN">%1$s</xliff:g>"</string> + <string name="zen_alarm_warning_indef" msgid="3482966345578319605">"U hoort je volgende alarm niet <xliff:g id="WHEN">%1$s</xliff:g> tenzij u dit voor die tijd uitschakelt"</string> + <string name="zen_alarm_warning" msgid="444533119582244293">"U hoort je volgende alarm niet <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="3980063409350522735">"om <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="4242179982586714810">"op <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Snelle instellingen, <xliff:g id="TITLE">%s</xliff:g>."</string> @@ -432,7 +432,7 @@ <string name="tuner_toast" msgid="603429811084428439">"Systeem-UI-tuner is toegevoegd aan Instellingen"</string> <string name="remove_from_settings" msgid="8389591916603406378">"Verwijderen uit Instellingen"</string> <string name="remove_from_settings_prompt" msgid="6069085993355887748">"Systeem-UI-tuner uit Instellingen verwijderen en het gebruik van alle functies daarvan stopzetten?"</string> - <string name="activity_not_found" msgid="348423244327799974">"Deze app is niet geïnstalleerd op uw apparaat"</string> + <string name="activity_not_found" msgid="348423244327799974">"Deze app is niet geïnstalleerd op je apparaat"</string> <string name="clock_seconds" msgid="7689554147579179507">"Klokseconden weergeven"</string> <string name="clock_seconds_desc" msgid="6282693067130470675">"Klokseconden op de statusbalk weergeven. Kan van invloed zijn op de accuduur."</string> <string name="qs_rearrange" msgid="8060918697551068765">"Snelle instellingen opnieuw indelen"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index e3f0fb32c964..2e41b2969dd2 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -36,7 +36,7 @@ <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Upozornenia"</string> <string name="battery_low_title" msgid="6456385927409742437">"Batéria je takmer vybitá"</string> <string name="battery_low_percent_format" msgid="2900940511201380775">"Zostáva <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> - <string name="battery_low_percent_format_saver_started" msgid="6859235584035338833">"Zostáva <xliff:g id="PERCENTAGE">%s</xliff:g>. Úspora batérie je zapnutá."</string> + <string name="battery_low_percent_format_saver_started" msgid="6859235584035338833">"Zostáva <xliff:g id="PERCENTAGE">%s</xliff:g>. Šetrič batérie je zapnutý."</string> <string name="invalid_charger" msgid="4549105996740522523">"Nabíjanie pomocou rozhrania USB nie je podporované.\nPoužívajte iba nabíjačku, ktorá bola dodaná spolu so zariadením."</string> <string name="invalid_charger_title" msgid="3515740382572798460">"Nabíjanie prostredníctvom USB nie je podporované."</string> <string name="invalid_charger_text" msgid="5474997287953892710">"Používajte iba originálnu nabíjačku."</string> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 195fdb1642be..1af4ab1f4b15 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -98,4 +98,6 @@ In sw600dp we want the buttons centered so this fills the space, (screen_pinning_request_width - 3 * screen_pinning_request_button_width) / 2 --> <dimen name="screen_pinning_request_side_width">8dp</dimen> + + <dimen name="fab_margin">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index fdad0d554a54..49edb5746548 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -203,10 +203,10 @@ <string name="accessibility_quick_settings_close" msgid="3115847794692516306">"Paneli kapat."</string> <string name="accessibility_quick_settings_more_time" msgid="3659274935356197708">"Daha uzun süre."</string> <string name="accessibility_quick_settings_less_time" msgid="2404728746293515623">"Daha kısa süre."</string> - <string name="accessibility_quick_settings_flashlight_off" msgid="4936432000069786988">"Flaş ışığı kapalı."</string> - <string name="accessibility_quick_settings_flashlight_on" msgid="2003479320007841077">"Flaş ışığı açık."</string> - <string name="accessibility_quick_settings_flashlight_changed_off" msgid="3303701786768224304">"Flaş ışığı kapatıldı."</string> - <string name="accessibility_quick_settings_flashlight_changed_on" msgid="6531793301533894686">"Flaş ışığı açıldı."</string> + <string name="accessibility_quick_settings_flashlight_off" msgid="4936432000069786988">"El feneri kapalı."</string> + <string name="accessibility_quick_settings_flashlight_on" msgid="2003479320007841077">"El feneri açık."</string> + <string name="accessibility_quick_settings_flashlight_changed_off" msgid="3303701786768224304">"El feneri kapatıldı."</string> + <string name="accessibility_quick_settings_flashlight_changed_on" msgid="6531793301533894686">"El feneri açıldı."</string> <string name="accessibility_quick_settings_color_inversion_changed_off" msgid="4406577213290173911">"Renkleri ters çevirme işlevi kapatıldı."</string> <string name="accessibility_quick_settings_color_inversion_changed_on" msgid="6897462320184911126">"Renkleri ters çevirme işlevi açıldı."</string> <string name="accessibility_quick_settings_hotspot_changed_off" msgid="5004708003447561394">"Mobil hotspot kapatıldı."</string> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 0dcbe884b30f..da210843bd28 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -143,4 +143,7 @@ <color name="volume_icon_color">#ffffffff</color> <color name="volume_settings_icon_color">#7fffffff</color> <color name="volume_slider_inactive">#FFB0BEC5</color><!-- blue grey 200 --> + + <color name="fab_ripple">#1fffffff</color><!-- 12% white --> + <color name="fab_shape">#ff009688</color><!-- Teal 500 --> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3817741f9b71..96a77256bc42 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -590,4 +590,9 @@ <!-- Thickness of the shadows of the assist disclosure beams --> <dimen name="assist_disclosure_shadow_thickness">1.5dp</dimen> + + <dimen name="fab_size">56dp</dimen> + <dimen name="fab_margin">16dp</dimen> + <dimen name="fab_elevation">12dp</dimen> + <dimen name="fab_press_translation_z">9dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b732e9914f2e..db1e688c1244 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1156,4 +1156,9 @@ settings are --> <string name="experimental">Experimental</string> + <string name="save" translatable="false">Save</string> + <string name="qs_customize" translatable="false">Allow long-press customize in Quick Settings</string> + <string name="qs_customize_info" translatable="false">Info</string> + <string name="qs_customize_remove" translatable="false">Remove</string> + </resources> diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index 59801089d1bd..07e7688d15b7 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -37,6 +37,10 @@ android:key="qs_paged_panel" android:title="@string/qs_paging" /> + <com.android.systemui.tuner.TunerSwitch + android:key="qs_allow_customize" + android:title="@string/qs_customize" /> + </PreferenceCategory> </PreferenceScreen> diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index b0e2afae1bcb..1dca14928c6d 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -112,7 +112,9 @@ public class ExpandHelper implements Gefingerpoken { public boolean onScaleBegin(ScaleGestureDetector detector) { if (DEBUG_SCALE) Log.v(TAG, "onscalebegin()"); - startExpanding(mResizedView, STRETCH); + if (!mOnlyMovements) { + startExpanding(mResizedView, STRETCH); + } return mExpanding; } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 9265b631e486..6f262228da8f 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -1,5 +1,7 @@ package com.android.systemui.assist; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.SearchManager; @@ -9,9 +11,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.PixelFormat; -import android.media.AudioAttributes; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -21,7 +21,6 @@ import android.provider.Settings; import android.service.voice.VoiceInteractionSession; import android.util.Log; import android.view.Gravity; -import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,10 +32,6 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import java.io.FileDescriptor; -import java.io.PrintWriter; /** * Class to manage everything related to assist in SystemUI. @@ -58,8 +53,6 @@ public class AssistManager { private final BaseStatusBar mBar; private final AssistUtils mAssistUtils; - private ComponentName mAssistComponent; - private IVoiceInteractionSessionShowCallback mShowCallback = new IVoiceInteractionSessionShowCallback.Stub() { @@ -82,23 +75,11 @@ public class AssistManager { } }; - private final ContentObserver mAssistSettingsObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - updateAssistInfo(); - } - }; - public AssistManager(BaseStatusBar bar, Context context) { mContext = context; mBar = bar; mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mAssistUtils = new AssistUtils(context); - - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false, - mAssistSettingsObserver); - mAssistSettingsObserver.onChange(false); mAssistDisclosure = new AssistDisclosure(context, new Handler()); } @@ -123,19 +104,19 @@ public class AssistManager { } public void startAssist(Bundle args) { - updateAssistInfo(); - if (mAssistComponent == null) { + final ComponentName assistComponent = getAssistInfo(); + if (assistComponent == null) { return; } - final boolean isService = isAssistantService(); + final boolean isService = assistComponent.equals(getVoiceInteractorComponentName()); if (!isService || !isVoiceSessionRunning()) { - showOrb(); + showOrb(assistComponent, isService); mView.postDelayed(mHideRunnable, isService ? TIMEOUT_SERVICE : TIMEOUT_ACTIVITY); } - startAssistInternal(args); + startAssistInternal(args, assistComponent, isService); } public void hideAssist() { @@ -161,22 +142,21 @@ public class AssistManager { return lp; } - private void showOrb() { - maybeSwapSearchIcon(); + private void showOrb(@NonNull ComponentName assistComponent, boolean isService) { + maybeSwapSearchIcon(assistComponent, isService); mView.show(true /* show */, true /* animate */); } - private void startAssistInternal(Bundle args) { - if (mAssistComponent != null) { - if (isAssistantService()) { - startVoiceInteractor(args); - } else { - startAssistActivity(args); - } + private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent, + boolean isService) { + if (isService) { + startVoiceInteractor(args); + } else { + startAssistActivity(args, assistComponent); } } - private void startAssistActivity(Bundle args) { + private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) { if (!mBar.isDeviceProvisioned()) { return; } @@ -193,9 +173,7 @@ public class AssistManager { if (intent == null) { return; } - if (mAssistComponent != null) { - intent.setComponent(mAssistComponent); - } + intent.setComponent(assistComponent); intent.putExtras(args); if (structureEnabled) { @@ -243,13 +221,9 @@ public class AssistManager { mWindowManager.removeViewImmediate(mView); } - private void maybeSwapSearchIcon() { - if (mAssistComponent != null) { - replaceDrawable(mView.getOrb().getLogo(), mAssistComponent, ASSIST_ICON_METADATA_NAME, - isAssistantService()); - } else { - mView.getOrb().getLogo().setImageDrawable(null); - } + private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) { + replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME, + isService); } public void replaceDrawable(ImageView v, ComponentName component, String name, @@ -283,28 +257,15 @@ public class AssistManager { v.setImageDrawable(null); } - private boolean isAssistantService() { - return mAssistComponent == null ? - false : mAssistComponent.equals(getVoiceInteractorComponentName()); - } - - private void updateAssistInfo() { - mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT); - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("AssistManager state:"); - pw.print(" mAssistComponent="); pw.println(mAssistComponent); + @Nullable + private ComponentName getAssistInfo() { + return mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT); } public void showDisclosure() { mAssistDisclosure.postShow(); } - public void onUserSwitched(int newUserId) { - updateAssistInfo(); - } - public void onLockscreenShown() { mAssistUtils.onLockscreenShown(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index ece70227cf45..c612600ac82e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -75,8 +75,8 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override public void setTileVisibility(TileRecord tile, int visibility) { tile.tileView.setVisibility(visibility); - // TODO: Do something smarter here. - distributeTiles(); +// // TODO: Do something smarter here. +// distributeTiles(); } @Override @@ -183,13 +183,17 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mAllowDual = false; } + public void setMaxRows(int maxRows) { + mMaxRows = maxRows; + } + private void clear() { if (DEBUG) Log.d(TAG, "Clearing page"); removeAllViews(); mRecords.clear(); } - private boolean isFull() { + public boolean isFull() { return mRecords.size() >= mColumns * mMaxRows; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 683af97bfb82..880349e232fe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -39,6 +39,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.qs.QSTile.DetailAdapter; +import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.settings.BrightnessController; import com.android.systemui.settings.ToggleSlider; import com.android.systemui.statusbar.phone.QSTileHost; @@ -54,8 +55,9 @@ public class QSPanel extends FrameLayout implements Tunable { public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness"; public static final String QS_PAGED_PANEL = "qs_paged_panel"; + public static final String QS_ALLOW_CUSTOMIZE = "qs_allow_customize"; - private final Context mContext; + protected final Context mContext; protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>(); private final View mDetail; private final ViewGroup mDetailContent; @@ -79,8 +81,10 @@ public class QSPanel extends FrameLayout implements Tunable { private QSFooter mFooter; private boolean mGridContentVisible = true; - private LinearLayout mQsContainer; - private QSTileLayout mTileLayout; + protected LinearLayout mQsContainer; + protected QSTileLayout mTileLayout; + + private QSCustomizer mCustomizePanel; public QSPanel(Context context) { this(context, null); @@ -131,7 +135,8 @@ public class QSPanel extends FrameLayout implements Tunable { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - TunerService.get(mContext).addTunable(this, QS_SHOW_BRIGHTNESS, QS_PAGED_PANEL); + TunerService.get(mContext).addTunable(this, + QS_SHOW_BRIGHTNESS, QS_PAGED_PANEL, QS_ALLOW_CUSTOMIZE); } @Override @@ -160,6 +165,17 @@ public class QSPanel extends FrameLayout implements Tunable { for (int i = 0; i < mRecords.size(); i++) { mTileLayout.addTile(mRecords.get(i)); } + } else if (QS_ALLOW_CUSTOMIZE.equals(key)) { + if (newValue != null && Integer.parseInt(newValue) != 0) { + mCustomizePanel = (QSCustomizer) LayoutInflater.from(mContext) + .inflate(R.layout.qs_customize_panel, null); + mCustomizePanel.setHost(mHost); + } else { + if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) { + mCustomizePanel.hide(); + } + mCustomizePanel = null; + } } } @@ -224,6 +240,12 @@ public class QSPanel extends FrameLayout implements Tunable { mFooter.onConfigurationChanged(); } + public void onCollapse() { + if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) { + mCustomizePanel.hide(); + } + } + public void setExpanded(boolean expanded) { if (mExpanded == expanded) return; mExpanded = expanded; @@ -307,7 +329,7 @@ public class QSPanel extends FrameLayout implements Tunable { r.tileView.onStateChanged(state); } - private void addTile(final QSTile<?> tile) { + protected void addTile(final QSTile<?> tile) { final TileRecord r = new TileRecord(); r.tile = tile; r.tileView = tile.createTileView(mContext); @@ -358,7 +380,13 @@ public class QSPanel extends FrameLayout implements Tunable { final View.OnLongClickListener longClick = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { - r.tile.longClick(); + if (mCustomizePanel != null) { + if (!mCustomizePanel.isCustomizing()) { + mCustomizePanel.show(); + } + } else { + r.tile.longClick(); + } return true; } }; @@ -374,10 +402,16 @@ public class QSPanel extends FrameLayout implements Tunable { } public boolean isShowingDetail() { - return mDetailRecord != null; + return mDetailRecord != null + || (mCustomizePanel != null && mCustomizePanel.isCustomizing()); } public void closeDetail() { + if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) { + // Treat this as a detail panel for now, to make things easy. + mCustomizePanel.hide(); + return; + } showDetail(false, mDetailRecord); } @@ -527,7 +561,7 @@ public class QSPanel extends FrameLayout implements Tunable { int y; } - protected static final class TileRecord extends Record { + public static final class TileRecord extends Record { public QSTile<?> tile; public QSTileView tileView; public int row; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 9b3372c386ac..3b3593b4c477 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -65,6 +65,8 @@ public abstract class QSTile<TState extends State> implements Listenable { private TState mTmpState = newTileState(); private boolean mAnnounceNextStateChange; + private String mTileSpec; + abstract protected TState newTileState(); abstract protected void handleClick(); abstract protected void handleUpdateState(TState state, Object arg); @@ -83,7 +85,15 @@ public abstract class QSTile<TState extends State> implements Listenable { mContext = host.getContext(); mHandler = new H(host.getLooper()); } - + + public String getTileSpec() { + return mTileSpec; + } + + public void setTileSpec(String tileSpec) { + mTileSpec = tileSpec; + } + public int getTileType() { return QSTileView.QS_TYPE_NORMAL; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index e0c39c5b975d..8bd05fad5699 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -54,6 +54,11 @@ public class TileLayout extends ViewGroup implements QSTileLayout { removeView(tile.tileView); } + public void removeAllViews() { + mRecords.clear(); + super.removeAllViews(); + } + @Override public void setTileVisibility(TileRecord tile, int visibility) { tile.tileView.setVisibility(visibility); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java new file mode 100644 index 000000000000..8866e558d6fb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java @@ -0,0 +1,82 @@ +/* + * 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.systemui.qs.customize; + +import android.content.ClipData; +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.systemui.R; +import com.android.systemui.qs.QSPanel; +import com.android.systemui.qs.QSTile; +import com.android.systemui.statusbar.phone.QSTileHost; + +/** + * A version of QSPanel that allows tiles to be dragged around rather than + * clicked on. Dragging starting and receiving is handled in the NonPagedTileLayout, + * and the saving/ordering is handled by the CustomQSTileHost. + */ +public class CustomQSPanel extends QSPanel { + + private CustomQSTileHost mCustomHost; + + public CustomQSPanel(Context context, AttributeSet attrs) { + super(context, attrs); + mTileLayout = (QSTileLayout) LayoutInflater.from(mContext) + .inflate(R.layout.qs_customize_layout, mQsContainer, false); + mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */); + ((NonPagedTileLayout) mTileLayout).setCustomQsPanel(this); + } + + @Override + public void setHost(QSTileHost host) { + super.setHost(host); + mCustomHost = (CustomQSTileHost) host; + } + + @Override + public void onTuningChanged(String key, String newValue) { + if (key.equals(QS_SHOW_BRIGHTNESS)) { + // No Brightness for you. + super.onTuningChanged(key, "0"); + } + } + + public CustomQSTileHost getCustomHost() { + return mCustomHost; + } + + public void tileSelected(QSTile<?> tile, ClipData currentClip) { + String sourceSpec = getSpec(currentClip); + String destSpec = tile.getTileSpec(); + if (!sourceSpec.equals(destSpec)) { + mCustomHost.moveTo(sourceSpec, destSpec); + } + } + + public ClipData getClip(QSTile<?> tile) { + String tileSpec = tile.getTileSpec(); + // TODO: Something better than plain text. + // TODO: Once using something better than plain text, stop listening to non-QS drag events. + return ClipData.newPlainText(tileSpec, tileSpec); + } + + public String getSpec(ClipData data) { + return data.getItemAt(0).getText().toString(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java new file mode 100644 index 000000000000..84b05d0055ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java @@ -0,0 +1,183 @@ +/* + * 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.systemui.qs.customize; + +import android.app.ActivityManager; +import android.content.Context; +import android.provider.Settings.Secure; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.qs.QSTile; +import com.android.systemui.statusbar.phone.QSTileHost; +import com.android.systemui.statusbar.policy.SecurityController; + +import java.util.ArrayList; +import java.util.List; + +/** + * @see CustomQSPanel + */ +public class CustomQSTileHost extends QSTileHost { + + private static final String TAG = "CustomHost"; + private List<String> mTiles; + private List<String> mSavedTiles; + private ArrayList<String> mStash; + + public CustomQSTileHost(Context context, QSTileHost host) { + super(context, null, host.getBluetoothController(), host.getLocationController(), + host.getRotationLockController(), host.getNetworkController(), + host.getZenModeController(), host.getHotspotController(), host.getCastController(), + host.getFlashlightController(), host.getUserSwitcherController(), + host.getKeyguardMonitor(), new BlankSecurityController()); + } + + @Override + protected QSTile<?> createTile(String tileSpec) { + QSTile<?> tile = super.createTile(tileSpec); + tile.setTileSpec(tileSpec); + return tile; + } + + @Override + public void onTuningChanged(String key, String newValue) { + // No Tunings For You. + if (TILES_SETTING.equals(key)) { + mSavedTiles = super.loadTileSpecs(newValue); + } + } + + public void setSavedTiles() { + setTiles(mSavedTiles); + } + + public void saveCurrentTiles() { + Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING, + TextUtils.join(",", mTiles), ActivityManager.getCurrentUser()); + } + + public void stashCurrentTiles() { + mStash = new ArrayList<>(mTiles); + } + + public void unstashTiles() { + setTiles(mStash); + } + + public void moveTo(String from, String to) { + int fromIndex = mTiles.indexOf(from); + if (fromIndex < 0) { + Log.e(TAG, "Unknown from tile " + from); + return; + } + int index = mTiles.indexOf(to); + if (index < 0) { + Log.e(TAG, "Unknown to tile " + to); + return; + } + mTiles.remove(fromIndex); + mTiles.add(index, from); + super.onTuningChanged(TILES_SETTING, null); + } + + public void remove(String spec) { + if (!mTiles.remove(spec)) { + Log.e(TAG, "Unknown remove spec " + spec); + } + super.onTuningChanged(TILES_SETTING, null); + } + + public void setTiles(List<String> tiles) { + mTiles = new ArrayList<>(tiles); + super.onTuningChanged(TILES_SETTING, null); + } + + @Override + protected List<String> loadTileSpecs(String tileList) { + return mTiles; + } + + public void replace(String oldTile, String newTile) { + if (oldTile.equals(newTile)) { + return; + } + MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + "," + + newTile); + List<String> order = new ArrayList<>(mTileSpecs); + int index = order.indexOf(oldTile); + if (index < 0) { + Log.e(TAG, "Can't find " + oldTile); + return; + } + order.remove(newTile); + order.add(index, newTile); + setTiles(order); + } + + /** + * Blank so that the customizing QS view doesn't show any security messages in the footer. + */ + private static class BlankSecurityController implements SecurityController { + @Override + public boolean hasDeviceOwner() { + return false; + } + + @Override + public boolean hasProfileOwner() { + return false; + } + + @Override + public String getDeviceOwnerName() { + return null; + } + + @Override + public String getProfileOwnerName() { + return null; + } + + @Override + public boolean isVpnEnabled() { + return false; + } + + @Override + public String getPrimaryVpnName() { + return null; + } + + @Override + public String getProfileVpnName() { + return null; + } + + @Override + public void onUserSwitched(int newUserId) { + } + + @Override + public void addCallback(SecurityControllerCallback callback) { + } + + @Override + public void removeCallback(SecurityControllerCallback callback) { + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java new file mode 100644 index 000000000000..0e15f2b12116 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package com.android.systemui.qs.customize; + +import android.content.ClipData; +import android.content.Context; +import android.util.AttributeSet; +import android.view.DragEvent; +import android.view.View; +import android.view.View.OnDragListener; +import android.widget.TextView; + +public class DropButton extends TextView implements OnDragListener { + + private OnDropListener mListener; + + public DropButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + // TODO: Don't do this, instead make this view the right size... + ((View) getParent()).setOnDragListener(this); + } + + public void setOnDropListener(OnDropListener listener) { + mListener = listener; + } + + private void setHovering(boolean hovering) { + setAlpha(hovering ? .5f : 1); + } + + @Override + public boolean onDrag(View v, DragEvent event) { + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_ENTERED: + setHovering(true); + break; + case DragEvent.ACTION_DROP: + if (mListener != null) { + mListener.onDrop(this, event.getClipData()); + } + case DragEvent.ACTION_DRAG_EXITED: + case DragEvent.ACTION_DRAG_ENDED: + setHovering(false); + break; + } + return true; + } + + public interface OnDropListener { + void onDrop(View v, ClipData data); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java new file mode 100644 index 000000000000..8791a1007adc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java @@ -0,0 +1,49 @@ +/* + * 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.systemui.qs.customize; + +import android.animation.AnimatorInflater; +import android.content.Context; +import android.graphics.Outline; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.widget.ImageView; + +import com.android.systemui.R; + +public class FloatingActionButton extends ImageView { + + public FloatingActionButton(Context context, AttributeSet attrs) { + super(context, attrs); + setScaleType(ScaleType.CENTER); + setStateListAnimator(AnimatorInflater.loadStateListAnimator(context, R.anim.fab_elevation)); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, getWidth(), getHeight()); + } + }); + setClipToOutline(true); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + invalidateOutline(); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java new file mode 100644 index 000000000000..012633c6860f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java @@ -0,0 +1,203 @@ +/* + * 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.systemui.qs.customize; + +import android.content.ClipData; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.DragEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.widget.LinearLayout; + +import com.android.systemui.R; +import com.android.systemui.qs.PagedTileLayout; +import com.android.systemui.qs.PagedTileLayout.TilePage; +import com.android.systemui.qs.QSPanel.QSTileLayout; +import com.android.systemui.qs.QSPanel.TileRecord; +import com.android.systemui.qs.QSTile; +import com.android.systemui.qs.QSTileView; +import com.android.systemui.qs.QuickTileLayout; + +import java.util.ArrayList; + +/** + * Similar to PagedTileLayout, except that instead of pages it lays them out + * vertically and expects to be inside a ScrollView. + * @see CustomQSPanel + */ +public class NonPagedTileLayout extends LinearLayout implements QSTileLayout, OnTouchListener { + + private QuickTileLayout mQuickTiles; + private final ArrayList<TilePage> mPages = new ArrayList<>(); + private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>(); + private CustomQSPanel mPanel; + private final Rect mHitRect = new Rect(); + + private ClipData mCurrentClip; + private View mCurrentView; + + public NonPagedTileLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mQuickTiles = (QuickTileLayout) findViewById(R.id.quick_tile_layout); + TilePage page = (PagedTileLayout.TilePage) findViewById(R.id.tile_page); + page.setMaxRows(3 /* First page only gets 3 */); + mPages.add(page); + } + + public void setCustomQsPanel(CustomQSPanel qsPanel) { + mPanel = qsPanel; + } + + @Override + public void addTile(TileRecord record) { + mTiles.add(record); + distributeTiles(); + if (record.tile.getTileType() == QSTileView.QS_TYPE_QUICK + || record.tileView.getTag() == record.tile) { + return; + } + record.tileView.setTag(record.tile); + record.tileView.setVisibility(View.VISIBLE); + record.tileView.init(null, null, null); + record.tileView.setOnTouchListener(this); + if (mCurrentClip != null + && mCurrentClip.getItemAt(0).getText().toString().equals(record.tile.getTileSpec())) { + record.tileView.setAlpha(.3f); + mCurrentView = record.tileView; + } + } + + @Override + public void removeTile(TileRecord tile) { + if (mTiles.remove(tile)) { + distributeTiles(); + } + } + + private void distributeTiles() { + mQuickTiles.removeAllViews(); + final int NP = mPages.size(); + for (int i = 0; i < NP; i++) { + mPages.get(i).removeAllViews(); + } + int index = 0; + final int NT = mTiles.size(); + for (int i = 0; i < NT; i++) { + TileRecord tile = mTiles.get(i); + if (tile.tile.getTileType() == QSTileView.QS_TYPE_QUICK) { + tile.tileView.setType(QSTileView.QS_TYPE_QUICK); + mQuickTiles.addView(tile.tileView); + continue; + } + mPages.get(index).addTile(tile); + if (mPages.get(index).isFull()) { + if (++index == mPages.size()) { + LayoutInflater inflater = LayoutInflater.from(mContext); + inflater.inflate(R.layout.horizontal_divider, this); + mPages.add((TilePage) inflater.inflate(R.layout.qs_paged_page, this, false)); + addView(mPages.get(mPages.size() - 1)); + } + } + } + } + + @Override + public void setTileVisibility(TileRecord tile, int visibility) { + // All tiles visible here, so that they can be re-arranged. + tile.tileView.setVisibility(View.VISIBLE); + } + + @Override + public int getOffsetTop(TileRecord tile) { + // No touch feedback, so this isn't required. + return 0; + } + + @Override + public void updateResources() { + } + + @Override + public boolean onDragEvent(DragEvent event) { + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_LOCATION: + float x = event.getX(); + float y = event.getY(); + if (contains(mQuickTiles, x, y)) { + // TODO: Reset to pre-drag state. + } else { + final int NP = mPages.size(); + for (int i = 0; i < NP; i++) { + TilePage page = mPages.get(i); + if (contains(page, x, y)) { + x -= page.getLeft(); + y -= page.getTop(); + final int NC = page.getChildCount(); + for (int j = 0; j < NC; j++) { + View child = page.getChildAt(j); + if (contains(child, x, y)) { + mPanel.tileSelected((QSTile<?>) child.getTag(), mCurrentClip); + } + } + break; + } + } + } + break; + case DragEvent.ACTION_DRAG_ENDED: + onDragEnded(); + break; + } + return true; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // Stash the current tiles, in case the drop is on info, that we can restore + // the previous state. + mPanel.getCustomHost().stashCurrentTiles(); + mCurrentView = v; + mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag()); + View.DragShadowBuilder shadow = new View.DragShadowBuilder(v); + ((View) getParent().getParent()).startDrag(mCurrentClip, shadow, null, 0); + v.setAlpha(.3f); + return true; + } + return false; + } + + public void onDragEnded() { + mCurrentView.setAlpha(1f); + mCurrentView = null; + mCurrentClip = null; + } + + private boolean contains(View v, float x, float y) { + v.getHitRect(mHitRect); + return mHitRect.contains((int) x, (int) y); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java new file mode 100644 index 000000000000..7e7478545a07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -0,0 +1,200 @@ +/* + * 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.systemui.qs.customize; + +import android.content.ClipData; +import android.content.Context; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.DragEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.Toolbar; +import android.widget.Toolbar.OnMenuItemClickListener; + +import com.android.systemui.R; +import com.android.systemui.SystemUIApplication; +import com.android.systemui.qs.QSTile.Host.Callback; +import com.android.systemui.qs.customize.DropButton.OnDropListener; +import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.phone.QSTileHost; +import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.tuner.QSPagingSwitch; + +import java.util.ArrayList; + +/** + * Allows full-screen customization of QS, through show() and hide(). + * + * This adds itself to the status bar window, so it can appear on top of quick settings and + * *someday* do fancy animations to get into/out of it. + */ +public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback, + OnDropListener, OnClickListener { + + private static final int MENU_SAVE = Menu.FIRST; + private static final int MENU_RESET = Menu.FIRST + 1; + + private PhoneStatusBar mPhoneStatusBar; + + private Toolbar mToolbar; + private ViewGroup mDragButtons; + private CustomQSPanel mQsPanel; + + private boolean isShown; + private CustomQSTileHost mHost; + private DropButton mInfoButton; + private DropButton mRemoveButton; + private FloatingActionButton mFab; + + public QSCustomizer(Context context, AttributeSet attrs) { + super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs); + mPhoneStatusBar = ((SystemUIApplication) mContext.getApplicationContext()) + .getComponent(PhoneStatusBar.class); + } + + public void setHost(QSTileHost host) { + mHost = new CustomQSTileHost(mContext, host); + mHost.setCallback(this); + mQsPanel.setTiles(mHost.getTiles()); + mQsPanel.setHost(mHost); + mHost.setSavedTiles(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mToolbar = (Toolbar) findViewById(com.android.internal.R.id.action_bar); + TypedValue value = new TypedValue(); + mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true); + mToolbar.setNavigationIcon( + getResources().getDrawable(value.resourceId, mContext.getTheme())); + mToolbar.setNavigationOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // TODO: Is this all we want...? + hide(); + } + }); + mToolbar.setOnMenuItemClickListener(this); + mToolbar.getMenu().add(Menu.NONE, MENU_SAVE, 0, mContext.getString(R.string.save)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, + mContext.getString(com.android.internal.R.string.reset)); + + mQsPanel = (CustomQSPanel) findViewById(R.id.quick_settings_panel); + + mDragButtons = (ViewGroup) findViewById(R.id.drag_buttons); + setDragging(false); + + mInfoButton = (DropButton) findViewById(R.id.info_button); + mInfoButton.setOnDropListener(this); + mRemoveButton = (DropButton) findViewById(R.id.remove_button); + mRemoveButton.setOnDropListener(this); + + mFab = (FloatingActionButton) findViewById(R.id.fab); + mFab.setImageResource(R.drawable.ic_add); + mFab.setOnClickListener(this); + } + + public void show() { + isShown = true; + mHost.setSavedTiles(); + // TODO: Fancy shmancy reveal. + mPhoneStatusBar.getStatusBarWindow().addView(this); + } + + public void hide() { + isShown = false; + // TODO: Similarly awesome or better hide. + mPhoneStatusBar.getStatusBarWindow().removeView(this); + } + + public boolean isCustomizing() { + return isShown; + } + + private void reset() { + ArrayList<String> tiles = new ArrayList<>(); + for (String tile : QSPagingSwitch.QS_PAGE_TILES.split(",")) { + tiles.add(tile); + } + mHost.setTiles(tiles); + } + + private void setDragging(boolean dragging) { + mToolbar.setVisibility(!dragging ? View.VISIBLE : View.INVISIBLE); + } + + private void save() { + mHost.saveCurrentTiles(); + hide(); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case MENU_SAVE: + save(); + break; + case MENU_RESET: + reset(); + break; + } + return true; + } + + @Override + public void onTilesChanged() { + mQsPanel.setTiles(mHost.getTiles()); + } + + public boolean onDragEvent(DragEvent event) { + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + setDragging(true); + break; + case DragEvent.ACTION_DRAG_ENDED: + setDragging(false); + break; + } + return true; + } + + public void onDrop(View v, ClipData data) { + if (v == mRemoveButton) { + mHost.remove(mQsPanel.getSpec(data)); + } else if (v == mInfoButton) { + mHost.unstashTiles(); + SystemUIDialog dialog = new SystemUIDialog(mContext); + dialog.setTitle(mQsPanel.getSpec(data)); + dialog.setPositiveButton(R.string.ok, null); + dialog.show(); + } + } + + @Override + public void onClick(View v) { + if (mFab == v) { + // TODO: Show list of tiles. + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java index 2c6987c5fe42..f6c1062f6749 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java @@ -34,11 +34,15 @@ class AppButtonData { this.pinned = pinned; } + public int getTaskCount() { + return tasks == null ? 0 : tasks.size(); + } + /** * Returns true if the button contains no useful information and should be removed. */ public boolean isEmpty() { - return !pinned && (tasks == null || tasks.isEmpty()); + return !pinned && getTaskCount() == 0; } public void addTask(RecentTaskInfo task) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index f4439bfacea4..012dc9c53207 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -390,7 +390,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM); try { if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection, - Context.BIND_AUTO_CREATE, new UserHandle(UserHandle.USER_CURRENT))) { + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + new UserHandle(UserHandle.USER_CURRENT))) { mPrewarmBound = true; } } catch (SecurityException e) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 030501bc622f..8e58d14a6e14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -166,13 +166,10 @@ public class LockIcon extends KeyguardAffordanceView { if (mAccessibilityController == null) { return; } - boolean trustManagedOrFingerprintAllowed = mUnlockMethodCache.isTrustManaged() - || KeyguardUpdateMonitor.getInstance(mContext).isUnlockingWithFingerprintAllowed(); - boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled(); - boolean clickToForceLock = trustManagedOrFingerprintAllowed + boolean clickToForceLock = mUnlockMethodCache.isTrustManaged() && !mAccessibilityController.isAccessibilityEnabled(); - boolean longClickToForceLock = trustManagedOrFingerprintAllowed + boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged() && !clickToForceLock; setClickable(clickToForceLock || clickToUnlock); setLongClickable(longClickToForceLock); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java index 1c9b04f5b4bd..5201f35fa224 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java @@ -34,6 +34,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Bundle; import android.os.RemoteException; @@ -43,10 +44,14 @@ import android.util.AttributeSet; import android.util.Slog; import android.view.DragEvent; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.PopupMenu; import android.widget.Toast; import com.android.internal.content.PackageMonitor; @@ -78,6 +83,7 @@ class NavigationBarApps extends LinearLayout { private final UserManager mUserManager; private final LayoutInflater mLayoutInflater; private final AppPackageMonitor mAppPackageMonitor; + private final WindowManager mWindowManager; // This view has two roles: @@ -103,11 +109,22 @@ class NavigationBarApps extends LinearLayout { } }; + // Layout params for the window that contains the anchor for the popup menus. + // We need to create a window for a popup menu because the NavBar window is too narrow and can't + // contain the menu. + private final WindowManager.LayoutParams mPopupAnchorLayoutParams; + // View that contains the anchor for popup menus. The view occupies the whole screen, and + // has a child that will be moved to make the menu to appear where we need it. + private final ViewGroup mPopupAnchor; + private final PopupMenu mPopupMenu; + private final int [] mClickedIconLocation = new int[2]; + public NavigationBarApps(Context context, AttributeSet attrs) { super(context, attrs); sAppsModel = new NavigationBarAppsModel(context); mPackageManager = context.getPackageManager(); - mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mLayoutInflater = LayoutInflater.from(context); mAppPackageMonitor = new AppPackageMonitor(); @@ -131,6 +148,23 @@ class NavigationBarApps extends LinearLayout { } catch (RemoteException e) { Slog.e(TAG, "registerTaskStackListener failed", e); } + + mPopupAnchorLayoutParams = + new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, + WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT); + mPopupAnchorLayoutParams.setTitle("ShelfMenuAnchor"); + + mPopupAnchor = (ViewGroup) mLayoutInflater.inflate(R.layout.shelf_menu_anchor, null); + + ImageView anchorButton = + (ImageView) mPopupAnchor.findViewById(R.id.shelf_menu_anchor_anchor); + mPopupMenu = new PopupMenu(context, anchorButton); } // Monitor that catches events like "app uninstalled". @@ -301,6 +335,7 @@ class NavigationBarApps extends LinearLayout { ImageView button = (ImageView) mLayoutInflater.inflate( R.layout.navigation_bar_app_item, this, false /* attachToRoot */); button.setOnClickListener(new AppClickListener()); + button.setOnContextClickListener(new AppContextClickListener()); // TODO: Ripple effect. Use either KeyButtonRipple or the default ripple background. button.setOnLongClickListener(new AppLongClickListener()); button.setOnDragListener(new AppIconDragListener()); @@ -580,6 +615,47 @@ class NavigationBarApps extends LinearLayout { } /** + * Shows already prepopulated popup menu using appIcon for anchor location. + */ + private void showPopupMenu(ImageView appIcon) { + // Movable view inside the popup anchor view. It serves as the actual anchor for the + // menu. + final ImageView anchorButton = + (ImageView) mPopupAnchor.findViewById(R.id.shelf_menu_anchor_anchor); + // Set same drawable as for the clicked button to have same size. + anchorButton.setImageDrawable(appIcon.getDrawable()); + + // Move the anchor button to the position of the app button. + appIcon.getLocationOnScreen(mClickedIconLocation); + anchorButton.setTranslationX(mClickedIconLocation[0]); + anchorButton.setTranslationY(mClickedIconLocation[1]); + + final OnAttachStateChangeListener onAttachStateChangeListener = + new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mPopupMenu.show(); + } + + @Override + public void onViewDetachedFromWindow(View v) {} + }; + anchorButton.addOnAttachStateChangeListener(onAttachStateChangeListener); + + mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() { + @Override + public void onDismiss(PopupMenu menu) { + mWindowManager.removeView(mPopupAnchor); + anchorButton.removeOnAttachStateChangeListener(onAttachStateChangeListener); + mPopupMenu.setOnDismissListener(null); + mPopupMenu.getMenu().clear(); + } + }); + + mWindowManager.addView(mPopupAnchor, mPopupAnchorLayoutParams); + } + + /** * A click listener that launches an activity. */ private class AppClickListener implements View.OnClickListener { @@ -606,13 +682,11 @@ class NavigationBarApps extends LinearLayout { mContext.startActivityAsUser(launchIntent, optsBundle, appInfo.getUser()); } - private void activateLatestTask(List<RecentTaskInfo> tasks) { - // 'tasks' is guaranteed to be non-empty. - int latestTaskPersistentId = tasks.get(0).persistentId; + private void activateTask(int taskPersistentId) { // Launch or bring the activity to front. IActivityManager manager = ActivityManagerNative.getDefault(); try { - manager.startActivityFromRecents(latestTaskPersistentId, null /* options */); + manager.startActivityFromRecents(taskPersistentId, null /* options */); } catch (RemoteException e) { Slog.e(TAG, "Exception when activating a recent task", e); } catch (IllegalArgumentException e) { @@ -620,16 +694,102 @@ class NavigationBarApps extends LinearLayout { } } + /** + * Adds to the popup menu items for activating each of tasks in the specified list. + */ + void populateLaunchMenu(List<RecentTaskInfo> tasks) { + Menu menu = mPopupMenu.getMenu(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; ++i) { + final RecentTaskInfo taskInfo = tasks.get(i); + MenuItem item = menu.add(getActivityForTask(taskInfo).flattenToShortString()); + item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + activateTask(taskInfo.persistentId); + return true; + } + }); + } + } + + /** + * Shows a task selection menu for clicked apps that have more than 1 running tasks. + */ + void maybeShowLaunchMenu(ImageView appIcon) { + final AppButtonData appButtonData = (AppButtonData) appIcon.getTag(); + + if (appButtonData.getTaskCount() <= 1) return; + + populateLaunchMenu(appButtonData.tasks); + showPopupMenu(appIcon); + } + @Override public void onClick(View v) { - AppButtonData appButtonData = (AppButtonData)v.getTag(); + AppButtonData appButtonData = (AppButtonData) v.getTag(); - if (appButtonData.tasks == null || appButtonData.tasks.size() == 0) { + if (appButtonData.getTaskCount() == 0) { launchApp(appButtonData.appInfo, v); } else { - activateLatestTask(appButtonData.tasks); + // Activate latest task. + activateTask(appButtonData.tasks.get(0).persistentId); + + maybeShowLaunchMenu((ImageView) v); + } + } + } + + /** + * Context click listener that shows app's context menu. + */ + private class AppContextClickListener implements View.OnContextClickListener { + void updateState(ImageView appIcon) { + savePinnedApps(); + if (DEBUG) { + AppButtonData appButtonData = (AppButtonData) appIcon.getTag(); + new GetActivityIconTask(mPackageManager, appIcon).execute(appButtonData); + } + } + + /** + * Adds to the popup menu items for pinning and unpinning the app in the shelf. + */ + void populateContextMenu(final ImageView appIcon) { + final AppButtonData appButtonData = (AppButtonData) appIcon.getTag(); + Menu menu = mPopupMenu.getMenu(); + if (appButtonData.pinned) { + menu.add("Unpin"). + setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + appButtonData.pinned = false; + if (appButtonData.isEmpty()) { + removeView(appIcon); + } + updateState(appIcon); + return true; + } + }); + } else { + menu.add("Pin").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + appButtonData.pinned = true; + updateState(appIcon); + return true; + } + }); } } + + @Override + public boolean onContextClick(View v) { + ImageView appIcon = (ImageView) v; + populateContextMenu(appIcon); + showPopupMenu(appIcon); + return true; + } } private void onUserSwitched(int currentUserId) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index f37383afb3cd..011889a49c86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -261,11 +261,11 @@ public class NavigationBarView extends LinearLayout { private void getIcons(Resources res) { mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); - mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); + mBackLandIcon = mBackIcon; mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); - mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime_land); + mBackAltLandIcon = mBackAltIcon; mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent); - mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land); + mRecentLandIcon = mRecentIcon; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 814a65e522da..f47ec20e4246 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -112,12 +112,6 @@ public class NotificationPanelView extends PanelView implements private boolean mQsTracking; /** - * Handles launching the secure camera properly even when other applications may be using the - * camera hardware. - */ - private SecureCameraLaunchManager mSecureCameraLaunchManager; - - /** * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and * the expansion for quick settings. */ @@ -265,8 +259,6 @@ public class NotificationPanelView extends PanelView implements mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); - mSecureCameraLaunchManager = - new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea); mLastOrientation = getResources().getConfiguration().orientation; // recompute internal state when qspanel height changes @@ -371,16 +363,6 @@ public class NotificationPanelView extends PanelView implements updateMaxHeadsUpTranslation(); } - @Override - public void onAttachedToWindow() { - mSecureCameraLaunchManager.create(); - } - - @Override - public void onDetachedFromWindow() { - mSecureCameraLaunchManager.destroy(); - } - private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) { if (mQsSizeChangeAnimator != null) { oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); @@ -1992,12 +1974,12 @@ public class NotificationPanelView extends PanelView implements mStatusBar.executeRunnableDismissingKeyguard(new Runnable() { @Override public void run() { - mSecureCameraLaunchManager.startSecureCameraLaunch(); + mKeyguardBottomArea.launchCamera(); } }, null, true /* dismissShade */, false /* afterKeyguardGone */); } else { - mSecureCameraLaunchManager.startSecureCameraLaunch(); + mKeyguardBottomArea.launchCamera(); } } mStatusBar.startLaunchTransitionTimeout(); @@ -2046,7 +2028,6 @@ public class NotificationPanelView extends PanelView implements boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon; if (camera) { - mSecureCameraLaunchManager.onSwipingStarted(); mKeyguardBottomArea.bindCameraPrewarmService(); } requestDisallowInterceptTouchEvent(true); 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 55fbb2a9dc35..d3af8f03fa73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -292,6 +292,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private DozeServiceHost mDozeServiceHost; private boolean mWakeUpComingFromTouch; private PointF mWakeUpTouchLocation; + private boolean mScreenTurningOn; int mPixelFormat; Object mQueueLock = new Object(); @@ -2792,9 +2793,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mNextAlarmController != null) { mNextAlarmController.dump(fd, pw, args); } - if (mAssistManager != null) { - mAssistManager.dump(fd, pw, args); - } if (mSecurityController != null) { mSecurityController.dump(fd, pw, args); } @@ -3030,7 +3028,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateNotifications(); resetUserSetupObserver(); setControllerUsers(); - mAssistManager.onUserSwitched(newUserId); + clearCurrentMediaNotification(); + updateMediaMetaData(true); if (mFullscreenUserSwitcher != null) { mFullscreenUserSwitcher.onUserSwitched(newUserId); } @@ -3575,6 +3574,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); releaseGestureWakeLock(); mNotificationPanel.onAffordanceLaunchEnded(); + mNotificationPanel.animate().cancel(); mNotificationPanel.setAlpha(1f); return staying; } @@ -3979,6 +3979,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public void onScreenTurningOn() { + mScreenTurningOn = true; mNotificationPanel.onScreenTurningOn(); if (mLaunchCameraOnScreenTurningOn) { mNotificationPanel.launchCamera(false); @@ -3987,10 +3988,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void vibrateForCameraGesture() { - mVibrator.vibrate(1000L); + mVibrator.vibrate(750L); } public void onScreenTurnedOn() { + mScreenTurningOn = false; mDozeScrimController.onScreenTurnedOn(); } @@ -4167,7 +4169,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mScrimController.dontAnimateBouncerChangesUntilNextFrame(); mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L); } - if (mStatusBarKeyguardViewManager.isScreenTurnedOn()) { + if (mScreenTurningOn || mStatusBarKeyguardViewManager.isScreenTurnedOn()) { mNotificationPanel.launchCamera(mDeviceInteractive /* animate */); } else { // We need to defer the camera launch until the screen comes on, since otherwise diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java deleted file mode 100644 index 45c893849262..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.hardware.camera2.CameraManager; -import android.os.AsyncTask; -import android.os.Handler; -import android.provider.MediaStore; -import android.util.Log; - -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardUpdateMonitor; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Handles launching the secure camera properly even when other applications may be using the camera - * hardware. - * - * When other applications (e.g., Face Unlock) are using the camera, they must close the camera to - * allow the secure camera to open it. Since we want to minimize the delay when opening the secure - * camera, other apps should close the camera at the first possible opportunity (i.e., as soon as - * the user begins swiping to go to the secure camera). - * - * If the camera is unavailable when the user begins to swipe, the SecureCameraLaunchManager sends a - * broadcast to tell other apps to close the camera. When and if the user completes their swipe to - * launch the secure camera, the SecureCameraLaunchManager delays launching the secure camera until - * a callback indicates that the camera has become available. If it doesn't receive that callback - * within a specified timeout period, the secure camera is launched anyway. - * - * Ideally, the secure camera would handle waiting for the camera to become available. This allows - * some of the time necessary to close the camera to happen in parallel with starting the secure - * camera app. We can't rely on all third-party camera apps to handle this. However, an app can - * put com.android.systemui.statusbar.phone.will_wait_for_camera_available in its meta-data to - * indicate that it will be responsible for waiting for the camera to become available. - * - * It is assumed that the functions in this class, including the constructor, will be called from - * the UI thread. - */ -public class SecureCameraLaunchManager { - private static final boolean DEBUG = false; - private static final String TAG = "SecureCameraLaunchManager"; - - // Action sent as a broadcast to tell other apps to stop using the camera. Other apps that use - // the camera from keyguard (e.g., Face Unlock) should listen for this broadcast and close the - // camera as soon as possible after receiving it. - private static final String CLOSE_CAMERA_ACTION_NAME = - "com.android.systemui.statusbar.phone.CLOSE_CAMERA"; - - // Apps should put this field in their meta-data to indicate that they will take on the - // responsibility of waiting for the camera to become available. If this field is present, the - // SecureCameraLaunchManager launches the secure camera even if the camera hardware has not - // become available. Having the secure camera app do the waiting is the optimal approach, but - // without this field, the SecureCameraLaunchManager doesn't launch the secure camera until the - // camera hardware is available. - private static final String META_DATA_WILL_WAIT_FOR_CAMERA_AVAILABLE = - "com.android.systemui.statusbar.phone.will_wait_for_camera_available"; - - // If the camera hardware hasn't become available after this period of time, the - // SecureCameraLaunchManager launches the secure camera anyway. - private static final int CAMERA_AVAILABILITY_TIMEOUT_MS = 1000; - - private Context mContext; - private Handler mHandler; - private LockPatternUtils mLockPatternUtils; - private KeyguardBottomAreaView mKeyguardBottomArea; - - private CameraManager mCameraManager; - private CameraAvailabilityCallback mCameraAvailabilityCallback; - private Map<String, Boolean> mCameraAvailabilityMap; - private boolean mWaitingToLaunchSecureCamera; - private Runnable mLaunchCameraRunnable; - - private class CameraAvailabilityCallback extends CameraManager.AvailabilityCallback { - @Override - public void onCameraUnavailable(String cameraId) { - if (DEBUG) Log.d(TAG, "onCameraUnavailble(" + cameraId + ")"); - mCameraAvailabilityMap.put(cameraId, false); - } - - @Override - public void onCameraAvailable(String cameraId) { - if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")"); - mCameraAvailabilityMap.put(cameraId, true); - - // If we were waiting for the camera hardware to become available to launch the - // secure camera, we can launch it now if all cameras are available. If one or more - // cameras are still not available, we will get this callback again for those - // cameras. - if (mWaitingToLaunchSecureCamera && areAllCamerasAvailable()) { - mKeyguardBottomArea.launchCamera(); - mWaitingToLaunchSecureCamera = false; - - // We no longer need to launch the camera after the timeout hits. - mHandler.removeCallbacks(mLaunchCameraRunnable); - } - } - } - - public SecureCameraLaunchManager(Context context, KeyguardBottomAreaView keyguardBottomArea) { - mContext = context; - mHandler = new Handler(); - mLockPatternUtils = new LockPatternUtils(context); - mKeyguardBottomArea = keyguardBottomArea; - - mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); - mCameraAvailabilityCallback = new CameraAvailabilityCallback(); - - // An onCameraAvailable() or onCameraUnavailable() callback will be received for each camera - // when the availability callback is registered, thus initializing the map. - // - // Keeping track of the state of all cameras using the onCameraAvailable() and - // onCameraUnavailable() callbacks can get messy when dealing with hot-pluggable cameras. - // However, we have a timeout in place such that we will never hang waiting for cameras. - mCameraAvailabilityMap = new HashMap<String, Boolean>(); - - mWaitingToLaunchSecureCamera = false; - mLaunchCameraRunnable = new Runnable() { - @Override - public void run() { - if (mWaitingToLaunchSecureCamera) { - Log.w(TAG, "Timeout waiting for camera availability"); - mKeyguardBottomArea.launchCamera(); - mWaitingToLaunchSecureCamera = false; - } - } - }; - } - - /** - * Initializes the SecureCameraManager and starts listening for camera availability. - */ - public void create() { - mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, mHandler); - } - - /** - * Stops listening for camera availability and cleans up the SecureCameraManager. - */ - public void destroy() { - mCameraManager.unregisterAvailabilityCallback(mCameraAvailabilityCallback); - } - - /** - * Called when the user is starting to swipe horizontally, possibly to start the secure camera. - * Although this swipe ultimately may not result in the secure camera opening, we need to stop - * all other camera usage (e.g., Face Unlock) as soon as possible. We send out a broadcast to - * notify other apps that they should close the camera immediately. The broadcast is sent even - * if the camera appears to be available, because there could be an app that is about to open - * the camera. - */ - public void onSwipingStarted() { - if (DEBUG) Log.d(TAG, "onSwipingStarted"); - AsyncTask.execute(new Runnable() { - @Override - public void run() { - Intent intent = new Intent(); - intent.setAction(CLOSE_CAMERA_ACTION_NAME); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent); - } - }); - } - - /** - * Called when the secure camera should be started. If the camera is available or the secure - * camera app has indicated that it will wait for camera availability, the secure camera app is - * launched immediately. Otherwise, we wait for the camera to become available (or timeout) - * before launching the secure camera. - */ - public void startSecureCameraLaunch() { - if (DEBUG) Log.d(TAG, "startSecureCameraLunch"); - if (areAllCamerasAvailable() || targetWillWaitForCameraAvailable()) { - mKeyguardBottomArea.launchCamera(); - } else { - mWaitingToLaunchSecureCamera = true; - mHandler.postDelayed(mLaunchCameraRunnable, CAMERA_AVAILABILITY_TIMEOUT_MS); - } - } - - /** - * Returns true if all of the cameras we are tracking are currently available. - */ - private boolean areAllCamerasAvailable() { - for (boolean cameraAvailable: mCameraAvailabilityMap.values()) { - if (!cameraAvailable) { - return false; - } - } - return true; - } - - /** - * Determines if the secure camera app will wait for the camera hardware to become available - * before trying to open the camera. If so, we can fire off an intent to start the secure - * camera app before the camera is available. Otherwise, it is our responsibility to wait for - * the camera hardware to become available before firing off the intent to start the secure - * camera. - * - * Ideally we are able to fire off the secure camera intent as early as possibly so that, if the - * camera is closing, it can continue to close while the secure camera app is opening. This - * improves secure camera startup time. - */ - private boolean targetWillWaitForCameraAvailable() { - // Create intent that would launch the secure camera. - Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE) - .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - PackageManager packageManager = mContext.getPackageManager(); - - // Get the list of applications that can handle the intent. - final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser( - intent, PackageManager.MATCH_DEFAULT_ONLY, KeyguardUpdateMonitor.getCurrentUser()); - if (appList.size() == 0) { - if (DEBUG) Log.d(TAG, "No targets found for secure camera intent"); - return false; - } - - // Get the application that the intent resolves to. - ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, - PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, - KeyguardUpdateMonitor.getCurrentUser()); - - if (resolved == null || resolved.activityInfo == null) { - return false; - } - - // If we would need to launch the resolver activity, then we can't assume that the target - // is one that would wait for the camera. - if (wouldLaunchResolverActivity(resolved, appList)) { - if (DEBUG) Log.d(TAG, "Secure camera intent would launch resolver"); - return false; - } - - // If the target doesn't have meta-data we must assume it won't wait for the camera. - if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) { - if (DEBUG) Log.d(TAG, "No meta-data found for secure camera application"); - return false; - } - - // Check the secure camera app meta-data to see if it indicates that it will wait for the - // camera to become available. - boolean willWaitForCameraAvailability = - resolved.activityInfo.metaData.getBoolean(META_DATA_WILL_WAIT_FOR_CAMERA_AVAILABLE); - - if (DEBUG) Log.d(TAG, "Target will wait for camera: " + willWaitForCameraAvailability); - - return willWaitForCameraAvailability; - } - - /** - * Determines if the activity that would be launched by the intent is the ResolverActivity. - */ - private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) { - // If the list contains the resolved activity, then it can't be the ResolverActivity itself. - for (int i = 0; i < appList.size(); i++) { - ResolveInfo tmp = appList.get(i); - if (tmp.activityInfo.name.equals(resolved.activityInfo.name) - && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) { - return false; - } - } - return true; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java index 343a23149008..4387b33d0797 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java @@ -8,7 +8,7 @@ import com.android.systemui.statusbar.phone.QSTileHost; public class QSPagingSwitch extends TunerSwitch { - private static final String QS_PAGE_TILES = + public static final String QS_PAGE_TILES = "dwifi,dbt,inversion,dnd,cell,airplane,rotation,flashlight,location," + "hotspot,qwifi,qbt,qrotation,qflashlight,qairplane,cast"; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java index 6475cbf158cb..b2f527e3bbb4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -104,6 +104,7 @@ public class VolumeDialog { private final SpTexts mSpTexts; private final SparseBooleanArray mDynamic = new SparseBooleanArray(); private final KeyguardManager mKeyguard; + private final AudioManager mAudioManager; private final int mExpandButtonAnimationDuration; private final ZenFooter mZenFooter; private final LayoutTransition mLayoutTransition; @@ -136,6 +137,7 @@ public class VolumeDialog { mCallback = callback; mSpTexts = new SpTexts(mContext); mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mDialog = new CustomDialog(mContext); @@ -649,7 +651,8 @@ public class VolumeDialog { private void updateFooterH() { if (D.BUG) Log.d(TAG, "updateFooterH"); final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE; - final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF; + final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF + && mAudioManager.isStreamAffectedByRingerMode(mActiveStream); if (wasVisible != visible && !visible) { prepareForCollapse(); } diff --git a/packages/VpnDialogs/res/values-nl/strings.xml b/packages/VpnDialogs/res/values-nl/strings.xml index 0f28016d952e..ae789b2a05e4 100644 --- a/packages/VpnDialogs/res/values-nl/strings.xml +++ b/packages/VpnDialogs/res/values-nl/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="prompt" msgid="3183836924226407828">"Verbindingsverzoek"</string> - <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> wil een VPN-verbinding opzetten om netwerkverkeer te controleren. Accepteer het verzoek alleen als u de bron vertrouwt. <br /> <br /> <img src=vpn_icon /> wordt boven aan uw scherm weergegeven wanneer VPN actief is."</string> + <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> wil een VPN-verbinding opzetten om netwerkverkeer te controleren. Accepteer het verzoek alleen als je de bron vertrouwt. <br /> <br /> <img src=vpn_icon /> wordt boven aan je scherm weergegeven wanneer VPN actief is."</string> <string name="legacy_title" msgid="192936250066580964">"Verbinding met VPN"</string> <string name="configure" msgid="4905518375574791375">"Configureren"</string> <string name="disconnect" msgid="971412338304200056">"Verbinding verbreken"</string> diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 9f080cabd5ff..87a0b808f573 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -63,6 +63,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_FEATURE_FILTER_KEY_EVENTS = 0x00000004; + /** + * Flag for enabling "Automatically click on mouse stop" feature. + * + * @see #setEnabledFeatures(int) + */ + static final int FLAG_FEATURE_AUTOCLICK = 0x00000008; + private final Runnable mProcessBatchedEventsRunnable = new Runnable() { @Override public void run() { @@ -88,8 +95,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private final Choreographer mChoreographer; - private int mCurrentTouchDeviceId; - private boolean mInstalled; private int mEnabledFeatures; @@ -98,17 +103,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private ScreenMagnifier mScreenMagnifier; + private AutoclickController mAutoclickController; + + private KeyboardInterceptor mKeyboardInterceptor; + private EventStreamTransformation mEventHandler; private MotionEventHolder mEventQueue; - private boolean mMotionEventSequenceStarted; - - private boolean mHoverEventSequenceStarted; + private EventStreamState mMouseStreamState; - private boolean mKeyEventSequenceStarted; + private EventStreamState mTouchScreenStreamState; - private boolean mFilterKeyEvents; + private EventStreamState mKeyboardStreamState; AccessibilityInputFilter(Context context, AccessibilityManagerService service) { super(context.getMainLooper()); @@ -142,89 +149,95 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo @Override public void onInputEvent(InputEvent event, int policyFlags) { if (DEBUG) { - Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); } - if (event instanceof MotionEvent - && event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { - MotionEvent motionEvent = (MotionEvent) event; - onMotionEvent(motionEvent, policyFlags); - } else if (event instanceof KeyEvent - && event.isFromSource(InputDevice.SOURCE_KEYBOARD)) { - KeyEvent keyEvent = (KeyEvent) event; - onKeyEvent(keyEvent, policyFlags); - } else { - super.onInputEvent(event, policyFlags); - } - } - private void onMotionEvent(MotionEvent event, int policyFlags) { if (mEventHandler == null) { super.onInputEvent(event, policyFlags); return; } - if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { - mMotionEventSequenceStarted = false; - mHoverEventSequenceStarted = false; - mEventHandler.clear(); + + EventStreamState state = getEventStreamState(event); + if (state == null) { super.onInputEvent(event, policyFlags); return; } - final int deviceId = event.getDeviceId(); - if (mCurrentTouchDeviceId != deviceId) { - mCurrentTouchDeviceId = deviceId; - mMotionEventSequenceStarted = false; - mHoverEventSequenceStarted = false; - mEventHandler.clear(); - } - if (mCurrentTouchDeviceId < 0) { + + int eventSource = event.getSource(); + if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { + state.reset(); + mEventHandler.clearEvents(eventSource); super.onInputEvent(event, policyFlags); return; } - // We do not handle scroll events. - if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { + + if (state.updateDeviceId(event.getDeviceId())) { + mEventHandler.clearEvents(eventSource); + } + + if (!state.deviceIdValid()) { super.onInputEvent(event, policyFlags); return; } - // Wait for a down touch event to start processing. - if (event.isTouchEvent()) { - if (!mMotionEventSequenceStarted) { - if (event.getActionMasked() != MotionEvent.ACTION_DOWN) { - return; - } - mMotionEventSequenceStarted = true; - } - } else { - // Wait for an enter hover event to start processing. - if (!mHoverEventSequenceStarted) { - if (event.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) { - return; - } - mHoverEventSequenceStarted = true; - } + + if (event instanceof MotionEvent) { + MotionEvent motionEvent = (MotionEvent) event; + processMotionEvent(state, motionEvent, policyFlags); + } else if (event instanceof KeyEvent) { + KeyEvent keyEvent = (KeyEvent) event; + processKeyEvent(state, keyEvent, policyFlags); } - batchMotionEvent((MotionEvent) event, policyFlags); } - private void onKeyEvent(KeyEvent event, int policyFlags) { - if (!mFilterKeyEvents) { + /** + * Gets current event stream state associated with an input event. + * @return The event stream state that should be used for the event. Null if the event should + * not be handled by #AccessibilityInputFilter. + */ + private EventStreamState getEventStreamState(InputEvent event) { + if (event instanceof MotionEvent) { + if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { + if (mTouchScreenStreamState == null) { + mTouchScreenStreamState = new TouchScreenEventStreamState(); + } + return mTouchScreenStreamState; + } + if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (mMouseStreamState == null) { + mMouseStreamState = new MouseEventStreamState(); + } + return mMouseStreamState; + } + } else if (event instanceof KeyEvent) { + if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) { + if (mKeyboardStreamState == null) { + mKeyboardStreamState = new KeyboardEventStreamState(); + } + return mKeyboardStreamState; + } + } + return null; + } + + private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) { + if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) { super.onInputEvent(event, policyFlags); return; } - if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { - mKeyEventSequenceStarted = false; - super.onInputEvent(event, policyFlags); + + if (!state.shouldProcessMotionEvent(event)) { return; } - // Wait for a down key event to start processing. - if (!mKeyEventSequenceStarted) { - if (event.getAction() != KeyEvent.ACTION_DOWN) { - super.onInputEvent(event, policyFlags); - return; - } - mKeyEventSequenceStarted = true; + + batchMotionEvent(event, policyFlags); + } + + private void processKeyEvent(EventStreamState state, KeyEvent event, int policyFlags) { + if (!state.shouldProcessKeyEvent(event)) { + return; } - mAms.notifyKeyEvent(event, policyFlags); + mEventHandler.onKeyEvent(event, policyFlags); } private void scheduleProcessBatchedEvents() { @@ -293,6 +306,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + sendInputEvent(event, policyFlags); + } + + @Override public void onAccessibilityEvent(AccessibilityEvent event) { // TODO Implement this to inject the accessibility event // into the accessibility manager service similarly @@ -305,7 +323,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } @Override - public void clear() { + public void clearEvents(int inputSource) { /* do nothing */ } @@ -329,43 +347,77 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } private void enableFeatures() { - mMotionEventSequenceStarted = false; - mHoverEventSequenceStarted = false; - if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { - mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext, - Display.DEFAULT_DISPLAY, mAms); - mEventHandler.setNext(this); + resetStreamState(); + + if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) { + mAutoclickController = new AutoclickController(mContext); + addFirstEventHandler(mAutoclickController); } + if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { mTouchExplorer = new TouchExplorer(mContext, mAms); - mTouchExplorer.setNext(this); - if (mEventHandler != null) { - mEventHandler.setNext(mTouchExplorer); - } else { - mEventHandler = mTouchExplorer; - } + addFirstEventHandler(mTouchExplorer); + } + + if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { + mScreenMagnifier = new ScreenMagnifier(mContext, + Display.DEFAULT_DISPLAY, mAms); + addFirstEventHandler(mScreenMagnifier); } + if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) { - mFilterKeyEvents = true; + mKeyboardInterceptor = new KeyboardInterceptor(mAms); + addFirstEventHandler(mKeyboardInterceptor); } } + /** + * Adds an event handler to the event handler chain. The handler is added at the beginning of + * the chain. + * + * @param handler The handler to be added to the event handlers list. + */ + private void addFirstEventHandler(EventStreamTransformation handler) { + if (mEventHandler != null) { + handler.setNext(mEventHandler); + } else { + handler.setNext(this); + } + mEventHandler = handler; + } + void disableFeatures() { + if (mAutoclickController != null) { + mAutoclickController.onDestroy(); + mAutoclickController = null; + } if (mTouchExplorer != null) { - mTouchExplorer.clear(); mTouchExplorer.onDestroy(); mTouchExplorer = null; } if (mScreenMagnifier != null) { - mScreenMagnifier.clear(); mScreenMagnifier.onDestroy(); mScreenMagnifier = null; } + if (mKeyboardInterceptor != null) { + mKeyboardInterceptor.onDestroy(); + mKeyboardInterceptor = null; + } + mEventHandler = null; - mKeyEventSequenceStarted = false; - mMotionEventSequenceStarted = false; - mHoverEventSequenceStarted = false; - mFilterKeyEvents = false; + resetStreamState(); + } + + void resetStreamState() { + if (mTouchScreenStreamState != null) { + mTouchScreenStreamState.reset(); + } + if (mMouseStreamState != null) { + mMouseStreamState.reset(); + } + if (mKeyboardStreamState != null) { + mKeyboardStreamState.reset(); + } } @Override @@ -402,4 +454,171 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo sPool.release(this); } } + + /** + * Keeps state of event streams observed for an input device with a certain source. + * Provides information about whether motion and key events should be processed by accessibility + * #EventStreamTransformations. Base implementation describes behaviour for event sources that + * whose events should not be handled by a11y event stream transformations. + */ + private static class EventStreamState { + private int mDeviceId; + + EventStreamState() { + mDeviceId = -1; + } + + /** + * Updates the ID of the device associated with the state. If the ID changes, resets + * internal state. + * + * @param deviceId Updated input device ID. + * @return Whether the device ID has changed. + */ + public boolean updateDeviceId(int deviceId) { + if (mDeviceId == deviceId) { + return false; + } + // Reset clears internal state, so make sure it's called before |mDeviceId| is updated. + reset(); + mDeviceId = deviceId; + return true; + } + + /** + * @return Whether device ID is valid. + */ + public boolean deviceIdValid() { + return mDeviceId >= 0; + } + + /** + * Resets the event stream state. + */ + public void reset() { + mDeviceId = -1; + } + + /** + * @return Whether scroll events for device should be handled by event transformations. + */ + public boolean shouldProcessScroll() { + return false; + } + + /** + * @param event An observed motion event. + * @return Whether the event should be handled by event transformations. + */ + public boolean shouldProcessMotionEvent(MotionEvent event) { + return false; + } + + /** + * @param event An observed key event. + * @return Whether the event should be handled by event transformations. + */ + public boolean shouldProcessKeyEvent(KeyEvent event) { + return false; + } + } + + /** + * Keeps state of stream of events from a mouse device. + */ + private static class MouseEventStreamState extends EventStreamState { + private boolean mMotionSequenceStarted; + + public MouseEventStreamState() { + reset(); + } + + @Override + final public void reset() { + super.reset(); + mMotionSequenceStarted = false; + } + + @Override + final public boolean shouldProcessScroll() { + return true; + } + + @Override + final public boolean shouldProcessMotionEvent(MotionEvent event) { + if (mMotionSequenceStarted) { + return true; + } + // Wait for down or move event to start processing mouse events. + int action = event.getActionMasked(); + mMotionSequenceStarted = + action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_HOVER_MOVE; + return mMotionSequenceStarted; + } + } + + /** + * Keeps state of stream of events from a touch screen device. + */ + private static class TouchScreenEventStreamState extends EventStreamState { + private boolean mTouchSequenceStarted; + private boolean mHoverSequenceStarted; + + public TouchScreenEventStreamState() { + reset(); + } + + @Override + final public void reset() { + super.reset(); + mTouchSequenceStarted = false; + mHoverSequenceStarted = false; + } + + @Override + final public boolean shouldProcessMotionEvent(MotionEvent event) { + // Wait for a down touch event to start processing. + if (event.isTouchEvent()) { + if (mTouchSequenceStarted) { + return true; + } + mTouchSequenceStarted = event.getActionMasked() == MotionEvent.ACTION_DOWN; + return mTouchSequenceStarted; + } + + // Wait for an enter hover event to start processing. + if (mHoverSequenceStarted) { + return true; + } + mHoverSequenceStarted = event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER; + return mHoverSequenceStarted; + } + } + + /** + * Keeps state of stream of events from a keyboard device. + */ + private static class KeyboardEventStreamState extends EventStreamState { + private boolean mEventSequenceStarted; + + public KeyboardEventStreamState() { + reset(); + } + + @Override + final public void reset() { + super.reset(); + mEventSequenceStarted = false; + } + + @Override + final public boolean shouldProcessKeyEvent(KeyEvent event) { + // Wait for a down key event to start processing. + if (mEventSequenceStarted) { + return true; + } + mEventSequenceStarted = event.getAction() == KeyEvent.ACTION_DOWN; + return mEventSequenceStarted; + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java new file mode 100644 index 000000000000..318b7b4dbfc1 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java @@ -0,0 +1,83 @@ +/* + * 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.server.accessibility; + +import android.content.Context; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.accessibility.AccessibilityEvent; + +/** + * Implements "Automatically click on mouse stop" feature. + * + * If enabled, it will observe motion events from mouse source, and send click event sequence short + * while after mouse stops moving. The click will only be performed if mouse movement had been + * actually detected. + * + * Movement detection has tolerance to jitter that may be caused by poor motor control to prevent: + * <ul> + * <li>Initiating unwanted clicks with no mouse movement.</li> + * <li>Autoclick never occurring after mouse arriving at target.</li> + * </ul> + * + * Non-mouse motion events, key events (excluding modifiers) and non-movement mouse events cancel + * the automatic click. + */ +public class AutoclickController implements EventStreamTransformation { + private EventStreamTransformation mNext; + + public AutoclickController(Context context) {} + + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // TODO: Implement this. + if (mNext != null) { + mNext.onMotionEvent(event, rawEvent, policyFlags); + } + } + + @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + // TODO: Implement this. + if (mNext != null) { + mNext.onKeyEvent(event, policyFlags); + } + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } + } + + @Override + public void setNext(EventStreamTransformation next) { + mNext = next; + } + + @Override + public void clearEvents(int inputSource) { + if (mNext != null) { + mNext.clearEvents(inputSource); + } + } + + @Override + public void onDestroy() { + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/EventStreamTransformation.java b/services/accessibility/java/com/android/server/accessibility/EventStreamTransformation.java index 8c93e7b4440d..fdc40984dab4 100644 --- a/services/accessibility/java/com/android/server/accessibility/EventStreamTransformation.java +++ b/services/accessibility/java/com/android/server/accessibility/EventStreamTransformation.java @@ -68,6 +68,14 @@ interface EventStreamTransformation { public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); /** + * Receives a key event. + * + * @param event The key event. + * @param policyFlags Policy flags for the event. + */ + public void onKeyEvent(KeyEvent event, int policyFlags); + + /** * Receives an accessibility event. * * @param event The accessibility event. @@ -82,9 +90,11 @@ interface EventStreamTransformation { public void setNext(EventStreamTransformation next); /** - * Clears the internal state of this transformation. + * Clears internal state associated with events from specific input source. + * + * @param inputSource The input source class for which transformation state should be cleared. */ - public void clear(); + public void clearEvents(int inputSource); /** * Destroys this transformation. diff --git a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java new file mode 100644 index 000000000000..bbb25af5da43 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.accessibility.AccessibilityEvent; + +/** + * Intercepts key events and forwards them to accessibility manager service. + */ +public class KeyboardInterceptor implements EventStreamTransformation { + private EventStreamTransformation mNext; + private AccessibilityManagerService mAms; + + public KeyboardInterceptor(AccessibilityManagerService service) { + mAms = service; + } + + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mNext != null) { + mNext.onMotionEvent(event, rawEvent, policyFlags); + } + } + + @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + mAms.notifyKeyEvent(event, policyFlags); + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } + } + + @Override + public void setNext(EventStreamTransformation next) { + mNext = next; + } + + @Override + public void clearEvents(int inputSource) { + if (mNext != null) { + mNext.clearEvents(inputSource); + } + } + + @Override + public void onDestroy() { + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java index b4613d6d811a..37276bdb8145 100644 --- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java +++ b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java @@ -37,6 +37,8 @@ import android.util.Slog; import android.util.TypedValue; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.InputDevice; +import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; @@ -325,6 +327,12 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { + if (mNext != null) { + mNext.onMotionEvent(event, rawEvent, policyFlags); + } + return; + } mMagnifiedContentInteractonStateHandler.onMotionEvent(event); switch (mCurrentState) { case STATE_DELEGATING: { @@ -348,6 +356,13 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + if (mNext != null) { + mNext.onKeyEvent(event, policyFlags); + } + } + + @Override public void onAccessibilityEvent(AccessibilityEvent event) { if (mNext != null) { mNext.onAccessibilityEvent(event); @@ -360,22 +375,30 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } @Override - public void clear() { - mCurrentState = STATE_DETECTING; - mDetectingStateHandler.clear(); - mStateViewportDraggingHandler.clear(); - mMagnifiedContentInteractonStateHandler.clear(); + public void clearEvents(int inputSource) { + if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) { + clear(); + } + if (mNext != null) { - mNext.clear(); + mNext.clearEvents(inputSource); } } @Override public void onDestroy() { + clear(); mScreenStateObserver.destroy(); mWindowManager.setMagnificationCallbacks(null); } + private void clear() { + mCurrentState = STATE_DETECTING; + mDetectingStateHandler.clear(); + mStateViewportDraggingHandler.clear(); + mMagnifiedContentInteractonStateHandler.clear(); + } + private void handleMotionEventStateDelegating(MotionEvent event, MotionEvent rawEvent, int policyFlags) { switch (event.getActionMasked()) { diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index f18b5ef338aa..85730cdef082 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -29,6 +29,8 @@ import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; import android.util.Slog; +import android.view.InputDevice; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; @@ -253,7 +255,22 @@ class TouchExplorer implements EventStreamTransformation { mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density); } - public void clear() { + @Override + public void clearEvents(int inputSource) { + if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) { + clear(); + } + if (mNext != null) { + mNext.clearEvents(inputSource); + } + } + + @Override + public void onDestroy() { + clear(); + } + + private void clear() { // If we have not received an event then we are in initial // state. Therefore, there is not need to clean anything. MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent(); @@ -262,10 +279,6 @@ class TouchExplorer implements EventStreamTransformation { } } - public void onDestroy() { - // TODO: Implement - } - private void clear(MotionEvent event, int policyFlags) { switch (mCurrentState) { case STATE_TOUCH_EXPLORING: { @@ -304,9 +317,6 @@ class TouchExplorer implements EventStreamTransformation { mLongPressingPointerDeltaX = 0; mLongPressingPointerDeltaY = 0; mCurrentState = STATE_TOUCH_EXPLORING; - if (mNext != null) { - mNext.clear(); - } mTouchExplorationInProgress = false; mAms.onTouchInteractionEnd(); } @@ -318,6 +328,13 @@ class TouchExplorer implements EventStreamTransformation { @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { + if (mNext != null) { + mNext.onMotionEvent(event, rawEvent, policyFlags); + } + return; + } + if (DEBUG) { Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); @@ -344,6 +361,14 @@ class TouchExplorer implements EventStreamTransformation { } } + @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + if (mNext != null) { + mNext.onKeyEvent(event, policyFlags); + } + } + + @Override public void onAccessibilityEvent(AccessibilityEvent event) { final int eventType = event.getEventType(); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1f4427f760c3..d1e1683d6478 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -359,6 +359,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31; + /** Handler thread used for both of the handlers below. */ + @VisibleForTesting + protected final HandlerThread mHandlerThread; /** Handler used for internal events. */ final private InternalHandler mHandler; /** Handler used for incoming {@link NetworkStateTracker} events. */ @@ -614,6 +617,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(); + @VisibleForTesting + protected HandlerThread createHandlerThread() { + return new HandlerThread("ConnectivityServiceThread"); + } + public ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { if (DBG) log("ConnectivityService starting up"); @@ -627,10 +635,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultMobileDataRequest = createInternetRequestForTransport( NetworkCapabilities.TRANSPORT_CELLULAR); - HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); - handlerThread.start(); - mHandler = new InternalHandler(handlerThread.getLooper()); - mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper()); + mHandlerThread = createHandlerThread(); + mHandlerThread.start(); + mHandler = new InternalHandler(mHandlerThread.getLooper()); + mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper()); // setup our unique device name if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { @@ -1458,7 +1466,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void enforceKeepalivePermission() { - mContext.enforceCallingPermission(KeepaliveTracker.PERMISSION, "ConnectivityService"); + mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService"); } public void sendConnectedBroadcast(NetworkInfo info) { diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index 342a3ef94be3..69f0cef6d39e 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -17,7 +17,6 @@ package com.android.server; import android.app.ActivityManager; -import android.app.KeyguardManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -33,10 +32,11 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.Vibrator; import android.provider.Settings; import android.util.Slog; +import android.view.KeyEvent; +import com.android.internal.logging.MetricsLogger; import com.android.server.statusbar.StatusBarManagerInternal; /** @@ -46,10 +46,16 @@ import com.android.server.statusbar.StatusBarManagerInternal; * added.</p> * @hide */ -class GestureLauncherService extends SystemService { +public class GestureLauncherService extends SystemService { private static final boolean DBG = false; private static final String TAG = "GestureLauncherService"; + /** + * Time in milliseconds in which the power button must be pressed twice so it will be considered + * as a camera launch. + */ + private static final long CAMERA_POWER_DOUBLE_TAP_TIME_MS = 300; + /** The listener that receives the gesture event. */ private final GestureEventListener mGestureListener = new GestureEventListener(); @@ -91,13 +97,20 @@ class GestureLauncherService extends SystemService { */ private int mCameraLaunchLastEventExtra = 0; + /** + * Whether camera double tap power button gesture is currently enabled; + */ + private boolean mCameraDoubleTapPowerEnabled; + private long mLastPowerDownWhileNonInteractive = 0; + + public GestureLauncherService(Context context) { super(context); mContext = context; } public void onStart() { - // Nothing to publish. + LocalServices.addService(GestureLauncherService.class, this); } public void onBootPhase(int phase) { @@ -113,17 +126,21 @@ class GestureLauncherService extends SystemService { mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "GestureLauncherService"); updateCameraRegistered(); + updateCameraDoubleTapPowerEnabled(); mUserId = ActivityManager.getCurrentUser(); mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED)); - registerContentObserver(); + registerContentObservers(); } } - private void registerContentObserver() { + private void registerContentObservers() { mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED), false, mSettingObserver, mUserId); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED), + false, mSettingObserver, mUserId); } private void updateCameraRegistered() { @@ -135,6 +152,13 @@ class GestureLauncherService extends SystemService { } } + private void updateCameraDoubleTapPowerEnabled() { + boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId); + synchronized (this) { + mCameraDoubleTapPowerEnabled = enabled; + } + } + private void unregisterCameraLaunchGesture() { if (mRegistered) { mRegistered = false; @@ -197,6 +221,12 @@ class GestureLauncherService extends SystemService { Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0); } + public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) { + return isCameraDoubleTapPowerEnabled(context.getResources()) + && (Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0); + } + /** * Whether to enable the camera launch gesture. */ @@ -207,13 +237,64 @@ class GestureLauncherService extends SystemService { !SystemProperties.getBoolean("gesture.disable_camera_launch", false); } + public static boolean isCameraDoubleTapPowerEnabled(Resources resources) { + return resources.getBoolean( + com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled); + } + /** * Whether GestureLauncherService should be enabled according to system properties. */ public static boolean isGestureLauncherEnabled(Resources resources) { - // For now, the only supported gesture is camera launch gesture, so whether to enable this - // service equals to isCameraLaunchEnabled(); - return isCameraLaunchEnabled(resources); + return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources); + } + + public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive) { + boolean launched = false; + synchronized (this) { + if (!mCameraDoubleTapPowerEnabled) { + mLastPowerDownWhileNonInteractive = 0; + return false; + } + if (event.getEventTime() - mLastPowerDownWhileNonInteractive + < CAMERA_POWER_DOUBLE_TAP_TIME_MS) { + launched = true; + } + mLastPowerDownWhileNonInteractive = interactive ? 0 : event.getEventTime(); + } + if (launched) { + Slog.i(TAG, "Power button double tap gesture detected, launching camera."); + launched = handleCameraLaunchGesture(false /* useWakelock */, + MetricsLogger.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE); + } + return launched; + } + + /** + * @return true if camera was launched, false otherwise. + */ + private boolean handleCameraLaunchGesture(boolean useWakelock, int logCategory) { + boolean userSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; + if (!userSetupComplete) { + if (DBG) Slog.d(TAG, String.format( + "userSetupComplete = %s, ignoring camera launch gesture.", + userSetupComplete)); + return false; + } + if (DBG) Slog.d(TAG, String.format( + "userSetupComplete = %s, performing camera launch gesture.", + userSetupComplete)); + + if (useWakelock) { + // Make sure we don't sleep too early + mWakeLock.acquire(500L); + } + StatusBarManagerInternal service = LocalServices.getService( + StatusBarManagerInternal.class); + service.onCameraLaunchGestureDetected(); + MetricsLogger.action(mContext, logCategory); + return true; } private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { @@ -222,8 +303,9 @@ class GestureLauncherService extends SystemService { if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); mContext.getContentResolver().unregisterContentObserver(mSettingObserver); - registerContentObserver(); + registerContentObservers(); updateCameraRegistered(); + updateCameraDoubleTapPowerEnabled(); } } }; @@ -232,6 +314,7 @@ class GestureLauncherService extends SystemService { public void onChange(boolean selfChange, android.net.Uri uri, int userId) { if (userId == mUserId) { updateCameraRegistered(); + updateCameraDoubleTapPowerEnabled(); } } }; @@ -244,36 +327,17 @@ class GestureLauncherService extends SystemService { return; } if (event.sensor == mCameraLaunchSensor) { - handleCameraLaunchGesture(event); - return; - } - } - - private void handleCameraLaunchGesture(SensorEvent event) { - if (DBG) { - float[] values = event.values; - Slog.d(TAG, String.format("Received a camera launch event: " + - "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2])); - } - boolean userSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; - if (!userSetupComplete) { - if (DBG) Slog.d(TAG, String.format( - "userSetupComplete = %s, ignoring camera launch gesture.", - userSetupComplete)); + if (DBG) { + float[] values = event.values; + Slog.d(TAG, String.format("Received a camera launch event: " + + "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2])); + } + if (handleCameraLaunchGesture(true /* useWakelock */, + MetricsLogger.ACTION_WIGGLE_CAMERA_GESTURE)) { + trackCameraLaunchEvent(event); + } return; } - if (DBG) Slog.d(TAG, String.format( - "userSetupComplete = %s, performing camera launch gesture.", - userSetupComplete)); - - // Make sure we don't sleep too early - mWakeLock.acquire(500L); - StatusBarManagerInternal service = LocalServices.getService( - StatusBarManagerInternal.class); - service.onCameraLaunchGestureDetected(); - trackCameraLaunchEvent(event); - mWakeLock.release(); } @Override diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 72cece3430e8..540f8cb8557c 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -370,11 +370,17 @@ class MountService extends IMountService.Stub private boolean shouldBenchmark() { final long benchInterval = Settings.Global.getLong(mContext.getContentResolver(), Settings.Global.STORAGE_BENCHMARK_INTERVAL, DateUtils.WEEK_IN_MILLIS); + if (benchInterval == -1) { + return false; + } else if (benchInterval == 0) { + return true; + } + synchronized (mLock) { for (int i = 0; i < mVolumes.size(); i++) { final VolumeInfo vol = mVolumes.valueAt(i); final VolumeRecord rec = mRecords.get(vol.fsUuid); - if (vol.isMountedReadable() && rec != null) { + if (vol.isMountedWritable() && rec != null) { final long benchAge = System.currentTimeMillis() - rec.lastBenchMillis; if (benchAge >= benchInterval) { return true; diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 7aef38d41793..dbf128840c59 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -32,6 +32,7 @@ import android.accounts.IAccountManagerResponse; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -122,6 +123,7 @@ public class AccountManagerService private final Context mContext; private final PackageManager mPackageManager; + private final AppOpsManager mAppOpsManager; private UserManager mUserManager; private final MessageHandler mMessageHandler; @@ -266,6 +268,7 @@ public class AccountManagerService IAccountAuthenticatorCache authenticatorCache) { mContext = context; mPackageManager = packageManager; + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mMessageHandler = new MessageHandler(FgThread.get().getLooper()); @@ -510,7 +513,7 @@ public class AccountManagerService // Check if there's a shared account that needs to be created as an account Account[] sharedAccounts = getSharedAccountsAsUser(userId); if (sharedAccounts == null || sharedAccounts.length == 0) return; - Account[] accounts = getAccountsAsUser(null, userId); + Account[] accounts = getAccountsAsUser(null, userId, mContext.getOpPackageName()); int parentUserId = UserManager.isSplitSystemUser() ? mUserManager.getUserInfo(userId).restrictedProfileParentId : UserHandle.USER_SYSTEM; @@ -876,7 +879,8 @@ public class AccountManagerService // Confirm that the owner's account still exists before this step. UserAccounts owner = getUserAccounts(parentUserId); synchronized (owner.cacheLock) { - for (Account acc : getAccounts(parentUserId)) { + for (Account acc : getAccounts(parentUserId, + mContext.getOpPackageName())) { if (acc.equals(account)) { mAuthenticator.addAccountFromCredentials( this, account, accountCredentials); @@ -996,7 +1000,7 @@ public class AccountManagerService @Override public void hasFeatures(IAccountManagerResponse response, - Account account, String[] features) { + Account account, String[] features, String opPackageName) { int callingUid = Binder.getCallingUid(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "hasFeatures: " + account @@ -1009,7 +1013,8 @@ public class AccountManagerService if (account == null) throw new IllegalArgumentException("account is null"); if (features == null) throw new IllegalArgumentException("features is null"); int userId = UserHandle.getCallingUserId(); - checkReadAccountsPermitted(callingUid, account.type, userId); + checkReadAccountsPermitted(callingUid, account.type, userId, + opPackageName); long identityToken = clearCallingIdentity(); try { @@ -2521,9 +2526,10 @@ public class AccountManagerService * Returns the accounts visible to the client within the context of a specific user * @hide */ - public Account[] getAccounts(int userId) { + public Account[] getAccounts(int userId, String opPackageName) { int callingUid = Binder.getCallingUid(); - List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId); + List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId, + opPackageName); if (visibleAccountTypes.isEmpty()) { return new Account[0]; } @@ -2585,15 +2591,16 @@ public class AccountManagerService } @Override - public Account[] getAccountsAsUser(String type, int userId) { - return getAccountsAsUser(type, userId, null, -1); + public Account[] getAccountsAsUser(String type, int userId, String opPackageName) { + return getAccountsAsUser(type, userId, null, -1, opPackageName); } private Account[] getAccountsAsUser( String type, int userId, String callingPackage, - int packageUid) { + int packageUid, + String opPackageName) { int callingUid = Binder.getCallingUid(); // Only allow the system process to read accounts of other users if (userId != UserHandle.getCallingUserId() @@ -2614,9 +2621,11 @@ public class AccountManagerService // be passed in the original caller's uid here, which is what should be used for filtering. if (packageUid != -1 && UserHandle.isSameApp(callingUid, Process.myUid())) { callingUid = packageUid; + opPackageName = callingPackage; } - List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId); + List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId, + opPackageName); if (visibleAccountTypes.isEmpty() || (type != null && !visibleAccountTypes.contains(type))) { return new Account[0]; @@ -2755,22 +2764,24 @@ public class AccountManagerService } @Override - public Account[] getAccounts(String type) { - return getAccountsAsUser(type, UserHandle.getCallingUserId()); + public Account[] getAccounts(String type, String opPackageName) { + return getAccountsAsUser(type, UserHandle.getCallingUserId(), opPackageName); } @Override - public Account[] getAccountsForPackage(String packageName, int uid) { + public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) { int callingUid = Binder.getCallingUid(); if (!UserHandle.isSameApp(callingUid, Process.myUid())) { throw new SecurityException("getAccountsForPackage() called from unauthorized uid " + callingUid + " with uid=" + uid); } - return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid); + return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid, + opPackageName); } @Override - public Account[] getAccountsByTypeForPackage(String type, String packageName) { + public Account[] getAccountsByTypeForPackage(String type, String packageName, + String opPackageName) { int packageUid = -1; try { packageUid = AppGlobals.getPackageManager().getPackageUid( @@ -2779,14 +2790,16 @@ public class AccountManagerService Slog.e(TAG, "Couldn't determine the packageUid for " + packageName + re); return new Account[0]; } - return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid); + return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, + packageUid, opPackageName); } @Override public void getAccountsByFeatures( IAccountManagerResponse response, String type, - String[] features) { + String[] features, + String opPackageName) { int callingUid = Binder.getCallingUid(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAccounts: accountType " + type @@ -2799,7 +2812,8 @@ public class AccountManagerService if (type == null) throw new IllegalArgumentException("accountType is null"); int userId = UserHandle.getCallingUserId(); - List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId); + List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId, + opPackageName); if (!visibleAccountTypes.contains(type)) { Bundle result = new Bundle(); // Need to return just the accounts that are from matching signatures. @@ -3699,31 +3713,22 @@ public class AccountManagerService } } - private boolean isPermitted(int callingUid, String... permissions) { + private boolean isPermitted(String opPackageName, int callingUid, String... permissions) { for (String perm : permissions) { if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, " caller uid " + callingUid + " has " + perm); } - return true; + final int opCode = AppOpsManager.permissionToOpCode(perm); + if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp( + opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) { + return true; + } } } return false; } - /** Succeeds if any of the specified permissions are granted. */ - private void checkBinderPermission(String... permissions) { - final int callingUid = Binder.getCallingUid(); - if (isPermitted(callingUid, permissions)) { - String msg = String.format( - "caller uid %s lacks any of %s", - callingUid, - TextUtils.join(",", permissions)); - Log.w(TAG, " " + msg); - throw new SecurityException(msg); - } - } - private int handleIncomingUser(int userId) { try { return ActivityManagerNative.getDefault().handleIncomingUser( @@ -3777,11 +3782,13 @@ public class AccountManagerService return fromAuthenticator || hasExplicitGrants || isPrivileged; } - private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId) { + private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId, + String opPackageName) { if (accountType == null) { return false; } else { - return getTypesVisibleToCaller(callingUid, userId).contains(accountType); + return getTypesVisibleToCaller(callingUid, userId, + opPackageName).contains(accountType); } } @@ -3793,9 +3800,10 @@ public class AccountManagerService } } - private List<String> getTypesVisibleToCaller(int callingUid, int userId) { + private List<String> getTypesVisibleToCaller(int callingUid, int userId, + String opPackageName) { boolean isPermitted = - isPermitted(callingUid, Manifest.permission.GET_ACCOUNTS, + isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS, Manifest.permission.GET_ACCOUNTS_PRIVILEGED); Log.i(TAG, String.format("getTypesVisibleToCaller: isPermitted? %s", isPermitted)); return getTypesForCaller(callingUid, userId, isPermitted); @@ -3891,8 +3899,9 @@ public class AccountManagerService private void checkReadAccountsPermitted( int callingUid, String accountType, - int userId) { - if (!isAccountVisibleToCaller(accountType, callingUid, userId)) { + int userId, + String opPackageName) { + if (!isAccountVisibleToCaller(accountType, callingUid, userId, opPackageName)) { String msg = String.format( "caller uid %s cannot access %s accounts", callingUid, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 81936ee8ea46..14376d1bdb80 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2730,31 +2730,17 @@ public final class ActivityManagerService extends ActivityManagerNative } } - /** - * Activate an activity by bringing it to the top and set the focus on it. - * Note: This is only allowed for activities which are on the freeform stack. - * @param token The token of the activity calling which will get activated. - */ @Override - public void activateActivity(IBinder token) { - if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "ActivateActivity token=" + token); + public void setFocusedTask(int taskId) { + if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedTask: taskId=" + taskId); long callingId = Binder.clearCallingIdentity(); try { synchronized (ActivityManagerService.this) { - final ActivityRecord anyTaskRecord = ActivityRecord.isInStackLocked(token); - if (anyTaskRecord == null) { - Slog.w(TAG, "ActivateActivity: token=" + token + " not found"); - return; - } - TaskRecord task = anyTaskRecord.task; - final boolean runsOnFreeformStack = - task.stack.getStackId() == FREEFORM_WORKSPACE_STACK_ID; - if (!runsOnFreeformStack) { - Slog.w(TAG, "Tried to use activateActivity on a non freeform workspace!"); - } else if (task != null) { - ActivityRecord topTaskRecord = task.topRunningActivityLocked(null); - if (topTaskRecord != null) { - setFocusedActivityLocked(topTaskRecord, "activateActivity"); + TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); + if (task != null) { + ActivityRecord r = task.topRunningActivityLocked(null); + if (r != null) { + setFocusedActivityLocked(r, "setFocusedTask"); mStackSupervisor.resumeTopActivitiesLocked(task.stack, null, null); } } @@ -2764,21 +2750,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - @Override - public void setFocusedTask(int taskId) { - if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedTask: taskId=" + taskId); - synchronized (ActivityManagerService.this) { - TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); - if (task != null) { - ActivityRecord r = task.topRunningActivityLocked(null); - if (r != null) { - setFocusedActivityLocked(r, "setFocusedTask"); - mStackSupervisor.resumeTopActivitiesLocked(task.stack, null, null); - } - } - } - } - /** Sets the task stack listener that gets callbacks when a task stack changes. */ @Override public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException { @@ -8721,58 +8692,6 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public void setActivityBounds(IBinder token, Rect bounds) { - long ident = Binder.clearCallingIdentity(); - try { - synchronized (this) { - final ActivityRecord r = ActivityRecord.isInStackLocked(token); - if (r == null) { - Slog.w(TAG, "setActivityBounds: token=" + token + " not found"); - return; - } - final TaskRecord task = r.task; - if (task == null) { - Slog.e(TAG, "setActivityBounds: No TaskRecord for the ActivityRecord r=" + r); - return; - } - if (task.stack != null && task.stack.mStackId == DOCKED_STACK_ID) { - mStackSupervisor.resizeStackLocked(task.stack.mStackId, bounds); - } else { - mStackSupervisor.resizeTaskLocked(task, bounds); - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - @Override - public Rect getActivityBounds(IBinder token) { - long ident = Binder.clearCallingIdentity(); - Rect rect = null; - try { - synchronized (this) { - final ActivityRecord r = ActivityRecord.isInStackLocked(token); - if (r == null) { - Slog.w(TAG, "getActivityBounds: token=" + token + " not found"); - return rect; - } - final TaskRecord task = r.task; - if (task == null) { - Slog.e(TAG, "getActivityBounds: No TaskRecord for the ActivityRecord r=" + r); - return rect; - } - if (task.mBounds != null) { - rect = new Rect(task.mBounds); - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - return rect; - } - - @Override public Bitmap getTaskDescriptionIcon(String filename) { if (!FileUtils.isValidExtFilename(filename) || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index dd9b6f13869d..8ab4ae59a201 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -205,6 +205,13 @@ public final class ActivityStackSupervisor implements DisplayListener { /** Action restriction: launching the activity is restricted by an app op. */ private static final int ACTIVITY_RESTRICTION_APPOP = 2; + // The height/width divide used when fitting a task within a bounds with method + // {@link #fitWithinBounds}. + // We always want the task to to be visible in the bounds without affecting its size when + // fitting. To make sure this is the case, we don't adjust the task left or top side pass + // the input bounds right or bottom side minus the width or height divided by this value. + private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3; + /** Status Bar Service **/ private IBinder mToken = new Binder(); private IStatusBarService mStatusBarService; @@ -330,8 +337,9 @@ public final class ActivityStackSupervisor implements DisplayListener { /** Used to keep resumeTopActivityLocked() from being entered recursively */ boolean inResumeTopActivity; - // temp. rect used during resize calculation so we don't need to create a new object each time. + // temp. rects used during resize calculation so we don't need to create a new object each time. private final Rect tempRect = new Rect(); + private final Rect tempRect2 = new Rect(); private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>(); private final SparseArray<Rect> mTmpBounds = new SparseArray<>(); @@ -2970,7 +2978,17 @@ public final class ActivityStackSupervisor implements DisplayListener { ArrayList<TaskRecord> tasks = stack.getAllTasks(); for (int i = tasks.size() - 1; i >= 0; i--) { TaskRecord task = tasks.get(i); - task.updateOverrideConfiguration(bounds); + if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) { + // For freeform stack we don't adjust the size of the tasks to match that of + // the stack, but we do try to make sure the tasks are still contained with the + // bounds of the stack. + tempRect2.set(task.mBounds); + fitWithinBounds(tempRect2, bounds); + task.updateOverrideConfiguration(tempRect2); + } else { + task.updateOverrideConfiguration(bounds); + } + mTmpConfigs.put(task.taskId, task.mOverrideConfig); mTmpBounds.put(task.taskId, task.mBounds); } @@ -4839,4 +4857,44 @@ public final class ActivityStackSupervisor implements DisplayListener { return onLeanbackOnly; } + + /** + * Adjust bounds to stay within stack bounds. + * + * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way + * that keep them unchanged, but be contained within the stack bounds. + * + * @param bounds Bounds to be adjusted. + * @param stackBounds Bounds within which the other bounds should remain. + */ + private static void fitWithinBounds(Rect bounds, Rect stackBounds) { + if (stackBounds == null || stackBounds.contains(bounds)) { + return; + } + + if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) { + final int maxRight = stackBounds.right + - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER); + int horizontalDiff = stackBounds.left - bounds.left; + if ((horizontalDiff < 0 && bounds.left >= maxRight) + || (bounds.left + horizontalDiff >= maxRight)) { + horizontalDiff = maxRight - bounds.left; + } + bounds.left += horizontalDiff; + bounds.right += horizontalDiff; + } + + if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) { + final int maxBottom = stackBounds.bottom + - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER); + int verticalDiff = stackBounds.top - bounds.top; + if ((verticalDiff < 0 && bounds.top >= maxBottom) + || (bounds.top + verticalDiff >= maxBottom)) { + verticalDiff = maxBottom - bounds.top; + } + bounds.top += verticalDiff; + bounds.bottom += verticalDiff; + } + } + } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 5694e7046888..8f10f083c0ff 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -108,13 +108,6 @@ final class TaskRecord { static final int INVALID_TASK_ID = -1; - // The height/width divide used when fitting a task within a bounds with method - // {@link #fitWithinBounds}. - // We always want the task to to be visible in the bounds without affecting its size when - // fitting. To make sure this is the case, we don't adjust the task left or top side pass - // the input bounds right or bottom side minus the width or height divided by this value. - private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3; - final int taskId; // Unique identifier for this task. String affinity; // The affinity name for this task, or null; may change identity. String rootAffinity; // Initial base affinity, or null; does not change from initial root. @@ -1186,12 +1179,6 @@ final class TaskRecord { * @return Update configuration or null if there is no change. */ Configuration updateOverrideConfiguration(Rect bounds) { - if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) { - // For freeform stack we don't adjust the size of the tasks to match that of the - // stack, but we do try to make sure the tasks are still contained with the - // bounds of the stack. - fitWithinBounds(bounds, stack.mBounds); - } if (Objects.equals(mBounds, bounds)) { return null; } @@ -1255,45 +1242,6 @@ final class TaskRecord { return mLastNonFullscreenBounds; } - /** - * Adjust bounds to stay within stack bounds. - * - * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way - * that keep them unchanged, but be contained within the stack bounds. - * - * @param bounds Bounds to be adjusted. - * @param stackBounds Bounds within which the other bounds should remain. - */ - private static void fitWithinBounds(Rect bounds, Rect stackBounds) { - if (stackBounds == null || stackBounds.contains(bounds)) { - return; - } - - if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) { - final int maxRight = stackBounds.right - - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER); - int horizontalDiff = stackBounds.left - bounds.left; - if ((horizontalDiff < 0 && bounds.left >= maxRight) - || (bounds.left + horizontalDiff >= maxRight)) { - horizontalDiff = maxRight - bounds.left; - } - bounds.left += horizontalDiff; - bounds.right += horizontalDiff; - } - - if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) { - final int maxBottom = stackBounds.bottom - - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER); - int verticalDiff = stackBounds.top - bounds.top; - if ((verticalDiff < 0 && bounds.top >= maxBottom) - || (bounds.top + verticalDiff >= maxBottom)) { - verticalDiff = maxBottom - bounds.top; - } - bounds.top += verticalDiff; - bounds.bottom += verticalDiff; - } - } - void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("userId="); pw.print(userId); pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid); diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java index 64b9399dcc70..2ccfdd1f3136 100644 --- a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java +++ b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java @@ -71,6 +71,7 @@ public class KeepalivePacketData { // Check we have two IP addresses of the same family. if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); } // Set the protocol. @@ -102,7 +103,7 @@ public class KeepalivePacketData { InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) throws InvalidPacketException { - if (!(srcAddress instanceof Inet4Address)) { + if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index c78f347a4638..90c9ddffd6bd 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -62,8 +62,7 @@ public class KeepaliveTracker { private static final String TAG = "KeepaliveTracker"; private static final boolean DBG = true; - // TODO: Change this to a system-only permission. - public static final String PERMISSION = android.Manifest.permission.CHANGE_NETWORK_STATE; + public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD; /** Keeps track of keepalive requests. */ private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives = @@ -208,6 +207,8 @@ public class KeepaliveTracker { Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name()); mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot); } + // TODO: at the moment we unconditionally return failure here. In cases where the + // NetworkAgent is alive, should we ask it to reply, so it can return failure? notifyMessenger(mSlot, reason); unlinkDeathRecipient(); } @@ -233,17 +234,14 @@ public class KeepaliveTracker { mKeepalives.put(nai, networkKeepalives); } - // Find the lowest-numbered free slot. + // Find the lowest-numbered free slot. Slot numbers start from 1, because that's what two + // separate chipset implementations independently came up with. int slot; - for (slot = 0; slot < networkKeepalives.size(); slot++) { + for (slot = 1; slot <= networkKeepalives.size(); slot++) { if (networkKeepalives.get(slot) == null) { return slot; } } - // No free slot, pick one at the end. - - // HACK for broadcom hardware that does not support slot 0! - if (slot == 0) slot = 1; return slot; } @@ -267,14 +265,15 @@ public class KeepaliveTracker { } public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) { + String networkName = (nai == null) ? "(null)" : nai.name(); HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); if (networkKeepalives == null) { - Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + nai.name()); + Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName); return; } KeepaliveInfo ki = networkKeepalives.get(slot); if (ki == null) { - Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + nai.name()); + Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + networkName); return; } ki.stop(reason); @@ -332,6 +331,11 @@ public class KeepaliveTracker { public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) { + if (nai == null) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); + return; + } + InetAddress srcAddress, dstAddress; try { srcAddress = NetworkUtils.numericToInetAddress(srcAddrString); diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 292aff90df7a..e6dc8959f219 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -340,7 +340,8 @@ public class SyncManager { for (UserInfo user : mUserManager.getUsers(true)) { // Skip any partially created/removed users if (user.partial) continue; - Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id); + Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts( + user.id, mContext.getOpPackageName()); mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id); } } @@ -1232,7 +1233,8 @@ public class SyncManager { } // Schedule sync for any accounts under started user - final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId); + final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId, + mContext.getOpPackageName()); for (Account account : accounts) { scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, 0 /* no delay */, 0 /* No flex */, diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index f53ccc9154ce..2eabd326f214 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -341,7 +341,8 @@ final class DisplayPowerState { private int mPendingBacklight = INITIAL_BACKLIGHT; private int mActualState = INITIAL_SCREEN_STATE; private int mActualBacklight = INITIAL_BACKLIGHT; - private boolean mChangeInProgress; + private boolean mStateChangeInProgress; + private boolean mBacklightChangeInProgress; public PhotonicModulator() { super("PhotonicModulator"); @@ -349,7 +350,9 @@ final class DisplayPowerState { public boolean setState(int state, int backlight) { synchronized (mLock) { - if (state != mPendingState || backlight != mPendingBacklight) { + boolean stateChanged = state != mPendingState; + boolean backlightChanged = backlight != mPendingBacklight; + if (stateChanged || backlightChanged) { if (DEBUG) { Slog.d(TAG, "Requesting new screen state: state=" + Display.stateToString(state) + ", backlight=" + backlight); @@ -358,12 +361,15 @@ final class DisplayPowerState { mPendingState = state; mPendingBacklight = backlight; - if (!mChangeInProgress) { - mChangeInProgress = true; + boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress; + mStateChangeInProgress = stateChanged; + mBacklightChangeInProgress = backlightChanged; + + if (!changeInProgress) { mLock.notifyAll(); } } - return !mChangeInProgress; + return !mStateChangeInProgress; } } @@ -375,7 +381,8 @@ final class DisplayPowerState { pw.println(" mPendingBacklight=" + mPendingBacklight); pw.println(" mActualState=" + Display.stateToString(mActualState)); pw.println(" mActualBacklight=" + mActualBacklight); - pw.println(" mChangeInProgress=" + mChangeInProgress); + pw.println(" mStateChangeInProgress=" + mStateChangeInProgress); + pw.println(" mBacklightChangeInProgress=" + mBacklightChangeInProgress); } } @@ -392,10 +399,15 @@ final class DisplayPowerState { stateChanged = (state != mActualState); backlight = mPendingBacklight; backlightChanged = (backlight != mActualBacklight); - if (!stateChanged && !backlightChanged) { - // All changed applied, notify outer class and wait for more. - mChangeInProgress = false; + if (!stateChanged) { + // State changed applied, notify outer class. postScreenUpdateThreadSafe(); + mStateChangeInProgress = false; + } + if (!backlightChanged) { + mBacklightChangeInProgress = false; + } + if (!stateChanged && !backlightChanged) { try { mLock.wait(); } catch (InterruptedException ex) { } diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 002325840c63..ea7d85e36944 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -21,10 +21,15 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManagerNative; +import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.IUserSwitchObserver; +import android.app.PendingIntent; import android.content.ComponentName; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback; @@ -85,6 +90,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private static final String FINGERPRINTD = "android.hardware.fingerprint.IFingerprintDaemon"; private static final int MSG_USER_SWITCHING = 10; private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute + private static final String ACTION_LOCKOUT_RESET = + "com.android.server.fingerprint.ACTION_LOCKOUT_RESET"; private ClientMonitor mAuthClient = null; private ClientMonitor mEnrollClient = null; @@ -118,9 +125,19 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private long mHalDeviceId; private int mFailedAttempts; private IFingerprintDaemon mDaemon; - private PowerManager mPowerManager; + private final PowerManager mPowerManager; + private final AlarmManager mAlarmManager; - private final Runnable mLockoutReset = new Runnable() { + private final BroadcastReceiver mLockoutReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) { + resetFailedAttempts(); + } + } + }; + + private final Runnable mResetFailedAttemptsRunnable = new Runnable() { @Override public void run() { resetFailedAttempts(); @@ -133,7 +150,10 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString( com.android.internal.R.string.config_keyguardComponent)).getPackageName(); mAppOps = context.getSystemService(AppOpsManager.class); - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mPowerManager = mContext.getSystemService(PowerManager.class); + mAlarmManager = mContext.getSystemService(AlarmManager.class); + mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET), + RESET_FINGERPRINT_LOCKOUT, null /* handler */); } @Override @@ -262,14 +282,28 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return mFailedAttempts >= MAX_FAILED_ATTEMPTS; } + private void scheduleLockoutReset() { + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent()); + } + + private void cancelLockoutReset() { + mAlarmManager.cancel(getLockoutResetIntent()); + } + + private PendingIntent getLockoutResetIntent() { + return PendingIntent.getBroadcast(mContext, 0, + new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT); + } + private void resetFailedAttempts() { if (DEBUG && inLockoutMode()) { Slog.v(TAG, "Reset fingerprint lockout"); } mFailedAttempts = 0; - // If we're asked to reset failed attempts externally (i.e. from Keyguard), the runnable - // may still be in the queue; remove it. - mHandler.removeCallbacks(mLockoutReset); + // If we're asked to reset failed attempts externally (i.e. from Keyguard), the alarm might + // still be pending; remove it. + cancelLockoutReset(); notifyLockoutResetMonitors(); } @@ -277,8 +311,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe mFailedAttempts++; if (inLockoutMode()) { // Failing multiple times will continue to push out the lockout time. - mHandler.removeCallbacks(mLockoutReset); - mHandler.postDelayed(mLockoutReset, FAIL_LOCKOUT_TIMEOUT_MS); + scheduleLockoutReset(); if (clientMonitor != null && !clientMonitor.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { Slog.w(TAG, "Cannot send lockout message to client"); @@ -683,7 +716,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe FingerprintUtils.vibrateFingerprintSuccess(getContext()); } result |= true; // we have a valid fingerprint - mHandler.post(mLockoutReset); + resetFailedAttempts(); } return result; } @@ -1016,7 +1049,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe public void resetTimeout(byte [] token) { checkPermission(RESET_FINGERPRINT_LOCKOUT); // TODO: confirm security token when we move timeout management into the HAL layer. - mHandler.post(mLockoutReset); + mHandler.post(mResetFailedAttemptsRunnable); } @Override diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4d67702a444f..63d0ba131aee 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -118,6 +118,7 @@ import com.android.internal.R; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.widget.PointerLocationView; +import com.android.server.GestureLauncherService; import com.android.server.LocalServices; import com.android.server.policy.keyguard.KeyguardServiceDelegate; import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; @@ -126,7 +127,6 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; -import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -944,10 +944,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + GestureLauncherService gestureService = LocalServices.getService( + GestureLauncherService.class); + boolean gesturedServiceIntercepted = false; + if (gestureService != null) { + gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive); + } + // If the power key has still not yet been handled, then detect short // press, long press, or multi press and decide what to do. mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered - || mScreenshotChordVolumeUpKeyTriggered; + || mScreenshotChordVolumeUpKeyTriggered || gesturedServiceIntercepted; if (!mPowerKeyHandled) { if (interactive) { // When interactive, we're already awake. @@ -2301,13 +2308,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { PhoneWindow win = new PhoneWindow(context); win.setIsStartingWindow(true); - final TypedArray ta = win.getWindowStyle(); - if (ta.getBoolean( - com.android.internal.R.styleable.Window_windowDisablePreview, false) - || ta.getBoolean( - com.android.internal.R.styleable.Window_windowShowWallpaper,false)) { - return null; - } Resources r = context.getResources(); win.setTitle(r.getText(labelRes, nonLocalizedLabel)); @@ -2362,16 +2362,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); view = win.getDecorView(); - if (win.isFloating()) { - // Whoops, there is no way to display an animation/preview - // of such a thing! After all that work... let's skip it. - // (Note that we must do this here because it is in - // getDecorView() where the theme is evaluated... maybe - // we should peek the floating attribute from the theme - // earlier.) - return null; - } - if (DEBUG_STARTING_WINDOW) Slog.d( TAG, "Adding starting window for " + packageName + " / " + appToken + ": " diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 5dc6887522f3..192b168122bd 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -133,6 +133,7 @@ public class AppTransition implements Dump { private static final int DEFAULT_APP_TRANSITION_DURATION = 336; private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336; private static final int THUMBNAIL_APP_TRANSITION_ALPHA_DURATION = 336; + private static final long APP_TRANSITION_TIMEOUT_MS = 5000; private final Context mContext; private final Handler mH; @@ -242,10 +243,6 @@ public class AppTransition implements Dump { return mNextAppTransition != TRANSIT_UNSET; } - boolean isTransitionNone() { - return mNextAppTransition == TRANSIT_NONE; - } - boolean isTransitionEqual(int transit) { return mNextAppTransition == transit; } @@ -254,7 +251,7 @@ public class AppTransition implements Dump { return mNextAppTransition; } - void setAppTransition(int transit) { + private void setAppTransition(int transit) { mNextAppTransition = transit; } @@ -299,7 +296,7 @@ public class AppTransition implements Dump { return mNextAppTransitionScaleUp; } - boolean prepare() { + private boolean prepare() { if (!isRunning()) { mAppTransitionState = APP_STATE_IDLE; notifyAppTransitionPendingLocked(); @@ -1194,7 +1191,7 @@ public class AppTransition implements Dump { } void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, - IRemoteCallback startedCallback) { + IRemoteCallback startedCallback) { if (isTransitionSet()) { mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM; mNextAppTransitionPackage = packageName; @@ -1269,12 +1266,20 @@ public class AppTransition implements Dump { mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; mNextAppTransitionPackage = null; + mDefaultNextAppTransitionAnimationSpec = null; mNextAppTransitionAnimationsSpecs.clear(); mNextAppTransitionScaleUp = scaleUp; for (int i = 0; i < specs.length; i++) { AppTransitionAnimationSpec spec = specs[i]; if (spec != null) { mNextAppTransitionAnimationsSpecs.put(spec.taskId, spec); + if (i == 0) { + // In full screen mode, the transition code depends on the default spec to + // be set. + Rect rect = spec.rect; + putDefaultNextAppTransitionCoordinates(rect.left, rect.top, rect.width(), + rect.height()); + } } } postAnimationCallback(); @@ -1450,4 +1455,34 @@ public class AppTransition implements Dump { public void setCurrentUser(int newUserId) { mCurrentUserId = newUserId; } + + /** + * @return true if transition is not running and should not be skipped, false if transition is + * already running + */ + boolean prepareAppTransitionLocked(int transit, boolean alwaysKeepCurrent) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Prepare app transition:" + + " transit=" + appTransitionToString(transit) + + " " + this + + " alwaysKeepCurrent=" + alwaysKeepCurrent + + " Callers=" + Debug.getCallers(3)); + if (!isTransitionSet() || mNextAppTransition == TRANSIT_NONE) { + setAppTransition(transit); + } else if (!alwaysKeepCurrent) { + if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) { + // Opening a new task always supersedes a close for the anim. + setAppTransition(transit); + } else if (transit == TRANSIT_ACTIVITY_OPEN + && isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)) { + // Opening a new activity always supersedes a close for the anim. + setAppTransition(transit); + } + } + boolean prepared = prepare(); + if (isTransitionSet()) { + mH.removeMessages(H.APP_TRANSITION_TIMEOUT); + mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS); + } + return prepared; + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 99e9fe63525f..325196d43056 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -21,8 +21,10 @@ import static android.app.ActivityManager.HOME_STACK_ID; import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerService.TAG; +import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; +import android.util.DisplayMetrics; import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; @@ -240,17 +242,85 @@ class DisplayContent { return -1; } + /** + * Find the id of the task whose outside touch area (for resizing) (x, y) + * falls within. Returns -1 if the touch doesn't fall into a resizing area. + */ + int taskIdForControlPoint(int x, int y) { + final int delta = calculatePixelFromDp(WindowState.RESIZE_HANDLE_WIDTH_IN_DP); + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + TaskStack stack = mStacks.get(stackNdx); + if (!stack.allowTaskResize()) { + break; + } + final ArrayList<Task> tasks = stack.getTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + final Task task = tasks.get(taskNdx); + if (task.isFullscreen()) { + return -1; + } + task.getBounds(mTmpRect); + mTmpRect.inset(-delta, -delta); + if (mTmpRect.contains(x, y)) { + mTmpRect.inset(delta, delta); + if (!mTmpRect.contains(x, y)) { + return task.mTaskId; + } + // User touched inside the task. No need to look further, + // focus transfer will be handled in ACTION_UP. + return -1; + } + } + } + return -1; + } + + private int calculatePixelFromDp(int dp) { + final Configuration serviceConfig = mService.mCurConfiguration; + // TODO(multidisplay): Update Dp to that of display stack is on. + final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + return (int)(dp * density); + } + void setTouchExcludeRegion(Task focusedTask) { mTouchExcludeRegion.set(mBaseDisplayRect); WindowList windows = getWindowList(); + final int delta = calculatePixelFromDp(WindowState.RESIZE_HANDLE_WIDTH_IN_DP); 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 && task != focusedTask) { - mTmpRect.set(win.mVisibleFrame); - // If no intersection, we need mTmpRect to be unmodified. - mTmpRect.intersect(win.mVisibleInsets); - mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE); + final Task task = win.mAppToken != null ? win.getTask() : null; + if (win.isVisibleLw() && task != null) { + /** + * Exclusion region is the region that TapDetector doesn't care about. + * Here we want to remove all non-focused tasks from the exclusion region. + * We also remove the outside touch area for resizing for all freeform + * tasks (including the focused). + * + * (For freeform focused task, the below logic will first remove the enlarged + * area, then add back the inner area.) + */ + final boolean isFreeformed = win.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. + */ + if (isFreeformed) { + mTmpRect.inset(-delta, -delta); + } + 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 (mTapDetector != null) { diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index e87dcdea77e6..8f77176feb73 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -283,11 +283,12 @@ class DragState { // stop intercepting input mService.mDragState.unregister(); - mService.mInputMonitor.updateInputWindowsLw(true /*force*/); // free our resources and drop all the object references mService.mDragState.reset(); mService.mDragState = null; + + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); } void notifyMoveLw(float x, float y) { diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index b3244ffb980f..9adcafcf7ef1 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -64,9 +64,9 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { public InputMonitor(WindowManagerService service) { mService = service; } - + /* Notifies the window manager about a broken input channel. - * + * * Called by the InputManager. */ @Override @@ -83,10 +83,10 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } } } - + /* Notifies the window manager about an application that is not responding. * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch. - * + * * Called by the InputManager. */ @Override @@ -257,6 +257,20 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } } + final boolean inPositioning = (mService.mTaskPositioner != null); + if (inPositioning) { + if (WindowManagerService.DEBUG_TASK_POSITIONING) { + Log.d(WindowManagerService.TAG, "Inserting window handle for repositioning"); + } + final InputWindowHandle dragWindowHandle = mService.mTaskPositioner.mDragWindowHandle; + if (dragWindowHandle != null) { + addInputWindowHandleLw(dragWindowHandle); + } else { + Slog.e(WindowManagerService.TAG, + "Repositioning is in progress but there is no drag window handle."); + } + } + boolean addInputConsumerHandle = mService.mInputConsumer != null; // Add all windows on the default display. @@ -437,56 +451,56 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (WindowManagerService.DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window); } - + window.paused = true; updateInputWindowsLw(true /*force*/); } } - + public void resumeDispatchingLw(WindowToken window) { if (window.paused) { if (WindowManagerService.DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window); } - + window.paused = false; updateInputWindowsLw(true /*force*/); } } - + public void freezeInputDispatchingLw() { if (! mInputDispatchFrozen) { if (WindowManagerService.DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Freezing input dispatching"); } - + mInputDispatchFrozen = true; updateInputDispatchModeLw(); } } - + public void thawInputDispatchingLw() { if (mInputDispatchFrozen) { if (WindowManagerService.DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Thawing input dispatching"); } - + mInputDispatchFrozen = false; updateInputDispatchModeLw(); } } - + public void setEventDispatchingLw(boolean enabled) { if (mInputDispatchEnabled != enabled) { if (WindowManagerService.DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Setting event dispatching to " + enabled); } - + mInputDispatchEnabled = enabled; updateInputDispatchModeLw(); } } - + private void updateInputDispatchModeLw() { mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen); } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 12f61f933695..1f62bc15024e 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -347,6 +347,13 @@ final class Session extends IWindowSession.Stub return true; // success! } + public boolean startMovingTask(IWindow window, float startX, float startY) { + if (WindowManagerService.DEBUG_TASK_POSITIONING) Slog.d( + WindowManagerService.TAG, "startMovingTask: {" + startX + "," + startY + "}"); + + return mService.startMovingTask(window, startX, startY); + } + public void reportDropResult(IWindow window, boolean consumed) { IBinder token = window.asBinder(); if (WindowManagerService.DEBUG_DRAG) { diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java new file mode 100644 index 000000000000..71b83a5e32ea --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -0,0 +1,325 @@ +/* + * 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.server.wm; + +import com.android.server.input.InputApplicationHandle; +import com.android.server.input.InputWindowHandle; +import com.android.server.wm.WindowManagerService.H; +import static com.android.server.wm.WindowManagerService.DEBUG_TASK_POSITIONING; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import android.annotation.IntDef; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Looper; +import android.os.Process; +import android.os.RemoteException; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.util.TypedValue; +import android.view.Display; +import android.view.InputChannel; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.MotionEvent; +import android.view.WindowManager; + +class TaskPositioner { + private static final String TAG = "TaskPositioner"; + + @IntDef(flag = true, + value = { + CTRL_NONE, + CTRL_LEFT, + CTRL_RIGHT, + CTRL_TOP, + CTRL_BOTTOM + }) + @Retention(RetentionPolicy.SOURCE) + @interface CtrlType {} + + private static final int CTRL_NONE = 0x0; + private static final int CTRL_LEFT = 0x1; + private static final int CTRL_RIGHT = 0x2; + private static final int CTRL_TOP = 0x4; + private static final int CTRL_BOTTOM = 0x8; + + private final WindowManagerService mService; + private WindowPositionerEventReceiver mInputEventReceiver; + private Display mDisplay; + private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + + private int mTaskId; + private final Rect mWindowOriginalBounds = new Rect(); + private final Rect mWindowDragBounds = new Rect(); + private float mStartDragX; + private float mStartDragY; + @CtrlType + private int mCtrlType = CTRL_NONE; + + InputChannel mServerChannel; + InputChannel mClientChannel; + InputApplicationHandle mDragApplicationHandle; + InputWindowHandle mDragWindowHandle; + + private final class WindowPositionerEventReceiver extends InputEventReceiver { + public WindowPositionerEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + + @Override + public void onInputEvent(InputEvent event) { + if (!(event instanceof MotionEvent) + || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { + return; + } + final MotionEvent motionEvent = (MotionEvent) event; + boolean handled = false; + + try { + boolean endDrag = false; + final float newX = motionEvent.getRawX(); + final float newY = motionEvent.getRawY(); + + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); + } + } break; + + case MotionEvent.ACTION_MOVE: { + if (DEBUG_TASK_POSITIONING){ + Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); + } + synchronized (mService.mWindowMap) { + notifyMoveLocked(newX, newY); + } + try { + mService.mActivityManager.resizeTask(mTaskId, mWindowDragBounds); + } catch(RemoteException e) {} + } break; + + case MotionEvent.ACTION_UP: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); + } + endDrag = true; + } break; + + case MotionEvent.ACTION_CANCEL: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); + } + endDrag = true; + } break; + } + + if (endDrag) { + // Post back to WM to handle clean-ups. We still need the input + // event handler for the last finishInputEvent()! + mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING); + } + handled = true; + } catch (Exception e) { + Slog.e(TAG, "Exception caught by drag handleMotion", e); + } finally { + finishInputEvent(event, handled); + } + } + } + + TaskPositioner(WindowManagerService service) { + mService = service; + } + + /** + * @param display The Display that the window being dragged is on. + */ + void register(Display display) { + if (DEBUG_TASK_POSITIONING) { + Slog.d(TAG, "Registering task positioner"); + } + + if (mClientChannel != null) { + Slog.e(TAG, "Task positioner already registered"); + return; + } + + mDisplay = display; + mDisplay.getMetrics(mDisplayMetrics); + final InputChannel[] channels = InputChannel.openInputChannelPair(TAG); + mServerChannel = channels[0]; + mClientChannel = channels[1]; + mService.mInputManager.registerInputChannel(mServerChannel, null); + + mInputEventReceiver = new WindowPositionerEventReceiver(mClientChannel, + mService.mH.getLooper()); + + mDragApplicationHandle = new InputApplicationHandle(null); + mDragApplicationHandle.name = TAG; + mDragApplicationHandle.dispatchingTimeoutNanos = + WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + + mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, + mDisplay.getDisplayId()); + mDragWindowHandle.name = TAG; + mDragWindowHandle.inputChannel = mServerChannel; + mDragWindowHandle.layer = getDragLayerLocked(); + mDragWindowHandle.layoutParamsFlags = 0; + mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; + mDragWindowHandle.dispatchingTimeoutNanos = + WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + mDragWindowHandle.visible = true; + mDragWindowHandle.canReceiveKeys = false; + mDragWindowHandle.hasFocus = true; + mDragWindowHandle.hasWallpaper = false; + mDragWindowHandle.paused = false; + mDragWindowHandle.ownerPid = Process.myPid(); + mDragWindowHandle.ownerUid = Process.myUid(); + mDragWindowHandle.inputFeatures = 0; + mDragWindowHandle.scaleFactor = 1.0f; + + // The drag window cannot receive new touches. + mDragWindowHandle.touchableRegion.setEmpty(); + + // The drag window covers the entire display + mDragWindowHandle.frameLeft = 0; + mDragWindowHandle.frameTop = 0; + final Point p = new Point(); + mDisplay.getRealSize(p); + mDragWindowHandle.frameRight = p.x; + mDragWindowHandle.frameBottom = p.y; + + // Pause rotations before a drag. + if (WindowManagerService.DEBUG_ORIENTATION) { + Slog.d(TAG, "Pausing rotation during re-position"); + } + mService.pauseRotationLocked(); + } + + void unregister() { + if (DEBUG_TASK_POSITIONING) { + Slog.d(TAG, "Unregistering task positioner"); + } + + if (mClientChannel == null) { + Slog.e(TAG, "Task positioner not registered"); + return; + } + + mService.mInputManager.unregisterInputChannel(mServerChannel); + + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + mClientChannel.dispose(); + mServerChannel.dispose(); + mClientChannel = null; + mServerChannel = null; + + mDragWindowHandle = null; + mDragApplicationHandle = null; + mDisplay = null; + + // Resume rotations after a drag. + if (WindowManagerService.DEBUG_ORIENTATION) { + Slog.d(TAG, "Resuming rotation after re-position"); + } + mService.resumeRotationLocked(); + } + + void startDragLocked(WindowState win, boolean resize, float startX, float startY) { + if (DEBUG_TASK_POSITIONING) {Slog.d(TAG, + "startDragLocked: win=" + win + ", resize=" + resize + + ", {" + startX + ", " + startY + "}"); + } + mCtrlType = CTRL_NONE; + if (resize) { + final Rect visibleFrame = win.mVisibleFrame; + if (startX < visibleFrame.left) { + mCtrlType |= CTRL_LEFT; + } + if (startX > visibleFrame.right) { + mCtrlType |= CTRL_RIGHT; + } + if (startY < visibleFrame.top) { + mCtrlType |= CTRL_TOP; + } + if (startY > visibleFrame.bottom) { + mCtrlType |= CTRL_BOTTOM; + } + } + + final Task task = win.getTask(); + mTaskId = task.mTaskId; + mStartDragX = startX; + mStartDragY = startY; + + mService.getTaskBounds(mTaskId, mWindowOriginalBounds); + } + + private void notifyMoveLocked(float x, float y) { + if (DEBUG_TASK_POSITIONING) { + Slog.d(TAG, "notifyMoveLw: {" + x + "," + y + "}"); + } + + if (mCtrlType != CTRL_NONE) { + // This is a resizing operation. + final int deltaX = Math.round(x - mStartDragX); + final int deltaY = Math.round(y - mStartDragY); + // TODO: fix the min sizes when we have mininum width/height support, + // use hard-coded min sizes for now. + final int minSizeX = (int)(dipToPx(96)); + final int minSizeY = (int)(dipToPx(64)); + int left = mWindowOriginalBounds.left; + int top = mWindowOriginalBounds.top; + int right = mWindowOriginalBounds.right; + int bottom = mWindowOriginalBounds.bottom; + if ((mCtrlType & CTRL_LEFT) != 0) { + left = Math.min(left + deltaX, right - minSizeX); + } + if ((mCtrlType & CTRL_TOP) != 0) { + top = Math.min(top + deltaY, bottom - minSizeY); + } + if ((mCtrlType & CTRL_RIGHT) != 0) { + right = Math.max(left + minSizeX, right + deltaX); + } + if ((mCtrlType & CTRL_BOTTOM) != 0) { + bottom = Math.max(top + minSizeY, bottom + deltaY); + } + mWindowDragBounds.set(left, top, right, bottom); + } else { + // This is a moving operation. + mWindowDragBounds.set(mWindowOriginalBounds); + mWindowDragBounds.offset(Math.round(x - mStartDragX), + Math.round(y - mStartDragY)); + } + } + + private int getDragLayerLocked() { + return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG) + * WindowManagerService.TYPE_LAYER_MULTIPLIER + + WindowManagerService.TYPE_LAYER_OFFSET; + } + + private float dipToPx(float dip) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, mDisplayMetrics); + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index f21f00aefeb2..ae3bb9b20b04 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -25,7 +25,6 @@ import android.graphics.Rect; import android.os.Debug; import android.os.RemoteException; import android.util.EventLog; -import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.view.DisplayInfo; @@ -34,7 +33,6 @@ import com.android.server.EventLogTags; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; public class TaskStack implements DimLayer.DimLayerUser { @@ -95,6 +93,11 @@ public class TaskStack implements DimLayer.DimLayerUser { } } + boolean allowTaskResize() { + return mStackId == FREEFORM_WORKSPACE_STACK_ID + || mStackId == DOCKED_STACK_ID; + } + /** * Set the bounds of the stack and its containing tasks. * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen. @@ -414,7 +417,7 @@ public class TaskStack implements DimLayer.DimLayerUser { } } if (doAnotherLayoutPass) { - mService.requestTraversalLocked(); + mService.mWindowPlacerLocked.requestTraversal(); } if (mStackId == DOCKED_STACK_ID) { diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java index c97d12f374ad..ce1b785c6d06 100644 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java @@ -47,12 +47,23 @@ public class TaskTapPointerEventListener implements PointerEventListener { public void onPointerEvent(MotionEvent motionEvent) { final int action = motionEvent.getAction(); switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_DOWN: { mPointerId = motionEvent.getPointerId(0); mDownX = motionEvent.getX(); mDownY = motionEvent.getY(); + + final int x = (int) mDownX; + final int y = (int) mDownY; + synchronized (this) { + if (!mTouchExcludeRegion.contains(x, y)) { + mService.mH.obtainMessage(H.TAP_DOWN_OUTSIDE_TASK, x, y, + mDisplayContent).sendToTarget(); + } + } break; - case MotionEvent.ACTION_MOVE: + } + + case MotionEvent.ACTION_MOVE: { if (mPointerId >= 0) { int index = motionEvent.findPointerIndex(mPointerId); if ((motionEvent.getEventTime() - motionEvent.getDownTime()) > TAP_TIMEOUT_MSEC @@ -63,6 +74,8 @@ public class TaskTapPointerEventListener implements PointerEventListener { } } break; + } + case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: { int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index b334a05a4523..ab1bf202f666 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -57,6 +57,7 @@ public class WindowAnimator { final WindowManagerService mService; final Context mContext; final WindowManagerPolicy mPolicy; + private final WindowSurfacePlacer mWindowPlacerLocked; /** Is any window animating? */ boolean mAnimating; @@ -112,6 +113,7 @@ public class WindowAnimator { mService = service; mContext = service.mContext; mPolicy = service.mPolicy; + mWindowPlacerLocked = service.mWindowPlacerLocked; mAnimationFrameCallback = new Choreographer.FrameCallback() { public void doFrame(long frameTimeNs) { @@ -300,7 +302,7 @@ public class WindowAnimator { setPendingLayoutChanges(Display.DEFAULT_DISPLAY, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.mWindowPlacerLocked.debugLayoutRepeats( + mWindowPlacerLocked.debugLayoutRepeats( "updateWindowsAndWallpaperLocked 2", getPendingLayoutChanges(Display.DEFAULT_DISPLAY)); } @@ -315,7 +317,7 @@ public class WindowAnimator { setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.mWindowPlacerLocked.debugLayoutRepeats( + mWindowPlacerLocked.debugLayoutRepeats( "updateWindowsAndWallpaperLocked 3", getPendingLayoutChanges(displayId)); } @@ -410,7 +412,7 @@ public class WindowAnimator { setPendingLayoutChanges(Display.DEFAULT_DISPLAY, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.mWindowPlacerLocked.debugLayoutRepeats( + mWindowPlacerLocked.debugLayoutRepeats( "updateWindowsAndWallpaperLocked 4", getPendingLayoutChanges(Display.DEFAULT_DISPLAY)); } @@ -435,7 +437,7 @@ public class WindowAnimator { setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.mWindowPlacerLocked.debugLayoutRepeats( + mWindowPlacerLocked.debugLayoutRepeats( "updateWindowsAndWallpaperLocked 5", getPendingLayoutChanges(displayId)); } @@ -741,15 +743,15 @@ public class WindowAnimator { boolean doRequest = false; if (mBulkUpdateParams != 0) { - doRequest = mService.mWindowPlacerLocked.copyAnimToLayoutParamsLocked(); + doRequest = mWindowPlacerLocked.copyAnimToLayoutParamsLocked(); } if (hasPendingLayoutChanges || doRequest) { - mService.requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } if (!mAnimating && wasAnimating) { - mService.requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } if (WindowManagerService.DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating @@ -850,7 +852,7 @@ public class WindowAnimator { if (displayId == windows.get(i).getDisplayId()) { setPendingLayoutChanges(displayId, changes); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.mWindowPlacerLocked.debugLayoutRepeats(reason, + mWindowPlacerLocked.debugLayoutRepeats(reason, getPendingLayoutChanges(displayId)); } break; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f3e7d1d58313..d345aca6892c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -207,6 +207,7 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_SURFACE_TRACE = false; static final boolean DEBUG_WINDOW_TRACE = false; static final boolean DEBUG_TASK_MOVEMENT = false; + static final boolean DEBUG_TASK_POSITIONING = false; static final boolean DEBUG_STACK = false; static final boolean DEBUG_DISPLAY = false; static final boolean DEBUG_POWER = false; @@ -471,7 +472,6 @@ public class WindowManagerService extends IWindowManager.Stub int mSystemDecorLayer = 0; final Rect mScreenRect = new Rect(); - boolean mTraversalScheduled = false; boolean mDisplayFrozen = false; long mDisplayFreezeTime = 0; int mLastDisplayFreezeDuration = 0; @@ -599,6 +599,7 @@ public class WindowManagerService extends IWindowManager.Stub // Whether or not a layout can cause a wake up when theater mode is enabled. boolean mAllowTheaterModeWakeFromLayout; + TaskPositioner mTaskPositioner; DragState mDragState = null; // For frozen screen animations. @@ -1790,7 +1791,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) { // No need for this guy! - if (localLOGV) Slog.v( + if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v( TAG, "**** NO NEED TO START: " + attrs.getTitle()); return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED; } @@ -2773,7 +2774,7 @@ public class WindowManagerService extends IWindowManager.Stub if (displayContent != null) { displayContent.layoutNeeded = true; } - requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } } } finally { @@ -3472,35 +3473,12 @@ public class WindowManagerService extends IWindowManager.Stub "prepareAppTransition()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } - synchronized(mWindowMap) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Prepare app transition:" - + " transit=" + AppTransition.appTransitionToString(transit) - + " " + mAppTransition - + " alwaysKeepCurrent=" + alwaysKeepCurrent - + " Callers=" + Debug.getCallers(3)); - if (!mAppTransition.isTransitionSet() || mAppTransition.isTransitionNone()) { - mAppTransition.setAppTransition(transit); - } else if (!alwaysKeepCurrent) { - if (transit == AppTransition.TRANSIT_TASK_OPEN - && mAppTransition.isTransitionEqual( - AppTransition.TRANSIT_TASK_CLOSE)) { - // Opening a new task always supersedes a close for the anim. - mAppTransition.setAppTransition(transit); - } else if (transit == AppTransition.TRANSIT_ACTIVITY_OPEN - && mAppTransition.isTransitionEqual( - AppTransition.TRANSIT_ACTIVITY_CLOSE)) { - // Opening a new activity always supersedes a close for the anim. - mAppTransition.setAppTransition(transit); - } - } - if (okToDisplay() && mAppTransition.prepare()) { + boolean prepared = mAppTransition.prepareAppTransitionLocked( + transit, alwaysKeepCurrent); + if (prepared && okToDisplay()) { mSkipAppTransitionAnimation = false; } - if (mAppTransition.isTransitionSet()) { - mH.removeMessages(H.APP_TRANSITION_TIMEOUT); - mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, 5000); - } } } @@ -3624,108 +3602,8 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (transferFrom != null) { - AppWindowToken ttoken = findAppWindowToken(transferFrom); - if (ttoken != null) { - WindowState startingWindow = ttoken.startingWindow; - if (startingWindow != null) { - // In this case, the starting icon has already been displayed, so start - // letting windows get shown immediately without any more transitions. - mSkipAppTransitionAnimation = true; - - if (DEBUG_STARTING_WINDOW) Slog.v(TAG, - "Moving existing starting " + startingWindow + " from " + ttoken - + " to " + wtoken); - final long origId = Binder.clearCallingIdentity(); - - // Transfer the starting window over to the new token. - wtoken.startingData = ttoken.startingData; - wtoken.startingView = ttoken.startingView; - wtoken.startingDisplayed = ttoken.startingDisplayed; - ttoken.startingDisplayed = false; - wtoken.startingWindow = startingWindow; - wtoken.reportedVisible = ttoken.reportedVisible; - ttoken.startingData = null; - ttoken.startingView = null; - ttoken.startingWindow = null; - ttoken.startingMoved = true; - startingWindow.mToken = wtoken; - startingWindow.mRootToken = wtoken; - startingWindow.mAppToken = wtoken; - - if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) { - Slog.v(TAG, "Removing starting window: " + startingWindow); - } - startingWindow.getWindowList().remove(startingWindow); - mWindowsChanged = true; - if (DEBUG_ADD_REMOVE) Slog.v(TAG, - "Removing starting " + startingWindow + " from " + ttoken); - ttoken.windows.remove(startingWindow); - ttoken.allAppWindows.remove(startingWindow); - addWindowToListInOrderLocked(startingWindow, true); - - // Propagate other interesting state between the - // tokens. If the old token is displayed, we should - // immediately force the new one to be displayed. If - // it is animating, we need to move that animation to - // the new one. - if (ttoken.allDrawn) { - wtoken.allDrawn = true; - wtoken.deferClearAllDrawn = ttoken.deferClearAllDrawn; - } - if (ttoken.firstWindowDrawn) { - wtoken.firstWindowDrawn = true; - } - if (!ttoken.hidden) { - wtoken.hidden = false; - wtoken.hiddenRequested = false; - wtoken.willBeHidden = false; - } - if (wtoken.clientHidden != ttoken.clientHidden) { - wtoken.clientHidden = ttoken.clientHidden; - wtoken.sendAppVisibilityToClients(); - } - ttoken.mAppAnimator.transferCurrentAnimation( - wtoken.mAppAnimator, startingWindow.mWinAnimator); - - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, - true /*updateInputWindows*/); - getDefaultDisplayContentLocked().layoutNeeded = true; - mWindowPlacerLocked.performSurfacePlacement(); - Binder.restoreCallingIdentity(origId); - return; - } else if (ttoken.startingData != null) { - // The previous app was getting ready to show a - // starting window, but hasn't yet done so. Steal it! - if (DEBUG_STARTING_WINDOW) Slog.v(TAG, - "Moving pending starting from " + ttoken - + " to " + wtoken); - wtoken.startingData = ttoken.startingData; - ttoken.startingData = null; - ttoken.startingMoved = true; - Message m = mH.obtainMessage(H.ADD_STARTING, wtoken); - // Note: we really want to do sendMessageAtFrontOfQueue() because we - // want to process the message ASAP, before any other queued - // messages. - mH.sendMessageAtFrontOfQueue(m); - return; - } - final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator; - final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator; - if (tAppAnimator.thumbnail != null) { - // The old token is animating with a thumbnail, transfer - // that to the new token. - if (wAppAnimator.thumbnail != null) { - wAppAnimator.thumbnail.destroy(); - } - wAppAnimator.thumbnail = tAppAnimator.thumbnail; - wAppAnimator.thumbnailX = tAppAnimator.thumbnailX; - wAppAnimator.thumbnailY = tAppAnimator.thumbnailY; - wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer; - wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation; - tAppAnimator.thumbnail = null; - } - } + if (transferStartingWindow(transferFrom, wtoken)) { + return; } // There is no existing starting window, and the caller doesn't @@ -3748,30 +3626,28 @@ public class WindowManagerService extends IWindowManager.Stub // pretend like we didn't see that. return; } - if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Translucent=" - + ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsTranslucent, false) - + " Floating=" - + ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsFloating, false) - + " ShowWallpaper=" - + ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowShowWallpaper, false)); final boolean windowIsTranslucentDefined = ent.array.hasValue( com.android.internal.R.styleable.Window_windowIsTranslucent); final boolean windowIsTranslucent = ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsTranslucent, false); final boolean windowSwipeToDismiss = ent.array.getBoolean( com.android.internal.R.styleable.Window_windowSwipeToDismiss, false); + final boolean windowIsFloating = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowIsFloating, false); + final boolean windowShowWallpaper = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowShowWallpaper, false); + final boolean windowDisableStarting = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowDisablePreview, false); + if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Translucent=" + windowIsTranslucent + + " Floating=" + windowIsFloating + + " ShowWallpaper=" + windowShowWallpaper); if (windowIsTranslucent || (!windowIsTranslucentDefined && windowSwipeToDismiss)) { return; } - if (ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsFloating, false)) { + if (windowIsFloating || windowDisableStarting) { return; } - if (ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowShowWallpaper, false)) { + if (windowShowWallpaper) { if (mWallpaperControllerLocked.getWallpaperTarget() == null) { // If this theme is requesting a wallpaper, and the wallpaper // is not curently visible, then this effectively serves as @@ -3797,6 +3673,113 @@ public class WindowManagerService extends IWindowManager.Stub } } + private boolean transferStartingWindow(IBinder transferFrom, AppWindowToken wtoken) { + if (transferFrom == null) { + return false; + } + AppWindowToken ttoken = findAppWindowToken(transferFrom); + if (ttoken == null) { + return false; + } + WindowState startingWindow = ttoken.startingWindow; + if (startingWindow != null) { + // In this case, the starting icon has already been displayed, so start + // letting windows get shown immediately without any more transitions. + mSkipAppTransitionAnimation = true; + + if (DEBUG_STARTING_WINDOW) Slog.v(TAG, + "Moving existing starting " + startingWindow + " from " + ttoken + + " to " + wtoken); + final long origId = Binder.clearCallingIdentity(); + + // Transfer the starting window over to the new token. + wtoken.startingData = ttoken.startingData; + wtoken.startingView = ttoken.startingView; + wtoken.startingDisplayed = ttoken.startingDisplayed; + ttoken.startingDisplayed = false; + wtoken.startingWindow = startingWindow; + wtoken.reportedVisible = ttoken.reportedVisible; + ttoken.startingData = null; + ttoken.startingView = null; + ttoken.startingWindow = null; + ttoken.startingMoved = true; + startingWindow.mToken = wtoken; + startingWindow.mRootToken = wtoken; + startingWindow.mAppToken = wtoken; + + if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) { + Slog.v(TAG, "Removing starting window: " + startingWindow); + } + startingWindow.getWindowList().remove(startingWindow); + mWindowsChanged = true; + if (DEBUG_ADD_REMOVE) Slog.v(TAG, + "Removing starting " + startingWindow + " from " + ttoken); + ttoken.windows.remove(startingWindow); + ttoken.allAppWindows.remove(startingWindow); + addWindowToListInOrderLocked(startingWindow, true); + + // Propagate other interesting state between the + // tokens. If the old token is displayed, we should + // immediately force the new one to be displayed. If + // it is animating, we need to move that animation to + // the new one. + if (ttoken.allDrawn) { + wtoken.allDrawn = true; + wtoken.deferClearAllDrawn = ttoken.deferClearAllDrawn; + } + if (ttoken.firstWindowDrawn) { + wtoken.firstWindowDrawn = true; + } + if (!ttoken.hidden) { + wtoken.hidden = false; + wtoken.hiddenRequested = false; + wtoken.willBeHidden = false; + } + if (wtoken.clientHidden != ttoken.clientHidden) { + wtoken.clientHidden = ttoken.clientHidden; + wtoken.sendAppVisibilityToClients(); + } + ttoken.mAppAnimator.transferCurrentAnimation( + wtoken.mAppAnimator, startingWindow.mWinAnimator); + + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + true /*updateInputWindows*/); + getDefaultDisplayContentLocked().layoutNeeded = true; + mWindowPlacerLocked.performSurfacePlacement(); + Binder.restoreCallingIdentity(origId); + return true; + } else if (ttoken.startingData != null) { + // The previous app was getting ready to show a + // starting window, but hasn't yet done so. Steal it! + if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Moving pending starting from " + ttoken + + " to " + wtoken); + wtoken.startingData = ttoken.startingData; + ttoken.startingData = null; + ttoken.startingMoved = true; + Message m = mH.obtainMessage(H.ADD_STARTING, wtoken); + // Note: we really want to do sendMessageAtFrontOfQueue() because we + // want to process the message ASAP, before any other queued + // messages. + mH.sendMessageAtFrontOfQueue(m); + return true; + } + final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator; + final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator; + if (tAppAnimator.thumbnail != null) { + // The old token is animating with a thumbnail, transfer that to the new token. + if (wAppAnimator.thumbnail != null) { + wAppAnimator.thumbnail.destroy(); + } + wAppAnimator.thumbnail = tAppAnimator.thumbnail; + wAppAnimator.thumbnailX = tAppAnimator.thumbnailX; + wAppAnimator.thumbnailY = tAppAnimator.thumbnailY; + wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer; + wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation; + tAppAnimator.thumbnail = null; + } + return false; + } + public void removeAppStartingWindow(IBinder token) { synchronized (mWindowMap) { AppWindowToken wtoken = mTokenMap.get(token).appWindowToken; @@ -3831,7 +3814,7 @@ public class WindowManagerService extends IWindowManager.Stub if (atoken != null) { atoken.appFullscreen = toOpaque; setWindowOpaqueLocked(token, toOpaque); - requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } } } @@ -4836,7 +4819,7 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator.mKeyguardGoingAway = true; mAnimator.mKeyguardGoingAwayToNotificationShade = keyguardGoingToNotificationShade; mAnimator.mKeyguardGoingAwayDisableWindowAnimations = disableWindowAnimations; - requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } } @@ -6836,6 +6819,88 @@ public class WindowManagerService extends IWindowManager.Stub } } + boolean startMovingTask(IWindow window, float startX, float startY) { + WindowState callingWin = null; + synchronized (mWindowMap) { + callingWin = windowForClientLocked(null, window, false); + if (!startPositioningLocked(callingWin, false /*resize*/, startX, startY)) { + return false; + } + } + try { + mActivityManager.setFocusedTask(callingWin.getTask().mTaskId); + } catch(RemoteException e) {} + return true; + } + + private void startResizingTask(DisplayContent displayContent, int startX, int startY) { + int taskId = -1; + AppWindowToken atoken = null; + synchronized (mWindowMap) { + taskId = displayContent.taskIdForControlPoint(startX, startY); + Task task = mTaskIdToTask.get(taskId); + if (task == null || task.mAppTokens == null) { + return; + } + AppTokenList tokens = task.mAppTokens; + atoken = tokens.get(tokens.size() - 1); + WindowState win = atoken.findMainWindow(); + if (!startPositioningLocked(win, true /*resize*/, startX, startY)) { + return; + } + } + try { + mActivityManager.setFocusedTask(taskId); + } catch(RemoteException e) {} + } + + private boolean startPositioningLocked( + WindowState win, boolean resize, float startX, float startY) { + if (WindowManagerService.DEBUG_TASK_POSITIONING) { + Slog.d(TAG, "startPositioningLocked: win=" + win + + ", resize=" + resize + ", {" + startX + ", " + startY + "}"); + } + if (win == null || win.getAppToken() == null || !win.inFreeformWorkspace()) { + Slog.w(TAG, "startPositioningLocked: Bad window " + win); + return false; + } + + final DisplayContent displayContent = win.getDisplayContent(); + if (displayContent == null) { + Slog.w(TAG, "startPositioningLocked: Invalid display content " + win); + return false; + } + + Display display = displayContent.getDisplay(); + mTaskPositioner = new TaskPositioner(this); + mTaskPositioner.register(display); + mInputMonitor.updateInputWindowsLw(true /*force*/); + if (!mInputManager.transferTouchFocus( + win.mInputChannel, mTaskPositioner.mServerChannel)) { + Slog.e(TAG, "startPositioningLocked: Unable to transfer touch focus"); + mTaskPositioner.unregister(); + mTaskPositioner = null; + mInputMonitor.updateInputWindowsLw(true /*force*/); + return false; + } + + mTaskPositioner.startDragLocked(win, resize, startX, startY); + return true; + } + + private void finishPositioning() { + if (WindowManagerService.DEBUG_TASK_POSITIONING) { + Slog.d(TAG, "finishPositioning"); + } + synchronized (mWindowMap) { + if (mTaskPositioner != null) { + mTaskPositioner.unregister(); + mTaskPositioner = null; + mInputMonitor.updateInputWindowsLw(true /*force*/); + } + } + } + // ------------------------------------------------------------- // Drag and drop // ------------------------------------------------------------- @@ -7101,6 +7166,9 @@ public class WindowManagerService extends IWindowManager.Stub public static final int RESET_ANR_MESSAGE = 38; public static final int WALLPAPER_DRAW_PENDING_TIMEOUT = 39; + public static final int TAP_DOWN_OUTSIDE_TASK = 40; + public static final int FINISH_TASK_POSITIONING = 41; + @Override public void handleMessage(Message msg) { if (DEBUG_WINDOW_TRACE) { @@ -7175,7 +7243,6 @@ public class WindowManagerService extends IWindowManager.Stub case DO_TRAVERSAL: { synchronized(mWindowMap) { - mTraversalScheduled = false; mWindowPlacerLocked.performSurfacePlacement(); } } break; @@ -7562,6 +7629,17 @@ public class WindowManagerService extends IWindowManager.Stub } } break; + + case TAP_DOWN_OUTSIDE_TASK: { + startResizingTask((DisplayContent)msg.obj, msg.arg1, msg.arg2); + } + break; + + case FINISH_TASK_POSITIONING: { + finishPositioning(); + } + break; + case NOTIFY_ACTIVITY_DRAWN: try { mActivityManager.notifyActivityDrawn((IBinder) msg.obj); @@ -8458,14 +8536,7 @@ public class WindowManagerService extends IWindowManager.Stub void requestTraversal() { synchronized (mWindowMap) { - requestTraversalLocked(); - } - } - - void requestTraversalLocked() { - if (!mTraversalScheduled) { - mTraversalScheduled = true; - mH.sendEmptyMessage(H.DO_TRAVERSAL); + mWindowPlacerLocked.requestTraversal(); } } @@ -9329,6 +9400,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mInputMethodWindow != null) { pw.print(" mInputMethodWindow="); pw.println(mInputMethodWindow); } + mWindowPlacerLocked.dump(pw, " "); mWallpaperControllerLocked.dump(pw, " "); if (mInputMethodAnimLayerAdjustment != 0 || mWallpaperControllerLocked.getAnimLayerAdjustment() != 0) { @@ -9364,8 +9436,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" window="); pw.print(mWindowAnimationScaleSetting); pw.print(" transition="); pw.print(mTransitionAnimationScaleSetting); pw.print(" animator="); pw.println(mAnimatorDurationScaleSetting); - pw.print(" mTraversalScheduled="); pw.println(mTraversalScheduled); - pw.print(" mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation); + pw.print(" mSkipAppTransitionAnimation=");pw.println(mSkipAppTransitionAnimation); pw.println(" mLayoutToAnim:"); mAppTransition.dump(pw, " "); } @@ -9723,7 +9794,7 @@ public class WindowManagerService extends IWindowManager.Stub createDisplayContentLocked(display); displayReady(displayId); } - requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } } @@ -9746,7 +9817,7 @@ public class WindowManagerService extends IWindowManager.Stub } } mAnimator.removeDisplayLocked(displayId); - requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } public void onDisplayChanged(int displayId) { @@ -9758,7 +9829,7 @@ public class WindowManagerService extends IWindowManager.Stub if (displayContent != null) { displayContent.updateDisplayInfo(); } - requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } @Override @@ -9905,7 +9976,7 @@ public class WindowManagerService extends IWindowManager.Stub } } } - requestTraversalLocked(); + mWindowPlacerLocked.requestTraversal(); } mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT); if (mWaitingForDrawn.isEmpty()) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d756d3912170..789354db1739 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -86,7 +86,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { // The thickness of a window resize handle outside the window bounds on the free form workspace // to capture touch events in that area. - private static final int RESIZE_HANDLE_WIDTH_IN_DP = 10; + static final int RESIZE_HANDLE_WIDTH_IN_DP = 10; static final boolean BOUNDS_FOR_TOUCH = true; @@ -1640,7 +1640,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { Slog.w(TAG, "Failed to report 'resized' to the client of " + this + ", removing this window."); mService.mPendingRemove.add(this); - mService.requestTraversalLocked(); + mService.mWindowPlacerLocked.requestTraversal(); } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 066dc9554ebc..bf1ab8fab7d0 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1446,7 +1446,7 @@ class WindowStateAnimator { updateSurfaceWindowCrop(recoveringMemory); } - public void prepareSurfaceLocked(final boolean recoveringMemory) { + void prepareSurfaceLocked(final boolean recoveringMemory) { final WindowState w = mWin; if (mSurfaceControl == null) { if (w.mOrientationChanging) { @@ -1782,17 +1782,14 @@ class WindowStateAnimator { * * @return Returns true if the surface was successfully shown. */ - boolean showSurfaceRobustlyLocked() { + private boolean showSurfaceRobustlyLocked() { try { - if (mSurfaceControl != null) { - mSurfaceShown = true; - mSurfaceControl.show(); - if (mWin.mTurnOnScreen) { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "Show surface turning screen on: " + mWin); - mWin.mTurnOnScreen = false; - mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN; - } + mSurfaceShown = true; + mSurfaceControl.show(); + if (mWin.mTurnOnScreen) { + if (DEBUG_VISIBILITY) Slog.v(TAG, "Show surface turning screen on: " + mWin); + mWin.mTurnOnScreen = false; + mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN; } return true; } catch (RuntimeException e) { diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 8582ca28bd4a..52efa6803499 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -57,6 +57,7 @@ import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import java.io.PrintWriter; import java.util.ArrayList; /** @@ -108,6 +109,8 @@ class WindowSurfacePlacer { private int mPreferredModeId = 0; + private boolean mTraversalScheduled; + public WindowSurfacePlacer(WindowManagerService service) { mService = service; mWallpaperControllerLocked = mService.mWallpaperControllerLocked; @@ -116,11 +119,11 @@ class WindowSurfacePlacer { final void performSurfacePlacement() { int loopCount = 6; do { - mService.mTraversalScheduled = false; + mTraversalScheduled = false; performSurfacePlacementLoop(); mService.mH.removeMessages(DO_TRAVERSAL); loopCount--; - } while (mService.mTraversalScheduled && loopCount > 0); + } while (mTraversalScheduled && loopCount > 0); mWallpaperActionPending = false; } @@ -177,7 +180,7 @@ class WindowSurfacePlacer { if (mService.needsLayout()) { if (++mLayoutRepeatCount < 6) { - mService.requestTraversalLocked(); + requestTraversal(); } else { Slog.e(TAG, "Performed 6 layouts in a row. Skipping"); mLayoutRepeatCount = 0; @@ -257,290 +260,7 @@ class WindowSurfacePlacer { ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces"); SurfaceControl.openTransaction(); try { - - if (mService.mWatermark != null) { - mService.mWatermark.positionSurface(defaultDw, defaultDh); - } - if (mService.mStrictModeFlash != null) { - mService.mStrictModeFlash.positionSurface(defaultDw, defaultDh); - } - if (mService.mCircularDisplayMask != null) { - mService.mCircularDisplayMask.positionSurface(defaultDw, defaultDh, - mService.mRotation); - } - if (mService.mEmulatorDisplayOverlay != null) { - mService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh, - mService.mRotation); - } - - boolean focusDisplayed = false; - - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final DisplayContent displayContent = mService.mDisplayContents.valueAt(displayNdx); - boolean updateAllDrawn = false; - WindowList windows = displayContent.getWindowList(); - DisplayInfo displayInfo = displayContent.getDisplayInfo(); - final int displayId = displayContent.getDisplayId(); - final int dw = displayInfo.logicalWidth; - final int dh = displayInfo.logicalHeight; - final int innerDw = displayInfo.appWidth; - final int innerDh = displayInfo.appHeight; - final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); - - // Reset for each display. - mDisplayHasContent = false; - mPreferredRefreshRate = 0; - mPreferredModeId = 0; - - int repeats = 0; - do { - repeats++; - if (repeats > 6) { - Slog.w(TAG, "Animation repeat aborted after too many iterations"); - displayContent.layoutNeeded = false; - break; - } - - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats( - "On entry to LockedInner", displayContent.pendingLayoutChanges); - - if ((displayContent.pendingLayoutChanges - & FINISH_LAYOUT_REDO_WALLPAPER) != 0 - && mWallpaperControllerLocked.adjustWallpaperWindows()) { - mService.assignLayersLocked(windows); - displayContent.layoutNeeded = true; - } - - if (isDefaultDisplay && (displayContent.pendingLayoutChanges - & FINISH_LAYOUT_REDO_CONFIG) != 0) { - if (DEBUG_LAYOUT) Slog.v(TAG, - "Computing new config from layout"); - if (mService.updateOrientationFromAppTokensLocked(true)) { - displayContent.layoutNeeded = true; - mService.mH.sendEmptyMessage( - SEND_NEW_CONFIGURATION); - } - } - - if ((displayContent.pendingLayoutChanges - & FINISH_LAYOUT_REDO_LAYOUT) != 0) { - displayContent.layoutNeeded = true; - } - - // FIRST LOOP: Perform a layout, if needed. - if (repeats < LAYOUT_REPEAT_THRESHOLD) { - performLayoutLockedInner(displayContent, repeats == 1, - false /*updateInputWindows*/); - } else { - Slog.w(TAG, "Layout repeat skipped after too many iterations"); - } - - // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think - // it is animating. - displayContent.pendingLayoutChanges = 0; - - if (isDefaultDisplay) { - mService.mPolicy.beginPostLayoutPolicyLw(dw, dh); - for (i = windows.size() - 1; i >= 0; i--) { - WindowState w = windows.get(i); - if (w.mHasSurface) { - mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, - w.mAttachedWindow); - } - } - displayContent.pendingLayoutChanges |= - mService.mPolicy.finishPostLayoutPolicyLw(); - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats( - "after finishPostLayoutPolicyLw", - displayContent.pendingLayoutChanges); - } - } while (displayContent.pendingLayoutChanges != 0); - - mObscured = false; - mSyswin = false; - displayContent.resetDimming(); - - // Only used if default window - final boolean someoneLosingFocus = !mService.mLosingFocus.isEmpty(); - - final int N = windows.size(); - for (i=N-1; i>=0; i--) { - WindowState w = windows.get(i); - final Task task = w.getTask(); - if (task == null && w.getAttrs().type != TYPE_PRIVATE_PRESENTATION) { - continue; - } - - final boolean obscuredChanged = w.mObscured != mObscured; - - // Update effect. - w.mObscured = mObscured; - if (!mObscured) { - handleNotObscuredLocked(w, innerDw, innerDh); - } - - if (task != null && !task.getContinueDimming()) { - w.handleFlagDimBehind(); - } - - if (isDefaultDisplay && obscuredChanged - && mWallpaperControllerLocked.isWallpaperTarget(w) - && w.isVisibleLw()) { - // This is the wallpaper target and its obscured state - // changed... make sure the current wallaper's visibility - // has been updated accordingly. - mWallpaperControllerLocked.updateWallpaperVisibility(); - } - - final WindowStateAnimator winAnimator = w.mWinAnimator; - - // If the window has moved due to its containing content frame changing, then - // notify the listeners and optionally animate it. - if (w.hasMoved()) { - // Frame has moved, containing content frame has also moved, and we're not - // currently animating... let's do something. - final int left = w.mFrame.left; - final int top = w.mFrame.top; - if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0) { - Animation a = AnimationUtils.loadAnimation(mService.mContext, - com.android.internal.R.anim.window_move_from_decor); - winAnimator.setAnimation(a); - winAnimator.mAnimDw = w.mLastFrame.left - left; - winAnimator.mAnimDh = w.mLastFrame.top - top; - winAnimator.mAnimateMove = true; - winAnimator.mAnimatingMove = true; - } - - //TODO (multidisplay): Accessibility supported only for the default display. - if (mService.mAccessibilityController != null - && displayId == Display.DEFAULT_DISPLAY) { - mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(); - } - - try { - w.mClient.moved(left, top); - } catch (RemoteException e) { - } - } - - //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); - w.mContentChanged = false; - - // Moved from updateWindowsAndWallpaperLocked(). - if (w.mHasSurface) { - // Take care of the window being ready to display. - final boolean committed = winAnimator.commitFinishDrawingLocked(); - if (isDefaultDisplay && committed) { - if (w.mAttrs.type == TYPE_DREAM) { - // HACK: When a dream is shown, it may at that - // point hide the lock screen. So we need to - // redo the layout to let the phone window manager - // make this happen. - displayContent.pendingLayoutChanges |= - FINISH_LAYOUT_REDO_LAYOUT; - if (DEBUG_LAYOUT_REPEATS) { - debugLayoutRepeats( - "dream and commitFinishDrawingLocked true", - displayContent.pendingLayoutChanges); - } - } - if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "First draw done in potential wallpaper target " + w); - mWallpaperMayChange = true; - displayContent.pendingLayoutChanges |= - FINISH_LAYOUT_REDO_WALLPAPER; - if (DEBUG_LAYOUT_REPEATS) { - debugLayoutRepeats( - "wallpaper and commitFinishDrawingLocked true", - displayContent.pendingLayoutChanges); - } - } - } - - winAnimator.setSurfaceBoundariesLocked(recoveringMemory); - } - - final AppWindowToken atoken = w.mAppToken; - if (DEBUG_STARTING_WINDOW && atoken != null - && w == atoken.startingWindow) { - Slog.d(TAG, "updateWindows: starting " + w - + " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn - + " freezingScreen=" + atoken.mAppAnimator.freezingScreen); - } - if (atoken != null - && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) { - if (atoken.lastTransactionSequence != mService.mTransactionSequence) { - atoken.lastTransactionSequence = mService.mTransactionSequence; - atoken.numInterestingWindows = atoken.numDrawnWindows = 0; - atoken.startingDisplayed = false; - } - if ((w.isOnScreenIgnoringKeyguard() - || winAnimator.mAttrType == TYPE_BASE_APPLICATION) - && !w.mExiting && !w.mDestroying) { - if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { - Slog.v(TAG, "Eval win " + w + ": isDrawn=" - + w.isDrawnLw() - + ", isAnimating=" + winAnimator.isAnimating()); - if (!w.isDrawnLw()) { - Slog.v(TAG, "Not displayed: s=" - + winAnimator.mSurfaceControl - + " pv=" + w.mPolicyVisibility - + " mDrawState=" + winAnimator.drawStateToString() - + " ah=" + w.mAttachedHidden - + " th=" + atoken.hiddenRequested - + " a=" + winAnimator.mAnimating); - } - } - if (w != atoken.startingWindow) { - if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) { - atoken.numInterestingWindows++; - if (w.isDrawnLw()) { - atoken.numDrawnWindows++; - if (DEBUG_VISIBILITY - || DEBUG_ORIENTATION) - Slog.v(TAG, - "tokenMayBeDrawn: " + atoken - + " freezingScreen=" - + atoken.mAppAnimator.freezingScreen - + " mAppFreezing=" + w.mAppFreezing); - updateAllDrawn = true; - } - } - } else if (w.isDrawnLw()) { - atoken.startingDisplayed = true; - } - } - } - - if (isDefaultDisplay && someoneLosingFocus && (w == mService.mCurrentFocus) - && w.isDisplayedLw()) { - focusDisplayed = true; - } - - mService.updateResizingWindows(w); - } - - mService.mDisplayManagerInternal.setDisplayProperties(displayId, - mDisplayHasContent, - mPreferredRefreshRate, - mPreferredModeId, - true /* inTraversal, must call performTraversalInTrans... below */); - - mService.getDisplayContentLocked(displayId).stopDimmingIfNeeded(); - - if (updateAllDrawn) { - updateAllDrawnLocked(displayContent); - } - } - - if (focusDisplayed) { - mService.mH.sendEmptyMessage(REPORT_LOSING_FOCUS); - } - - // Give the display manager a chance to adjust properties - // like display rotation if it needs to. - mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager(); + applySurfaceChangesTransaction(recoveringMemory, numDisplays, defaultDw, defaultDh); } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { @@ -801,6 +521,279 @@ class WindowSurfacePlacer { } } + private void applySurfaceChangesTransaction(boolean recoveringMemory, int numDisplays, + int defaultDw, int defaultDh) { + if (mService.mWatermark != null) { + mService.mWatermark.positionSurface(defaultDw, defaultDh); + } + if (mService.mStrictModeFlash != null) { + mService.mStrictModeFlash.positionSurface(defaultDw, defaultDh); + } + if (mService.mCircularDisplayMask != null) { + mService.mCircularDisplayMask.positionSurface(defaultDw, defaultDh, + mService.mRotation); + } + if (mService.mEmulatorDisplayOverlay != null) { + mService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh, + mService.mRotation); + } + + boolean focusDisplayed = false; + + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + final DisplayContent displayContent = mService.mDisplayContents.valueAt(displayNdx); + boolean updateAllDrawn = false; + WindowList windows = displayContent.getWindowList(); + DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final int displayId = displayContent.getDisplayId(); + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; + final int innerDw = displayInfo.appWidth; + final int innerDh = displayInfo.appHeight; + final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + + // Reset for each display. + mDisplayHasContent = false; + mPreferredRefreshRate = 0; + mPreferredModeId = 0; + + int repeats = 0; + do { + repeats++; + if (repeats > 6) { + Slog.w(TAG, "Animation repeat aborted after too many iterations"); + displayContent.layoutNeeded = false; + break; + } + + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats( + "On entry to LockedInner", displayContent.pendingLayoutChanges); + + if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0 && + mWallpaperControllerLocked.adjustWallpaperWindows()) { + mService.assignLayersLocked(windows); + displayContent.layoutNeeded = true; + } + + if (isDefaultDisplay + && (displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) { + if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout"); + if (mService.updateOrientationFromAppTokensLocked(true)) { + displayContent.layoutNeeded = true; + mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION); + } + } + + if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_LAYOUT) != 0) { + displayContent.layoutNeeded = true; + } + + // FIRST LOOP: Perform a layout, if needed. + if (repeats < LAYOUT_REPEAT_THRESHOLD) { + performLayoutLockedInner(displayContent, repeats == 1, + false /* updateInputWindows */); + } else { + Slog.w(TAG, "Layout repeat skipped after too many iterations"); + } + + // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think + // it is animating. + displayContent.pendingLayoutChanges = 0; + + if (isDefaultDisplay) { + mService.mPolicy.beginPostLayoutPolicyLw(dw, dh); + for (int i = windows.size() - 1; i >= 0; i--) { + WindowState w = windows.get(i); + if (w.mHasSurface) { + mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, + w.mAttachedWindow); + } + } + displayContent.pendingLayoutChanges |= + mService.mPolicy.finishPostLayoutPolicyLw(); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after finishPostLayoutPolicyLw", + displayContent.pendingLayoutChanges); + } + } while (displayContent.pendingLayoutChanges != 0); + + mObscured = false; + mSyswin = false; + displayContent.resetDimming(); + + // Only used if default window + final boolean someoneLosingFocus = !mService.mLosingFocus.isEmpty(); + + for (int i = windows.size() - 1; i >= 0; i--) { + WindowState w = windows.get(i); + final Task task = w.getTask(); + if (task == null && w.getAttrs().type != TYPE_PRIVATE_PRESENTATION) { + continue; + } + + final boolean obscuredChanged = w.mObscured != mObscured; + + // Update effect. + w.mObscured = mObscured; + if (!mObscured) { + handleNotObscuredLocked(w, innerDw, innerDh); + } + + if (task != null && !task.getContinueDimming()) { + w.handleFlagDimBehind(); + } + + if (isDefaultDisplay && obscuredChanged + && mWallpaperControllerLocked.isWallpaperTarget(w) && w.isVisibleLw()) { + // This is the wallpaper target and its obscured state + // changed... make sure the current wallaper's visibility + // has been updated accordingly. + mWallpaperControllerLocked.updateWallpaperVisibility(); + } + + final WindowStateAnimator winAnimator = w.mWinAnimator; + + // If the window has moved due to its containing content frame changing, then + // notify the listeners and optionally animate it. + if (w.hasMoved()) { + // Frame has moved, containing content frame has also moved, and we're not + // currently animating... let's do something. + final int left = w.mFrame.left; + final int top = w.mFrame.top; + if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0) { + Animation a = AnimationUtils.loadAnimation(mService.mContext, + com.android.internal.R.anim.window_move_from_decor); + winAnimator.setAnimation(a); + winAnimator.mAnimDw = w.mLastFrame.left - left; + winAnimator.mAnimDh = w.mLastFrame.top - top; + winAnimator.mAnimateMove = true; + winAnimator.mAnimatingMove = true; + } + + //TODO (multidisplay): Accessibility supported only for the default display. + if (mService.mAccessibilityController != null + && displayId == Display.DEFAULT_DISPLAY) { + mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(); + } + + try { + w.mClient.moved(left, top); + } catch (RemoteException e) { + } + } + + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); + w.mContentChanged = false; + + // Moved from updateWindowsAndWallpaperLocked(). + if (w.mHasSurface) { + // Take care of the window being ready to display. + final boolean committed = winAnimator.commitFinishDrawingLocked(); + if (isDefaultDisplay && committed) { + if (w.mAttrs.type == TYPE_DREAM) { + // HACK: When a dream is shown, it may at that + // point hide the lock screen. So we need to + // redo the layout to let the phone window manager + // make this happen. + displayContent.pendingLayoutChanges |= + FINISH_LAYOUT_REDO_LAYOUT; + if (DEBUG_LAYOUT_REPEATS) { + debugLayoutRepeats("dream and commitFinishDrawingLocked true", + displayContent.pendingLayoutChanges); + } + } + if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { + if (DEBUG_WALLPAPER_LIGHT) + Slog.v(TAG, "First draw done in potential wallpaper target " + w); + mWallpaperMayChange = true; + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + if (DEBUG_LAYOUT_REPEATS) { + debugLayoutRepeats("wallpaper and commitFinishDrawingLocked true", + displayContent.pendingLayoutChanges); + } + } + } + + winAnimator.setSurfaceBoundariesLocked(recoveringMemory); + } + + final AppWindowToken atoken = w.mAppToken; + if (DEBUG_STARTING_WINDOW && atoken != null && w == atoken.startingWindow) { + Slog.d(TAG, "updateWindows: starting " + w + + " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn + + " freezingScreen=" + atoken.mAppAnimator.freezingScreen); + } + if (atoken != null && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) { + if (atoken.lastTransactionSequence != mService.mTransactionSequence) { + atoken.lastTransactionSequence = mService.mTransactionSequence; + atoken.numInterestingWindows = atoken.numDrawnWindows = 0; + atoken.startingDisplayed = false; + } + if ((w.isOnScreenIgnoringKeyguard() + || winAnimator.mAttrType == TYPE_BASE_APPLICATION) + && !w.mExiting && !w.mDestroying) { + if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { + Slog.v(TAG, "Eval win " + w + ": isDrawn=" + + w.isDrawnLw() + + ", isAnimating=" + winAnimator.isAnimating()); + if (!w.isDrawnLw()) { + Slog.v(TAG, "Not displayed: s=" + + winAnimator.mSurfaceControl + + " pv=" + w.mPolicyVisibility + + " mDrawState=" + winAnimator.drawStateToString() + + " ah=" + w.mAttachedHidden + + " th=" + atoken.hiddenRequested + + " a=" + winAnimator.mAnimating); + } + } + if (w != atoken.startingWindow) { + if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) { + atoken.numInterestingWindows++; + if (w.isDrawnLw()) { + atoken.numDrawnWindows++; + if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) + Slog.v(TAG, "tokenMayBeDrawn: " + atoken + + " freezingScreen=" + + atoken.mAppAnimator.freezingScreen + + " mAppFreezing=" + w.mAppFreezing); + updateAllDrawn = true; + } + } + } else if (w.isDrawnLw()) { + atoken.startingDisplayed = true; + } + } + } + + if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus + && w.isDisplayedLw()) { + focusDisplayed = true; + } + + mService.updateResizingWindows(w); + } + + mService.mDisplayManagerInternal.setDisplayProperties(displayId, + mDisplayHasContent, + mPreferredRefreshRate, + mPreferredModeId, + true /* inTraversal, must call performTraversalInTrans... below */); + + mService.getDisplayContentLocked(displayId).stopDimmingIfNeeded(); + + if (updateAllDrawn) { + updateAllDrawnLocked(displayContent); + } + } + + if (focusDisplayed) { + mService.mH.sendEmptyMessage(REPORT_LOSING_FOCUS); + } + + // Give the display manager a chance to adjust properties + // like display rotation if it needs to. + mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager(); + } + boolean isInLayout() { return mInLayout; } @@ -1514,4 +1507,15 @@ class WindowSurfacePlacer { return doRequest; } + + void requestTraversal() { + if (!mTraversalScheduled) { + mTraversalScheduled = true; + mService.mH.sendEmptyMessage(DO_TRAVERSAL); + } + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mTraversalScheduled="); pw.println(mTraversalScheduled); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 020230931097..67e57034cf91 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -303,9 +303,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void onBootPhase(int phase) { - if (phase == PHASE_LOCK_SETTINGS_READY) { - mService.systemReady(); - } + mService.systemReady(phase); } } @@ -1134,7 +1132,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { updateScreenCaptureDisabledInWindowManager(userHandle, false /* default value */); } - void loadDeviceOwner() { + void loadOwners() { synchronized (this) { mOwners.load(); updateDeviceOwnerLocked(); @@ -1776,12 +1774,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void systemReady() { + public void systemReady(int phase) { if (!mHasFeature) { return; } + switch (phase) { + case SystemService.PHASE_LOCK_SETTINGS_READY: + onLockSettingsReady(); + break; + case SystemService.PHASE_BOOT_COMPLETED: + ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this. + break; + } + } + + private void onLockSettingsReady() { getUserData(UserHandle.USER_OWNER); - loadDeviceOwner(); + loadOwners(); cleanUpOldUsers(); // Register an observer for watching for user setup complete. new SetupContentObserver(mHandler).register(mContext.getContentResolver()); @@ -1798,6 +1807,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void ensureDeviceOwnerUserStarted() { + if (mOwners.hasDeviceOwner()) { + final IActivityManager am = ActivityManagerNative.getDefault(); + final int userId = mOwners.getDeviceOwnerUserId(); + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Starting non-system DO user: " + userId); + } + if (userId != UserHandle.USER_SYSTEM) { + try { + am.startUserInBackground(userId); + + // STOPSHIP Prevent the DO user from being killed. + + } catch (RemoteException e) { + Slog.w(LOG_TAG, "Exception starting user", e); + } + } + } + } + private void cleanUpOldUsers() { // This is needed in case the broadcast {@link Intent.ACTION_USER_REMOVED} was not handled // before reboot @@ -4110,17 +4139,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean setDeviceOwner(String packageName, String ownerName) { + public boolean setDeviceOwner(String packageName, String ownerName, int userId) { if (!mHasFeature) { return false; } if (packageName == null - || !Owners.isInstalled(packageName, mContext.getPackageManager())) { + || !Owners.isInstalledForUser(packageName, userId)) { throw new IllegalArgumentException("Invalid package name " + packageName + " for device owner"); } synchronized (this) { - enforceCanSetDeviceOwner(); + enforceCanSetDeviceOwner(userId); // Shutting down backup manager service permanently. long ident = Binder.clearCallingIdentity(); @@ -4134,14 +4163,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Binder.restoreCallingIdentity(ident); } - mOwners.setDeviceOwner(packageName, ownerName); + mOwners.setDeviceOwner(packageName, ownerName, userId); mOwners.writeDeviceOwner(); updateDeviceOwnerLocked(); Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED); ident = Binder.clearCallingIdentity(); try { - mContext.sendBroadcastAsUser(intent, UserHandle.OWNER); + // TODO Send to system too? + mContext.sendBroadcastAsUser(intent, new UserHandle(userId)); } finally { Binder.restoreCallingIdentity(ident); } @@ -4242,10 +4272,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return false; } - if (initializer == null || !Owners.isInstalled( - initializer.getPackageName(), mContext.getPackageManager())) { + if (initializer == null || + !mOwners.hasDeviceOwner() || + !Owners.isInstalledForUser(initializer.getPackageName(), + mOwners.getDeviceOwnerUserId())) { throw new IllegalArgumentException("Invalid component name " + initializer - + " for device initializer"); + + " for device initializer or no device owner set"); } boolean isInitializerSystemApp; try { @@ -4268,7 +4300,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.setDeviceInitializer(initializer); - addDeviceInitializerToLockTaskPackagesLocked(UserHandle.USER_OWNER); + addDeviceInitializerToLockTaskPackagesLocked(mOwners.getDeviceOwnerUserId()); mOwners.writeDeviceOwner(); return true; } @@ -4278,7 +4310,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who == null) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_DEVICE_ADMINS, null); - if (hasUserSetupCompleted(UserHandle.USER_OWNER)) { + if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) { throw new IllegalStateException( "Trying to set device initializer but device is already provisioned."); } @@ -4648,31 +4680,45 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * The device owner can only be set before the setup phase of the primary user has completed, * except for adb if no accounts or additional users are present on the device. */ - private void enforceCanSetDeviceOwner() { - if (mOwners != null && mOwners.hasDeviceOwner()) { + private void enforceCanSetDeviceOwner(int userId) { + if (mOwners.hasDeviceOwner()) { throw new IllegalStateException("Trying to set the device owner, but device owner " + "is already set."); } + if (!mUserManager.isUserRunning(new UserHandle(userId))) { + throw new IllegalStateException("User not running: " + userId); + } + int callingUid = Binder.getCallingUid(); if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { if (!hasUserSetupCompleted(UserHandle.USER_OWNER)) { return; } - if (mUserManager.getUserCount() > 1) { - throw new IllegalStateException("Not allowed to set the device owner because there " - + "are already several users on the device"); - } - if (AccountManager.get(mContext).getAccounts().length > 0) { - throw new IllegalStateException("Not allowed to set the device owner because there " - + "are already some accounts on the device"); + // STOPSHIP Do proper check in split user mode + if (!UserManager.isSplitSystemUser()) { + if (mUserManager.getUserCount() > 1) { + throw new IllegalStateException( + "Not allowed to set the device owner because there " + + "are already several users on the device"); + } + if (AccountManager.get(mContext).getAccounts().length > 0) { + throw new IllegalStateException( + "Not allowed to set the device owner because there " + + "are already some accounts on the device"); + } } return; } + // STOPSHIP check the caller UID with userId + mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null); - if (hasUserSetupCompleted(UserHandle.USER_OWNER)) { - throw new IllegalStateException("Cannot set the device owner if the device is " - + "already set-up"); + // STOPSHIP Do proper check in split user mode + if (!UserManager.isSplitSystemUser()) { + if (hasUserSetupCompleted(UserHandle.USER_OWNER)) { + throw new IllegalStateException("Cannot set the device owner if the device is " + + "already set-up"); + } } } @@ -4689,12 +4735,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private void enforceSystemProcess(String message) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException(message); - } - } - private void enforceNotManagedProfile(int userHandle, String message) { if(isManagedProfile(userHandle)) { throw new SecurityException("You can not " + message + " for a managed profile. "); @@ -6318,7 +6358,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mContext.sendBroadcastAsUser( new Intent(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED), - UserHandle.OWNER); + UserHandle.SYSTEM); } @Override @@ -6355,7 +6395,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "Only the system update service can broadcast update information"); if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) { - Slog.w(LOG_TAG, "Only the system update service in the primary user" + + Slog.w(LOG_TAG, "Only the system update service in the primary user " + "can broadcast update information."); return; } @@ -6368,6 +6408,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (deviceOwnerPackage == null) { return; } + final UserHandle deviceOwnerUser = new UserHandle(mOwners.getDeviceOwnerUserId()); ActivityInfo[] receivers = null; try { @@ -6383,7 +6424,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (permission.BIND_DEVICE_ADMIN.equals(receivers[i].permission)) { intent.setComponent(new ComponentName(deviceOwnerPackage, receivers[i].name)); - mContext.sendBroadcastAsUser(intent, UserHandle.OWNER); + mContext.sendBroadcastAsUser(intent, deviceOwnerUser); } } } finally { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 165671612a44..87cf28f861d8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.os.Environment; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; import android.util.AtomicFile; @@ -71,6 +72,8 @@ class Owners { private static final String TAG_DEVICE_OWNER = "device-owner"; private static final String TAG_DEVICE_INITIALIZER = "device-initializer"; private static final String TAG_PROFILE_OWNER = "profile-owner"; + // Holds "context" for device-owner, this must not be show up before device-owner. + private static final String TAG_DEVICE_OWNER_CONTEXT = "device-owner-context"; private static final String ATTR_NAME = "name"; private static final String ATTR_PACKAGE = "package"; @@ -85,6 +88,8 @@ class Owners { // Internal state for the device owner package. private OwnerInfo mDeviceOwner; + private int mDeviceOwnerUserId = UserHandle.USER_NULL; + // Internal state for the device initializer package. private OwnerInfo mDeviceInitializer; @@ -140,16 +145,26 @@ class Owners { return mDeviceOwner != null ? mDeviceOwner.packageName : null; } + int getDeviceOwnerUserId() { + return mDeviceOwnerUserId; + } + String getDeviceOwnerName() { return mDeviceOwner != null ? mDeviceOwner.name : null; } - void setDeviceOwner(String packageName, String ownerName) { + void setDeviceOwner(String packageName, String ownerName, int userId) { + if (userId < 0) { + Slog.e(TAG, "Invalid user id for device owner user: " + userId); + return; + } mDeviceOwner = new OwnerInfo(ownerName, packageName); + mDeviceOwnerUserId = userId; } void clearDeviceOwner() { mDeviceOwner = null; + mDeviceOwnerUserId = UserHandle.USER_NULL; } ComponentName getDeviceInitializerComponent() { @@ -215,20 +230,6 @@ class Owners { return mDeviceOwner != null; } - static boolean isInstalled(String packageName, PackageManager pm) { - try { - PackageInfo pi; - if ((pi = pm.getPackageInfo(packageName, 0)) != null) { - if ((pi.applicationInfo.flags) != 0) { - return true; - } - } - } catch (NameNotFoundException nnfe) { - Slog.w(TAG, "Device Owner package " + packageName + " not installed."); - } - return false; - } - static boolean isInstalledForUser(String packageName, int userHandle) { try { PackageInfo pi = (AppGlobals.getPackageManager()) @@ -263,6 +264,7 @@ class Owners { String name = parser.getAttributeValue(null, ATTR_NAME); String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); mDeviceOwner = new OwnerInfo(name, packageName); + mDeviceOwnerUserId = UserHandle.USER_SYSTEM; } else if (tag.equals(TAG_DEVICE_INITIALIZER)) { String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); String initializerComponentStr = @@ -464,7 +466,11 @@ class Owners { void writeInner(XmlSerializer out) throws IOException { if (mDeviceOwner != null) { mDeviceOwner.writeToXml(out, TAG_DEVICE_OWNER); + out.startTag(null, TAG_DEVICE_OWNER_CONTEXT); + out.attribute(null, ATTR_USERID, String.valueOf(mDeviceOwnerUserId)); + out.endTag(null, TAG_DEVICE_OWNER_CONTEXT); } + if (mDeviceInitializer != null) { mDeviceInitializer.writeToXml(out, TAG_DEVICE_INITIALIZER); } @@ -483,7 +489,18 @@ class Owners { switch (tag) { case TAG_DEVICE_OWNER: mDeviceOwner = OwnerInfo.readFromXml(parser); + mDeviceOwnerUserId = UserHandle.USER_SYSTEM; // Set default + break; + case TAG_DEVICE_OWNER_CONTEXT: { + final String userIdString = + parser.getAttributeValue(null, ATTR_USERID); + try { + mDeviceOwnerUserId = Integer.parseInt(userIdString); + } catch (NumberFormatException e) { + Slog.e(TAG, "Error parsing user-id " + userIdString); + } break; + } case TAG_DEVICE_INITIALIZER: mDeviceInitializer = OwnerInfo.readFromXml(parser); break; @@ -594,7 +611,6 @@ class Owners { pw.println(prefix + "admin=" + admin); pw.println(prefix + "name=" + name); pw.println(prefix + "package=" + packageName); - pw.println(); } } @@ -602,6 +618,17 @@ class Owners { if (mDeviceOwner != null) { pw.println(prefix + "Device Owner: "); mDeviceOwner.dump(prefix + " ", pw); + pw.println(prefix + " User ID: " + mDeviceOwnerUserId); + pw.println(); + } + if (mDeviceInitializer != null) { + pw.println(prefix + "Device Initializer: "); + mDeviceInitializer.dump(prefix + " ", pw); + pw.println(); + } + if (mSystemUpdatePolicy != null) { + pw.println(prefix + "System Update Policy: " + mSystemUpdatePolicy); + pw.println(); } if (mProfileOwners != null) { for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) { diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 3ecfd5509468..f6de12dbf6cb 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -27,6 +27,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.XmlResourceParser; +import android.media.midi.IBluetoothMidiService; import android.media.midi.IMidiDeviceListener; import android.media.midi.IMidiDeviceOpenCallback; import android.media.midi.IMidiDeviceServer; @@ -394,7 +395,20 @@ public class MidiService extends IMidiManager.Stub { mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - IMidiDeviceServer server = IMidiDeviceServer.Stub.asInterface(service); + IMidiDeviceServer server = null; + if (mBluetoothDevice != null) { + IBluetoothMidiService mBluetoothMidiService = IBluetoothMidiService.Stub.asInterface(service); + try { + // We need to explicitly add the device in a separate method + // because onBind() is only called once. + IBinder deviceBinder = mBluetoothMidiService.addBluetoothDevice(mBluetoothDevice); + server = IMidiDeviceServer.Stub.asInterface(deviceBinder); + } catch(RemoteException e) { + Log.e(TAG, "Could not call addBluetoothDevice()", e); + } + } else { + server = IMidiDeviceServer.Stub.asInterface(service); + } setDeviceServer(server); } @@ -411,7 +425,6 @@ public class MidiService extends IMidiManager.Stub { intent.setComponent(new ComponentName( MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, MidiManager.BLUETOOTH_MIDI_SERVICE_CLASS)); - intent.putExtra("device", mBluetoothDevice); } else { intent = new Intent(MidiDeviceService.SERVICE_INTERFACE); intent.setComponent( diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 386a9cbb9dc6..7d1282bceac9 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -39,6 +39,7 @@ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index b4c76b7faaae..97e16da42732 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -20,35 +20,9 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; -import static android.net.NetworkCapabilities.NET_CAPABILITY_IA; -import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; -import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; +import static android.net.NetworkCapabilities.*; + import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -58,8 +32,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; +import android.net.ConnectivityManager.PacketKeepalive; +import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.IpPrefix; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; @@ -74,8 +52,11 @@ import android.net.RouteInfo; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; -import android.os.Looper; import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.os.MessageQueue.IdleHandler; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; @@ -84,10 +65,10 @@ import android.util.LogPrinter; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; -import org.mockito.ArgumentCaptor; - import java.net.InetAddress; -import java.util.concurrent.Future; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -99,24 +80,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ConnectivityServiceTest extends AndroidTestCase { private static final String TAG = "ConnectivityServiceTest"; - private static final String MOBILE_IFACE = "rmnet3"; - private static final String WIFI_IFACE = "wlan6"; - - private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33"), - MOBILE_IFACE); - private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"), - MOBILE_IFACE); - - private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(parse("192.168.0.66"), - parse("192.168.0.1"), - WIFI_IFACE); - private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::66"), - parse("fd00::"), - WIFI_IFACE); - - private INetworkManagementService mNetManager; - private INetworkStatsService mStatsService; - private INetworkPolicyManager mPolicyService; + private static final int TIMEOUT_MS = 500; private BroadcastInterceptingContext mServiceContext; private WrappedConnectivityService mService; @@ -148,14 +112,93 @@ public class ConnectivityServiceTest extends AndroidTestCase { } } + /** + * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle + * will return immediately if the handler is already idle. + */ + private class IdleableHandlerThread extends HandlerThread { + private IdleHandler mIdleHandler; + + public IdleableHandlerThread(String name) { + super(name); + } + + public void waitForIdle(int timeoutMs) { + final ConditionVariable cv = new ConditionVariable(); + final MessageQueue queue = getLooper().getQueue(); + + synchronized (queue) { + if (queue.isIdle()) { + return; + } + + assertNull("BUG: only one idle handler allowed", mIdleHandler); + mIdleHandler = new IdleHandler() { + public boolean queueIdle() { + cv.open(); + mIdleHandler = null; + return false; // Remove the handler. + } + }; + queue.addIdleHandler(mIdleHandler); + } + + if (!cv.block(timeoutMs)) { + fail("HandlerThread " + getName() + + " did not become idle after " + timeoutMs + " ms"); + queue.removeIdleHandler(mIdleHandler); + } + } + } + + // Tests that IdleableHandlerThread works as expected. + public void testIdleableHandlerThread() { + final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. + + // Tests that waitForIdle returns immediately if the service is already idle. + for (int i = 0; i < attempts; i++) { + mService.waitForIdle(); + } + + // Bring up a network that we can use to send messages to ConnectivityService. + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + waitFor(cv); + Network n = mWiFiNetworkAgent.getNetwork(); + assertNotNull(n); + + // Tests that calling waitForIdle waits for messages to be processed. + for (int i = 0; i < attempts; i++) { + mWiFiNetworkAgent.setSignalStrength(i); + mService.waitForIdle(); + assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength()); + } + + // Ensure that not calling waitForIdle causes a race condition. + for (int i = 0; i < attempts; i++) { + mWiFiNetworkAgent.setSignalStrength(i); + if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) { + // We hit a race condition, as expected. Pass the test. + return; + } + } + + // No race? There is a bug in this test. + fail("expected race condition at least once in " + attempts + " attempts"); + } + private class MockNetworkAgent { private final WrappedNetworkMonitor mWrappedNetworkMonitor; private final NetworkInfo mNetworkInfo; private final NetworkCapabilities mNetworkCapabilities; - private final Thread mThread; + private final IdleableHandlerThread mHandlerThread; private final ConditionVariable mDisconnected = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; + private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; + private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE; + private Integer mExpectedKeepaliveSlot = null; MockNetworkAgent(int transport) { final int type = transportToLegacyType(transport); @@ -173,26 +216,42 @@ public class ConnectivityServiceTest extends AndroidTestCase { default: throw new UnsupportedOperationException("unimplemented network type"); } - final ConditionVariable initComplete = new ConditionVariable(); - final ConditionVariable networkMonitorAvailable = mService.getNetworkMonitorCreatedCV(); - mThread = new Thread() { - public void run() { - Looper.prepare(); - mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext, - "Mock" + typeName, mNetworkInfo, mNetworkCapabilities, - new LinkProperties(), mScore, new NetworkMisc()) { - public void unwanted() { mDisconnected.open(); } - }; - initComplete.open(); - Looper.loop(); + mHandlerThread = new IdleableHandlerThread("Mock-" + typeName); + mHandlerThread.start(); + mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext, + "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities, + new LinkProperties(), mScore, new NetworkMisc()) { + @Override + public void unwanted() { mDisconnected.open(); } + + @Override + public void startPacketKeepalive(Message msg) { + int slot = msg.arg1; + if (mExpectedKeepaliveSlot != null) { + assertEquals((int) mExpectedKeepaliveSlot, slot); + } + onPacketKeepaliveEvent(slot, mStartKeepaliveError); + } + + @Override + public void stopPacketKeepalive(Message msg) { + onPacketKeepaliveEvent(msg.arg1, mStopKeepaliveError); } }; - mThread.start(); - waitFor(initComplete); - waitFor(networkMonitorAvailable); + // Waits for the NetworkAgent to be registered, which includes the creation of the + // NetworkMonitor. + mService.waitForIdle(); mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor(); } + public void waitForIdle(int timeoutMs) { + mHandlerThread.waitForIdle(timeoutMs); + } + + public void waitForIdle() { + waitForIdle(TIMEOUT_MS); + } + public void adjustScore(int change) { mScore += change; mNetworkAgent.sendNetworkScore(mScore); @@ -203,6 +262,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } + public void setSignalStrength(int signalStrength) { + mNetworkCapabilities.setSignalStrength(signalStrength); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + public void connectWithoutInternet() { mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); @@ -272,15 +336,46 @@ public class ConnectivityServiceTest extends AndroidTestCase { public WrappedNetworkMonitor getWrappedNetworkMonitor() { return mWrappedNetworkMonitor; } + + public void sendLinkProperties(LinkProperties lp) { + mNetworkAgent.sendLinkProperties(lp); + } + + public void setStartKeepaliveError(int error) { + mStartKeepaliveError = error; + } + + public void setStopKeepaliveError(int error) { + mStopKeepaliveError = error; + } + + public void setExpectedKeepaliveSlot(Integer slot) { + mExpectedKeepaliveSlot = slot; + } } + /** + * A NetworkFactory that allows tests to wait until any in-flight NetworkRequest add or remove + * operations have been processed. Before ConnectivityService can add or remove any requests, + * the factory must be told to expect those operations by calling expectAddRequests or + * expectRemoveRequests. + */ private static class MockNetworkFactory extends NetworkFactory { private final ConditionVariable mNetworkStartedCV = new ConditionVariable(); private final ConditionVariable mNetworkStoppedCV = new ConditionVariable(); - private final ConditionVariable mNetworkRequestedCV = new ConditionVariable(); - private final ConditionVariable mNetworkReleasedCV = new ConditionVariable(); private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false); + // Used to expect that requests be removed or added on a separate thread, without sleeping. + // Callers can call either expectAddRequests() or expectRemoveRequests() exactly once, then + // cause some other thread to add or remove requests, then call waitForRequests(). We can + // either expect requests to be added or removed, but not both, because CountDownLatch can + // only count in one direction. + private CountDownLatch mExpectations; + + // Whether we are currently expecting requests to be added or removed. Valid only if + // mExpectations is non-null. + private boolean mExpectingAdditions; + public MockNetworkFactory(Looper looper, Context context, String logTag, NetworkCapabilities filter) { super(looper, context, logTag, filter); @@ -314,28 +409,75 @@ public class ConnectivityServiceTest extends AndroidTestCase { return mNetworkStoppedCV; } - protected void needNetworkFor(NetworkRequest networkRequest, int score) { - super.needNetworkFor(networkRequest, score); - mNetworkRequestedCV.open(); + @Override + protected void handleAddRequest(NetworkRequest request, int score) { + // If we're expecting anything, we must be expecting additions. + if (mExpectations != null && !mExpectingAdditions) { + fail("Can't add requests while expecting requests to be removed"); + } + + // Add the request. + super.handleAddRequest(request, score); + + // Reduce the number of request additions we're waiting for. + if (mExpectingAdditions) { + assertTrue("Added more requests than expected", mExpectations.getCount() > 0); + mExpectations.countDown(); + } + } + + @Override + protected void handleRemoveRequest(NetworkRequest request) { + // If we're expecting anything, we must be expecting removals. + if (mExpectations != null && mExpectingAdditions) { + fail("Can't remove requests while expecting requests to be added"); + } + + // Remove the request. + super.handleRemoveRequest(request); + + // Reduce the number of request removals we're waiting for. + if (!mExpectingAdditions) { + assertTrue("Removed more requests than expected", mExpectations.getCount() > 0); + mExpectations.countDown(); + } } - protected void releaseNetworkFor(NetworkRequest networkRequest) { - super.releaseNetworkFor(networkRequest); - mNetworkReleasedCV.open(); + private void assertNoExpectations() { + if (mExpectations != null) { + fail("Can't add expectation, " + mExpectations.getCount() + " already pending"); + } + } + + // Expects that count requests will be added. + public void expectAddRequests(final int count) { + assertNoExpectations(); + mExpectingAdditions = true; + mExpectations = new CountDownLatch(count); } - public ConditionVariable getNetworkRequestedCV() { - mNetworkRequestedCV.close(); - return mNetworkRequestedCV; + // Expects that count requests will be removed. + public void expectRemoveRequests(final int count) { + assertNoExpectations(); + mExpectingAdditions = false; + mExpectations = new CountDownLatch(count); } - public ConditionVariable getNetworkReleasedCV() { - mNetworkReleasedCV.close(); - return mNetworkReleasedCV; + // Waits for the expected request additions or removals to happen within a timeout. + public void waitForRequests() throws InterruptedException { + assertNotNull("Nothing to wait for", mExpectations); + mExpectations.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + final long count = mExpectations.getCount(); + final String msg = count + " requests still not " + + (mExpectingAdditions ? "added" : "removed") + + " after " + TIMEOUT_MS + " ms"; + assertEquals(msg, 0, count); + mExpectations = null; } - public void waitForNetworkRequests(final int count) { - waitFor(new Criteria() { public boolean get() { return count == getRequestCount(); } }); + public void waitForNetworkRequests(final int count) throws InterruptedException { + waitForRequests(); + assertEquals(count, getMyRequestCount()); } } @@ -356,7 +498,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { } private class WrappedConnectivityService extends ConnectivityService { - private final ConditionVariable mNetworkMonitorCreated = new ConditionVariable(); private WrappedNetworkMonitor mLastCreatedNetworkMonitor; public WrappedConnectivityService(Context context, INetworkManagementService netManager, @@ -365,6 +506,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { } @Override + protected HandlerThread createHandlerThread() { + return new IdleableHandlerThread("WrappedConnectivityService"); + } + + @Override protected int getDefaultTcpRwnd() { // Prevent wrapped ConnectivityService from trying to write to SystemProperties. return 0; @@ -397,7 +543,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(context, handler, nai, defaultRequest); mLastCreatedNetworkMonitor = monitor; - mNetworkMonitorCreated.open(); return monitor; } @@ -405,10 +550,14 @@ public class ConnectivityServiceTest extends AndroidTestCase { return mLastCreatedNetworkMonitor; } - public ConditionVariable getNetworkMonitorCreatedCV() { - mNetworkMonitorCreated.close(); - return mNetworkMonitorCreated; + public void waitForIdle(int timeoutMs) { + ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs); } + + public void waitForIdle() { + waitForIdle(TIMEOUT_MS); + } + } private interface Criteria { @@ -431,23 +580,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { } /** - * Wait up to 500ms for {@code conditonVariable} to open. - * Fails if 500ms goes by before {@code conditionVariable} opens. + * Wait up to TIMEOUT_MS for {@code conditionVariable} to open. + * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens. */ static private void waitFor(ConditionVariable conditionVariable) { - assertTrue(conditionVariable.block(500)); - } - - /** - * This should only be used to verify that nothing happens, in other words that no unexpected - * changes occur. It should never be used to wait for a specific positive signal to occur. - */ - private void shortSleep() { - // TODO: Instead of sleeping, instead wait for all message loops to idle. - try { - Thread.sleep(500); - } catch (InterruptedException e) { - } + assertTrue(conditionVariable.block(TIMEOUT_MS)); } @Override @@ -455,13 +592,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { super.setUp(); mServiceContext = new MockContext(getContext()); + mService = new WrappedConnectivityService(mServiceContext, + mock(INetworkManagementService.class), + mock(INetworkStatsService.class), + mock(INetworkPolicyManager.class)); - mNetManager = mock(INetworkManagementService.class); - mStatsService = mock(INetworkStatsService.class); - mPolicyService = mock(INetworkPolicyManager.class); - - mService = new WrappedConnectivityService( - mServiceContext, mNetManager, mStatsService, mPolicyService); mService.systemReady(); mCm = new ConnectivityManager(getContext(), mService); } @@ -583,11 +718,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { // Test bringing up unvalidated cellular mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - shortSleep(); + mService.waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test cellular disconnect. mCellNetworkAgent.disconnect(); - shortSleep(); + mService.waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); @@ -797,6 +932,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { LOST } + /** + * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks + * this class receives, by calling expectCallback() exactly once each time a callback is + * received. assertNoCallback may be called at any time. + */ private class TestNetworkCallback extends NetworkCallback { private final ConditionVariable mConditionVariable = new ConditionVariable(); private CallbackState mLastCallback = CallbackState.NONE; @@ -819,14 +959,15 @@ public class ConnectivityServiceTest extends AndroidTestCase { mConditionVariable.open(); } - ConditionVariable getConditionVariable() { + void expectCallback(CallbackState state) { + waitFor(mConditionVariable); + assertEquals(state, mLastCallback); mLastCallback = CallbackState.NONE; mConditionVariable.close(); - return mConditionVariable; } - CallbackState getLastCallback() { - return mLastCallback; + void assertNoCallback() { + assertEquals(CallbackState.NONE, mLastCallback); } } @@ -842,98 +983,68 @@ public class ConnectivityServiceTest extends AndroidTestCase { mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); // Test unvalidated networks - ConditionVariable cellCv = cellNetworkCallback.getConditionVariable(); - ConditionVariable wifiCv = wifiNetworkCallback.getConditionVariable(); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - waitFor(cellCv); - assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); + wifiNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); // This should not trigger spurious onAvailable() callbacks, b/21762680. mCellNetworkAgent.adjustScore(-1); - shortSleep(); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + mService.waitForIdle(); + wifiNetworkCallback.assertNoCallback(); + cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - waitFor(wifiCv); - assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE); + cellNetworkCallback.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.disconnect(); - waitFor(wifiCv); - assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.LOST); + cellNetworkCallback.assertNoCallback(); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.disconnect(); - waitFor(cellCv); - assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.LOST); + wifiNetworkCallback.assertNoCallback(); waitFor(cv); // Test validated networks - - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - waitFor(cellCv); - assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); + wifiNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); // This should not trigger spurious onAvailable() callbacks, b/21762680. mCellNetworkAgent.adjustScore(-1); - shortSleep(); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + mService.waitForIdle(); + wifiNetworkCallback.assertNoCallback(); + cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - waitFor(wifiCv); - assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback()); - waitFor(cellCv); - assertEquals(CallbackState.LOSING, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE); + cellNetworkCallback.expectCallback(CallbackState.LOSING); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mWiFiNetworkAgent.disconnect(); - waitFor(wifiCv); - assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.LOST); + cellNetworkCallback.assertNoCallback(); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mCellNetworkAgent.disconnect(); - waitFor(cellCv); - assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.LOST); + wifiNetworkCallback.assertNoCallback(); } private void tryNetworkFactoryRequests(int capability) throws Exception { @@ -957,18 +1068,21 @@ public class ConnectivityServiceTest extends AndroidTestCase { mServiceContext, "testFactory", filter); testFactory.setScoreFilter(40); ConditionVariable cv = testFactory.getNetworkStartedCV(); + testFactory.expectAddRequests(1); testFactory.register(); + testFactory.waitForNetworkRequests(1); int expectedRequestCount = 1; NetworkCallback networkCallback = null; // For non-INTERNET capabilities we cannot rely on the default request being present, so // add one. if (capability != NET_CAPABILITY_INTERNET) { - testFactory.waitForNetworkRequests(1); assertFalse(testFactory.getMyStartRequested()); NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build(); networkCallback = new NetworkCallback(); + testFactory.expectAddRequests(1); mCm.requestNetwork(request, networkCallback); expectedRequestCount++; + testFactory.waitForNetworkRequests(expectedRequestCount); } waitFor(cv); assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); @@ -981,13 +1095,20 @@ public class ConnectivityServiceTest extends AndroidTestCase { // unvalidated penalty. testAgent.adjustScore(40); cv = testFactory.getNetworkStoppedCV(); + + // When testAgent connects, ConnectivityService will re-send us all current requests with + // the new score. There are expectedRequestCount such requests, and we must wait for all of + // them. + testFactory.expectAddRequests(expectedRequestCount); testAgent.connect(false); testAgent.addCapability(capability); waitFor(cv); - assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); + testFactory.waitForNetworkRequests(expectedRequestCount); assertFalse(testFactory.getMyStartRequested()); // Bring in a bunch of requests. + testFactory.expectAddRequests(10); + assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); ConnectivityManager.NetworkCallback[] networkCallbacks = new ConnectivityManager.NetworkCallback[10]; for (int i = 0; i< networkCallbacks.length; i++) { @@ -1000,6 +1121,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { assertFalse(testFactory.getMyStartRequested()); // Remove the requests. + testFactory.expectRemoveRequests(10); for (int i = 0; i < networkCallbacks.length; i++) { mCm.unregisterNetworkCallback(networkCallbacks[i]); } @@ -1088,10 +1210,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { // Test bringing up unvalidated cellular with MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); - cv = networkCallback.getConditionVariable(); mCellNetworkAgent.connectWithoutInternet(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); + networkCallback.expectCallback(CallbackState.AVAILABLE); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS cv = mCellNetworkAgent.getDisconnectedCV(); @@ -1114,12 +1234,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up MMS cellular network - cv = networkCallback.getConditionVariable(); MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); + networkCallback.expectCallback(CallbackState.AVAILABLE); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent cv = mmsNetworkAgent.getDisconnectedCV(); @@ -1139,133 +1257,245 @@ public class ConnectivityServiceTest extends AndroidTestCase { final NetworkRequest validatedRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_VALIDATED).build(); mCm.registerNetworkCallback(validatedRequest, validatedCallback); - ConditionVariable validatedCv = validatedCallback.getConditionVariable(); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - ConditionVariable cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithCaptivePortal(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.AVAILABLE); // Take down network. // Expect onLost callback. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent.disconnect(); - waitFor(cv); - assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.LOST); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithCaptivePortal(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.AVAILABLE); // Make captive portal disappear then revalidate. // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); - waitFor(cv); - assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.LOST); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - waitFor(validatedCv); - assertEquals(CallbackState.AVAILABLE, validatedCallback.getLastCallback()); + validatedCallback.expectCallback(CallbackState.AVAILABLE); // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. - validatedCv = validatedCallback.getConditionVariable(); mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500; mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); - waitFor(validatedCv); - assertEquals(CallbackState.LOST, validatedCallback.getLastCallback()); + validatedCallback.expectCallback(CallbackState.LOST); + } + + private static class TestKeepaliveCallback extends PacketKeepaliveCallback { + + public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; + + private class CallbackValue { + public CallbackType callbackType; + public int error; + + public CallbackValue(CallbackType type) { + this.callbackType = type; + this.error = PacketKeepalive.SUCCESS; + assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); + } + + public CallbackValue(CallbackType type, int error) { + this.callbackType = type; + this.error = error; + assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); + } + + @Override + public boolean equals(Object o) { + return o instanceof CallbackValue && + this.callbackType == ((CallbackValue) o).callbackType && + this.error == ((CallbackValue) o).error; + } + + @Override + public String toString() { + return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error); + } + } + + private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>(); + + @Override + public void onStarted() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); + } + + @Override + public void onStopped() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); + } + + @Override + public void onError(int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + private void expectCallback(CallbackValue callbackValue) { + try { + assertEquals( + callbackValue, + mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); + } + } + + public void expectStarted() { + expectCallback(new CallbackValue(CallbackType.ON_STARTED)); + } + + public void expectStopped() { + expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); + } + + public void expectError(int error) { + expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); + } } -// @Override -// public void tearDown() throws Exception { -// super.tearDown(); -// } -// -// public void testMobileConnectedAddedRoutes() throws Exception { -// Future<?> nextConnBroadcast; -// -// // bring up mobile network -// mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mMobile.link.setInterfaceName(MOBILE_IFACE); -// mMobile.link.addRoute(MOBILE_ROUTE_V4); -// mMobile.link.addRoute(MOBILE_ROUTE_V6); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// // verify that both routes were added -// int mobileNetId = mMobile.tracker.getNetwork().netId; -// verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4)); -// verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6)); -// } -// -// public void testMobileWifiHandoff() throws Exception { -// Future<?> nextConnBroadcast; -// -// // bring up mobile network -// mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mMobile.link.setInterfaceName(MOBILE_IFACE); -// mMobile.link.addRoute(MOBILE_ROUTE_V4); -// mMobile.link.addRoute(MOBILE_ROUTE_V6); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// reset(mNetManager); -// -// // now bring up wifi network -// mWifi.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mWifi.link.setInterfaceName(WIFI_IFACE); -// mWifi.link.addRoute(WIFI_ROUTE_V4); -// mWifi.link.addRoute(WIFI_ROUTE_V6); -// mWifi.doReturnDefaults(); -// -// // expect that mobile will be torn down -// doReturn(true).when(mMobile.tracker).teardown(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget(); -// waitFor(cv); -// -// // verify that wifi routes added, and teardown requested -// int wifiNetId = mWifi.tracker.getNetwork().netId; -// verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V4)); -// verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V6)); -// verify(mMobile.tracker).teardown(); -// -// int mobileNetId = mMobile.tracker.getNetwork().netId; -// -// reset(mNetManager, mMobile.tracker); -// -// // tear down mobile network, as requested -// mMobile.info.setDetailedState(DetailedState.DISCONNECTED, null, null); -// mMobile.link.clear(); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4)); -// verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6)); -// -// } - - private static InetAddress parse(String addr) { - return InetAddress.parseNumericAddress(addr); + private Network connectKeepaliveNetwork(LinkProperties lp) { + // Ensure the network is disconnected before we do anything. + if (mWiFiNetworkAgent != null) { + assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); + } + + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent.connect(true); + waitFor(cv); + verifyActiveNetwork(TRANSPORT_WIFI); + mWiFiNetworkAgent.sendLinkProperties(lp); + mService.waitForIdle(); + return mWiFiNetworkAgent.getNetwork(); } + public void testPacketKeepalives() throws Exception { + InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); + InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); + InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + + Network notMyNet = new Network(61234); + Network myNet = connectKeepaliveNetwork(lp); + + TestKeepaliveCallback callback = new TestKeepaliveCallback(); + PacketKeepalive ka; + + // Attempt to start keepalives with invalid parameters and check for errors. + ka = mCm.startNattKeepalive(notMyNet, 25, callback, myIPv4, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); + + ka = mCm.startNattKeepalive(myNet, 19, callback, notMyIPv4, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_INTERVAL); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 1234, dstIPv6); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv6); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); // NAT-T is IPv4-only. + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + // Check that a started keepalive can be stopped. + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + mWiFiNetworkAgent.setStopKeepaliveError(PacketKeepalive.SUCCESS); + ka.stop(); + callback.expectStopped(); + + // Check that deleting the IP address stops the keepalive. + LinkProperties bogusLp = new LinkProperties(lp); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); + bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); + mWiFiNetworkAgent.sendLinkProperties(bogusLp); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + mWiFiNetworkAgent.sendLinkProperties(lp); + + // Check that a started keepalive is stopped correctly when the network disconnects. + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + mWiFiNetworkAgent.disconnect(); + callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); + + // ... and that stopping it after that has no adverse effects. + assertNull(mCm.getNetworkCapabilities(myNet)); + ka.stop(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + + // Check things work as expected when the keepalive is stopped and the network disconnects. + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + ka.stop(); + mWiFiNetworkAgent.disconnect(); + mService.waitForIdle(); + callback.expectStopped(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + + // Check that keepalive slots start from 1 and increment. The first one gets slot 1. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + + // The second one gets slot 2. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); + TestKeepaliveCallback callback2 = new TestKeepaliveCallback(); + PacketKeepalive ka2 = mCm.startNattKeepalive(myNet, 25, callback2, myIPv4, 6789, dstIPv4); + callback2.expectStarted(); + + // Now stop the first one and create a third. This also gets slot 1. + ka.stop(); + callback.expectStopped(); + + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + TestKeepaliveCallback callback3 = new TestKeepaliveCallback(); + PacketKeepalive ka3 = mCm.startNattKeepalive(myNet, 25, callback3, myIPv4, 9876, dstIPv4); + callback3.expectStarted(); + + ka2.stop(); + callback2.expectStopped(); + + ka3.stop(); + callback3.expectStopped(); + } } diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 0f20dde23c0c..f9aa124e2bc4 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -82,7 +82,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { mAms.addAccountExplicitly(a31, "p31", null); mAms.addAccountExplicitly(a32, "p32", null); - Account[] accounts = mAms.getAccounts(null); + Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(6, accounts.length); assertEquals(a11, accounts[0]); @@ -92,7 +92,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { assertEquals(a22, accounts[4]); assertEquals(a32, accounts[5]); - accounts = mAms.getAccounts("type1" ); + accounts = mAms.getAccounts("type1", mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(3, accounts.length); assertEquals(a11, accounts[0]); @@ -101,7 +101,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { mAms.removeAccountInternal(a21); - accounts = mAms.getAccounts("type1" ); + accounts = mAms.getAccounts("type1", mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(2, accounts.length); assertEquals(a11, accounts[0]); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java index a284ca038e72..3b88fb165775 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.UserInfo; import android.os.FileUtils; +import android.os.UserHandle; import android.test.AndroidTestCase; import android.util.Log; @@ -37,11 +38,13 @@ import static org.mockito.Mockito.when; /** * Tests for the DeviceOwner object that saves & loads device and policy owner information. * run this test with: - mmma frameworks/base/services/tests/servicestests/ && + m FrameworksServicesTests && adb install \ -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && adb shell am instrument -e class com.android.server.devicepolicy.OwnersTest \ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + + (mmma frameworks/base/services/tests/servicestests/ for non-ninja build) */ public class OwnersTest extends DpmTestBase { private static final String TAG = "DeviceOwnerTest"; @@ -147,6 +150,12 @@ public class OwnersTest extends DpmTestBase { assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + + assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); + assertFalse(owners.hasDeviceInitializer()); + assertNull(owners.getSystemUpdatePolicy()); + assertEquals(0, owners.getProfileOwnerKeys().size()); } // Then re-read and check. @@ -155,6 +164,7 @@ public class OwnersTest extends DpmTestBase { owners.load(); assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertFalse(owners.hasDeviceInitializer()); assertNull(owners.getSystemUpdatePolicy()); assertEquals(0, owners.getProfileOwnerKeys().size()); @@ -181,6 +191,15 @@ public class OwnersTest extends DpmTestBase { assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + + assertTrue(owners.hasDeviceOwner()); + assertEquals(null, owners.getDeviceOwnerName()); + assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName()); + assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId()); + + assertFalse(owners.hasDeviceInitializer()); + assertNull(owners.getSystemUpdatePolicy()); + assertEquals(0, owners.getProfileOwnerKeys().size()); } // Then re-read and check. @@ -191,6 +210,7 @@ public class OwnersTest extends DpmTestBase { assertTrue(owners.hasDeviceOwner()); assertEquals(null, owners.getDeviceOwnerName()); assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName()); + assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId()); assertFalse(owners.hasDeviceInitializer()); assertNull(owners.getSystemUpdatePolicy()); @@ -218,6 +238,23 @@ public class OwnersTest extends DpmTestBase { assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + + assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); + assertFalse(owners.hasDeviceInitializer()); + assertNull(owners.getSystemUpdatePolicy()); + + assertEquals(2, owners.getProfileOwnerKeys().size()); + assertEquals(new ComponentName("com.google.android.testdpc", + "com.google.android.testdpc.DeviceAdminReceiver0"), + owners.getProfileOwnerComponent(10)); + assertEquals("0", owners.getProfileOwnerName(10)); + assertEquals("com.google.android.testdpc", owners.getProfileOwnerPackage(10)); + + assertEquals(new ComponentName("com.google.android.testdpc1", ""), + owners.getProfileOwnerComponent(11)); + assertEquals("1", owners.getProfileOwnerName(11)); + assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11)); } // Then re-read and check. @@ -226,6 +263,7 @@ public class OwnersTest extends DpmTestBase { owners.load(); assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertFalse(owners.hasDeviceInitializer()); assertNull(owners.getSystemUpdatePolicy()); @@ -263,6 +301,28 @@ public class OwnersTest extends DpmTestBase { assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + + assertTrue(owners.hasDeviceOwner()); + assertEquals(null, owners.getDeviceOwnerName()); + assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName()); + assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId()); + + assertTrue(owners.hasDeviceInitializer()); + assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName()); + assertNotNull(owners.getSystemUpdatePolicy()); + assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType()); + + assertEquals(2, owners.getProfileOwnerKeys().size()); + assertEquals(new ComponentName("com.google.android.testdpc", + "com.google.android.testdpc.DeviceAdminReceiver0"), + owners.getProfileOwnerComponent(10)); + assertEquals("0", owners.getProfileOwnerName(10)); + assertEquals("com.google.android.testdpc", owners.getProfileOwnerPackage(10)); + + assertEquals(new ComponentName("com.google.android.testdpc1", ""), + owners.getProfileOwnerComponent(11)); + assertEquals("1", owners.getProfileOwnerName(11)); + assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11)); } // Then re-read and check. @@ -273,6 +333,7 @@ public class OwnersTest extends DpmTestBase { assertTrue(owners.hasDeviceOwner()); assertEquals(null, owners.getDeviceOwnerName()); assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName()); + assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId()); assertTrue(owners.hasDeviceInitializer()); assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName()); @@ -312,6 +373,15 @@ public class OwnersTest extends DpmTestBase { assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); + + assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); + + assertTrue(owners.hasDeviceInitializer()); + assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName()); + + assertNull(owners.getSystemUpdatePolicy()); + assertEquals(0, owners.getProfileOwnerKeys().size()); } // Then re-read and check. @@ -320,6 +390,7 @@ public class OwnersTest extends DpmTestBase { owners.load(); assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertTrue(owners.hasDeviceInitializer()); assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName()); @@ -348,6 +419,14 @@ public class OwnersTest extends DpmTestBase { assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); + + assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); + assertFalse(owners.hasDeviceInitializer()); + assertEquals(0, owners.getProfileOwnerKeys().size()); + + assertNotNull(owners.getSystemUpdatePolicy()); + assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType()); } // Then re-read and check. @@ -356,6 +435,7 @@ public class OwnersTest extends DpmTestBase { owners.load(); assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertFalse(owners.hasDeviceInitializer()); assertEquals(0, owners.getProfileOwnerKeys().size()); diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index b021e808d5dd..0c3f9da73663 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -235,7 +235,8 @@ public class UsbDeviceManager { final StorageManager storageManager = StorageManager.from(mContext); final StorageVolume primary = storageManager.getPrimaryVolume(); massStorageSupported = primary != null && primary.allowMassStorage(); - mUseUsbNotification = !massStorageSupported; + mUseUsbNotification = !massStorageSupported && mContext.getResources().getBoolean( + com.android.internal.R.bool.config_usbChargingMessage); // make sure the ADB_ENABLED setting value matches the current state try { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index aa750eff807a..0e858de32229 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -274,6 +274,27 @@ public class CarrierConfigManager { = "carrier_use_ims_first_for_emergency_bool"; /** + * When IMS instant lettering is available for a carrier (see + * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines the list of characters + * which may not be contained in messages. Should be specified as a regular expression suitable + * for use with {@link String#matches(String)}. + * @hide + */ + public static final String KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING = + "carrier_instant_lettering_invalid_chars_string"; + + /** + * When IMS instant lettering is available for a carrier (see + * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines a list of characters which + * must be escaped with a backslash '\' character. Should be specified as a string containing + * the characters to be escaped. For example to escape quote and backslash the string would be + * a quote and a backslash. + * @hide + */ + public static final String KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING = + "carrier_instant_lettering_escaped_chars_string"; + + /** * If Voice Radio Technology is RIL_RADIO_TECHNOLOGY_LTE:14 or RIL_RADIO_TECHNOLOGY_UNKNOWN:0 * this is the value that should be used instead. A configuration value of * RIL_RADIO_TECHNOLOGY_UNKNOWN:0 means there is no replacement value and that the default @@ -354,6 +375,14 @@ public class CarrierConfigManager { * sends out successive DTMF tones on the network. * @hide */ + public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int"; + + /** + * Specifies the amount of gap to be added in millis between DTMF tones. When a non-zero value + * is specified, the UE shall wait for the specified amount of time before it sends out + * successive DTMF tones on the network. + * @hide + */ public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int"; /** @@ -378,6 +407,18 @@ public class CarrierConfigManager { public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool"; /** + * Determine whether IMS apn can be shown. + * @hide + */ + public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool"; + + /** + * Determine whether preferred network type can be shown. + * @hide + */ + public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool"; + + /** * If this is true, the SIM card (through Customer Service Profile EF file) will be able to * prevent manual operator selection. If false, this SIM setting will be ignored and manual * operator selection will always be available. See CPHS4_2.WW6, CPHS B.4.7.1 for more @@ -448,6 +489,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true); + sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING, ""); + sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING, ""); sDefaults.putBoolean(KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL, false); sDefaults.putBoolean(KEY_DTMF_TYPE_ENABLED_BOOL, false); sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true); @@ -485,10 +528,13 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null); sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null); sDefaults.putBoolean(KEY_FORCE_HOME_NETWORK_BOOL, false); + sDefaults.putInt(KEY_GSM_DTMF_TONE_DELAY_INT, 0); sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0); sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100); sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true); + sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false); + sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false); // MMS defaults sDefaults.putBoolean(KEY_MMS_ALIAS_ENABLED_BOOL, false); diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index cbe7c5dacc1e..c29bb482c159 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -66,6 +66,7 @@ public: mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL), mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL), mBuildSharedLibrary(false), + mBuildAppAsSharedLibrary(false), mArgc(0), mArgv(NULL) {} ~Bundle(void) {} @@ -206,6 +207,8 @@ public: void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; } bool getBuildSharedLibrary() const { return mBuildSharedLibrary; } void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; } + bool getBuildAppAsSharedLibrary() const { return mBuildAppAsSharedLibrary; } + void setBuildAppAsSharedLibrary(bool val) { mBuildAppAsSharedLibrary = val; } void setNoVersionVectors(bool val) { mNoVersionVectors = val; } bool getNoVersionVectors() const { return mNoVersionVectors; } @@ -327,6 +330,7 @@ private: const char* mSingleCrunchInputFile; const char* mSingleCrunchOutputFile; bool mBuildSharedLibrary; + bool mBuildAppAsSharedLibrary; android::String8 mPlatformVersionCode; android::String8 mPlatformVersionName; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index d12ab3b725c8..21f47bc28ff3 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -2395,11 +2395,11 @@ int doPackage(Bundle* bundle) // Write the R.java file into the appropriate class directory // e.g. gen/com/foo/app/R.java err = writeResourceSymbols(bundle, assets, assets->getPackage(), true, - bundle->getBuildSharedLibrary()); + bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary()); } else { const String8 customPkg(bundle->getCustomPackage()); err = writeResourceSymbols(bundle, assets, customPkg, true, - bundle->getBuildSharedLibrary()); + bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary()); } if (err < 0) { goto bail; @@ -2414,7 +2414,7 @@ int doPackage(Bundle* bundle) while (packageString != NULL) { // Write the R.java file out with the correct package name err = writeResourceSymbols(bundle, assets, String8(packageString), true, - bundle->getBuildSharedLibrary()); + bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary()); if (err < 0) { goto bail; } diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index bcf0d5e53c07..64112867a4b4 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -200,6 +200,9 @@ void usage(void) " --shared-lib\n" " Make a shared library resource package that can be loaded by an application\n" " at runtime to access the libraries resources. Implies --non-constant-id.\n" + " --app-as-shared-lib\n" + " Make an app resource package that also can be loaded as shared library at runtime.\n" + " Implies --non-constant-id.\n" " --error-on-failed-insert\n" " Forces aapt to return an error if it fails to insert values into the manifest\n" " with --debug-mode, --min-sdk-version, --target-sdk-version --version-code\n" @@ -668,6 +671,9 @@ int main(int argc, char* const argv[]) } else if (strcmp(cp, "-shared-lib") == 0) { bundle.setNonConstantId(true); bundle.setBuildSharedLibrary(true); + } else if (strcmp(cp, "-app-as-shared-lib") == 0) { + bundle.setNonConstantId(true); + bundle.setBuildAppAsSharedLibrary(true); } else if (strcmp(cp, "-no-crunch") == 0) { bundle.setUseCrunchCache(true); } else if (strcmp(cp, "-ignore-assets") == 0) { diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index a4e5d3de69c1..e22e76de66c2 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -2120,7 +2120,7 @@ static status_t writeResourceLoadedCallback( size_t N = symbols->getSymbols().size(); for (i=0; i<N; i++) { const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i); - if (sym.typeCode == AaptSymbolEntry::TYPE_UNKNOWN) { + if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) { continue; } if (!assets->isJavaSymbol(sym, includePrivate)) { diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp index 832c51c08600..616f2768cd7b 100644 --- a/tools/aidl/aidl.cpp +++ b/tools/aidl/aidl.cpp @@ -33,6 +33,8 @@ using namespace std; +ParseState *psGlobal; + static void test_document(document_item_type* d) { @@ -111,7 +113,7 @@ main_import_parsed(buffer_type* statement) { import_info* import = (import_info*)malloc(sizeof(import_info)); memset(import, 0, sizeof(import_info)); - import->from = strdup(g_currentFilename); + import->from = strdup(psGlobal->FileName().c_str()); import->statement.lineno = statement->lineno; import->statement.data = strdup(statement->data); import->statement.extra = NULL; diff --git a/tools/aidl/aidl_language.cpp b/tools/aidl/aidl_language.cpp index 5fab6c26431a..4eb3faecb574 100644 --- a/tools/aidl/aidl_language.cpp +++ b/tools/aidl/aidl_language.cpp @@ -1,7 +1,9 @@ #include "aidl_language.h" +#include "aidl_language_y.hpp" #include <stdio.h> -#include <string.h> #include <stdlib.h> +#include <string> +#include <iostream> #ifdef _WIN32 int isatty(int fd) @@ -10,11 +12,83 @@ int isatty(int fd) } #endif -#if 0 -ParserCallbacks k_parserCallbacks = { - NULL -}; -#endif +using std::string; +using std::cerr; +using std::endl; ParserCallbacks* g_callbacks = NULL; // &k_parserCallbacks; +void yylex_init(void **); +void yylex_destroy(void *); +void yyset_in(FILE *f, void *); +int yyparse(ParseState*); + +ParseState::ParseState() : ParseState("") {} + +ParseState::ParseState(const string& filename) + : filename_(filename) { + yylex_init(&scanner_); +} + +ParseState::~ParseState() { + yylex_destroy(scanner_); +} + +string ParseState::FileName() { + return filename_; +} + +string ParseState::Package() { + return g_currentPackage; +} + +void ParseState::ProcessDocument(const document_item_type& items) { + /* The cast is not my fault. I didn't write the code on the other side. */ + /* TODO(sadmac): b/23977313 */ + g_callbacks->document((document_item_type *)&items); +} + +void ParseState::ProcessImport(const buffer_type& statement) { + /* The cast is not my fault. I didn't write the code on the other side. */ + /* TODO(sadmac): b/23977313 */ + g_callbacks->import((buffer_type *)&statement); +} + +void ParseState::ReportError(const string& err) { + /* FIXME: We're printing out the line number as -1. We used to use yylineno + * (which was NEVER correct even before reentrant parsing). Now we'll need + * another way. + */ + cerr << filename_ << ":" << -1 << ": " << err << endl; + error_ = 1; +} + +bool ParseState::FoundNoErrors() { + return error_ == 0; +} + +void *ParseState::Scanner() { + return scanner_; +} + +bool ParseState::OpenFileFromDisk() { + FILE *in = fopen(FileName().c_str(), "r"); + + if (! in) + return false; + + yyset_in(in, Scanner()); + return true; +} + +int ParseState::RunParser() { + int ret = yy::parser(this).parse(); + + free((void *)g_currentPackage); + g_currentPackage = NULL; + + if (error_) + return 1; + + return ret; +} diff --git a/tools/aidl/aidl_language.h b/tools/aidl/aidl_language.h index a3b1efcbaf40..f3a126e9f57a 100644 --- a/tools/aidl/aidl_language.h +++ b/tools/aidl/aidl_language.h @@ -1,6 +1,9 @@ #ifndef AIDL_AIDL_LANGUAGE_H_ #define AIDL_AIDL_LANGUAGE_H_ +#include <string> + +#include "macros.h" typedef enum { NO_EXTRA_TEXT = 0, @@ -141,12 +144,6 @@ typedef struct ParserCallbacks { extern ParserCallbacks* g_callbacks; -// true if there was an error parsing, false otherwise -extern int g_error; - -// the name of the file we're currently parsing -extern char const* g_currentFilename; - // the package name for our current file extern char const* g_currentPackage; @@ -157,6 +154,33 @@ typedef enum { void init_buffer_type(buffer_type* buf, int lineno); +class ParseState { + public: + ParseState(); + ParseState(const std::string& filename); + ~ParseState(); + + bool OpenFileFromDisk(); + int RunParser(); + void ReportError(const std::string& err); + + bool FoundNoErrors(); + std::string FileName(); + std::string Package(); + void *Scanner(); + + void ProcessDocument(const document_item_type& items); + void ProcessImport(const buffer_type& statement); + + private: + int error_ = 0; + std::string filename_; + std::string package_; + void *scanner_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ParseState); +}; + #if __cplusplus } #endif diff --git a/tools/aidl/aidl_language_l.l b/tools/aidl/aidl_language_l.l index aa42f2e9a9fd..540b550035ff 100644 --- a/tools/aidl/aidl_language_l.l +++ b/tools/aidl/aidl_language_l.l @@ -1,6 +1,6 @@ %{ #include "aidl_language.h" -#include "aidl_language_y.h" +#include "aidl_language_y.hpp" #include "search_path.h" #include <string.h> #include <stdlib.h> @@ -20,16 +20,19 @@ static void do_package_statement(const char* importText); #define SET_BUFFER(t) \ do { \ - yylval.buffer.lineno = yylineno; \ - yylval.buffer.token = (t); \ - yylval.buffer.data = strdup(yytext); \ - yylval.buffer.extra = get_extra_text(); \ + yylval->buffer.lineno = yyget_lineno(yyscanner); \ + yylval->buffer.token = (t); \ + yylval->buffer.data = strdup(yytext); \ + yylval->buffer.extra = get_extra_text(); \ } while(0) %} %option yylineno %option noyywrap +%option reentrant +%option bison-bridge +%option bison-locations %x COPYING LONG_COMMENT @@ -56,13 +59,13 @@ idvalue (0|[1-9][0-9]*) <LONG_COMMENT>\**\/ { BEGIN(INITIAL); } ^{whitespace}?import{whitespace}[^ \t\r\n]+{whitespace}?; { - SET_BUFFER(IMPORT); - return IMPORT; + SET_BUFFER(yy::parser::token::IMPORT); + return yy::parser::token::IMPORT; } ^{whitespace}?package{whitespace}[^ \t\r\n]+{whitespace}?; { do_package_statement(yytext); - SET_BUFFER(PACKAGE); - return PACKAGE; + SET_BUFFER(yy::parser::token::PACKAGE); + return yy::parser::token::PACKAGE; } <<EOF>> { yyterminate(); } @@ -81,25 +84,25 @@ idvalue (0|[1-9][0-9]*) = { SET_BUFFER('='); return '='; } /* keywords */ -parcelable { SET_BUFFER(PARCELABLE); return PARCELABLE; } -interface { SET_BUFFER(INTERFACE); return INTERFACE; } -in { SET_BUFFER(IN); return IN; } -out { SET_BUFFER(OUT); return OUT; } -inout { SET_BUFFER(INOUT); return INOUT; } -oneway { SET_BUFFER(ONEWAY); return ONEWAY; } - -{brackets}+ { SET_BUFFER(ARRAY); return ARRAY; } -{idvalue} { SET_BUFFER(IDVALUE); return IDVALUE; } -{identifier} { SET_BUFFER(IDENTIFIER); return IDENTIFIER; } +parcelable { SET_BUFFER(yy::parser::token::PARCELABLE); return yy::parser::token::PARCELABLE; } +interface { SET_BUFFER(yy::parser::token::INTERFACE); return yy::parser::token::INTERFACE; } +in { SET_BUFFER(yy::parser::token::IN); return yy::parser::token::IN; } +out { SET_BUFFER(yy::parser::token::OUT); return yy::parser::token::OUT; } +inout { SET_BUFFER(yy::parser::token::INOUT); return yy::parser::token::INOUT; } +oneway { SET_BUFFER(yy::parser::token::ONEWAY); return yy::parser::token::ONEWAY; } + +{brackets}+ { SET_BUFFER(yy::parser::token::ARRAY); return yy::parser::token::ARRAY; } +{idvalue} { SET_BUFFER(yy::parser::token::IDVALUE); return yy::parser::token::IDVALUE; } +{identifier} { SET_BUFFER(yy::parser::token::IDENTIFIER); return yy::parser::token::IDENTIFIER; } {identifier}\<{whitespace}*{identifier}({whitespace}*,{whitespace}*{identifier})*{whitespace}*\> { - SET_BUFFER(GENERIC); return GENERIC; } + SET_BUFFER(yy::parser::token::GENERIC); return yy::parser::token::GENERIC; } /* syntax error! */ . { printf("UNKNOWN(%s)", yytext); - yylval.buffer.lineno = yylineno; - yylval.buffer.token = IDENTIFIER; - yylval.buffer.data = strdup(yytext); - return IDENTIFIER; + yylval->buffer.lineno = yylineno; + yylval->buffer.token = yy::parser::token::IDENTIFIER; + yylval->buffer.data = strdup(yytext); + return yy::parser::token::IDENTIFIER; } %% @@ -177,36 +180,17 @@ void do_package_statement(const char* importText) // main parse function // ================================================ -char const* g_currentFilename = NULL; +extern ParseState *psGlobal; char const* g_currentPackage = NULL; -int yyparse(void); +int parse_aidl(char const *filename) { + ParseState ps(filename); + psGlobal = &ps; -int parse_aidl(char const *filename) -{ - yyin = fopen(filename, "r"); - if (yyin) { - char const* oldFilename = g_currentFilename; - char const* oldPackage = g_currentPackage; - g_currentFilename = strdup(filename); - - g_error = 0; - yylineno = 1; - int rv = yyparse(); - if (g_error != 0) { - rv = g_error; - } - - free((void*)g_currentFilename); - g_currentFilename = oldFilename; - - if (g_currentPackage) free((void*)g_currentPackage); - g_currentPackage = oldPackage; - - return rv; - } else { - fprintf(stderr, "aidl: unable to open file for read: %s\n", filename); - return 1; - } -} + if (!ps.OpenFileFromDisk()) { + fprintf(stderr, "aidl: unable to open file for read: %s\n", filename); + return 1; + } + return ps.RunParser(); +} diff --git a/tools/aidl/aidl_language_y.y b/tools/aidl/aidl_language_y.y index 9c5d10e6892a..7a2b78514ba4 100644 --- a/tools/aidl/aidl_language_y.y +++ b/tools/aidl/aidl_language_y.y @@ -1,17 +1,24 @@ %{ #include "aidl_language.h" +#include "aidl_language_y.hpp" #include <stdio.h> #include <stdlib.h> #include <string.h> -int yyerror(char* errstr); -int yylex(void); -extern int yylineno; +int yylex(lexer_type *, yy::parser::location_type *l, void *); static int count_brackets(const char*); +#define lex_scanner ps->Scanner() + %} +%parse-param { ParseState* ps } +%lex-param { void *lex_scanner } + +%pure-parser +%skeleton "glr.cc" + %token IMPORT %token PACKAGE %token IDENTIFIER @@ -27,8 +34,8 @@ static int count_brackets(const char*); %% document: - document_items { g_callbacks->document($1.document_item); } - | headers document_items { g_callbacks->document($2.document_item); } + document_items { ps->ProcessDocument(*$1.document_item); } + | headers document_items { ps->ProcessDocument(*$2.document_item); } ; headers: @@ -42,8 +49,8 @@ package: ; imports: - IMPORT { g_callbacks->import(&($1.buffer)); } - | IMPORT imports { g_callbacks->import(&($1.buffer)); } + IMPORT { ps->ProcessImport($1.buffer); } + | IMPORT imports { ps->ProcessImport($1.buffer); } ; document_items: @@ -66,7 +73,8 @@ document_items: } } | document_items error { - fprintf(stderr, "%s:%d: syntax error don't know what to do with \"%s\"\n", g_currentFilename, + fprintf(stderr, "%s:%d: syntax error don't know what to do with \"%s\"\n", + ps->FileName().c_str(), $2.buffer.lineno, $2.buffer.data); $$ = $1; } @@ -84,19 +92,20 @@ parcelable_decl: b->document_item.next = NULL; b->keyword_token = $1.buffer; b->name = $2.buffer; - b->package = g_currentPackage ? strdup(g_currentPackage) : NULL; + b->package = + strdup(ps->Package().c_str()); b->semicolon_token = $3.buffer; b->parcelable = true; $$.user_data = b; } | PARCELABLE ';' { fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name.\n", - g_currentFilename, $1.buffer.lineno); + ps->FileName().c_str(), $1.buffer.lineno); $$.user_data = NULL; } | PARCELABLE error ';' { fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name, saw \"%s\".\n", - g_currentFilename, $2.buffer.lineno, $2.buffer.data); + ps->FileName().c_str(), $2.buffer.lineno, $2.buffer.data); $$.user_data = NULL; } ; @@ -128,7 +137,8 @@ interface_decl: interface_header IDENTIFIER '{' interface_items '}' { interface_type* c = $1.interface_obj; c->name = $2.buffer; - c->package = g_currentPackage ? strdup(g_currentPackage) : NULL; + c->package = + strdup(ps->Package().c_str()); c->open_brace_token = $3.buffer; c->interface_items = $4.interface_item; c->close_brace_token = $5.buffer; @@ -136,12 +146,12 @@ interface_decl: } | INTERFACE error '{' interface_items '}' { fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n", - g_currentFilename, $2.buffer.lineno, $2.buffer.data); + ps->FileName().c_str(), $2.buffer.lineno, $2.buffer.data); $$.document_item = NULL; } | INTERFACE error '}' { fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n", - g_currentFilename, $2.buffer.lineno, $2.buffer.data); + ps->FileName().c_str(), $2.buffer.lineno, $2.buffer.data); $$.document_item = NULL; } @@ -163,7 +173,7 @@ interface_items: } | interface_items error ';' { fprintf(stderr, "%s:%d: syntax error before ';' (expected method declaration)\n", - g_currentFilename, $3.buffer.lineno); + ps->FileName().c_str(), $3.buffer.lineno); $$ = $1; } ; @@ -259,7 +269,8 @@ arg_list: } } | error { - fprintf(stderr, "%s:%d: syntax error in parameter list\n", g_currentFilename, $1.buffer.lineno); + fprintf(stderr, "%s:%d: syntax error in parameter list\n", + ps->FileName().c_str(), $1.buffer.lineno); $$.arg = NULL; } ; @@ -279,7 +290,8 @@ arg: type: IDENTIFIER { $$.type.type = $1.buffer; - init_buffer_type(&$$.type.array_token, yylineno); + init_buffer_type(&$$.type.array_token, + $1.buffer.lineno); $$.type.dimension = 0; } | IDENTIFIER ARRAY { @@ -289,13 +301,14 @@ type: } | GENERIC { $$.type.type = $1.buffer; - init_buffer_type(&$$.type.array_token, yylineno); + init_buffer_type(&$$.type.array_token, + $1.buffer.lineno); $$.type.dimension = 0; } ; direction: - { init_buffer_type(&$$.buffer, yylineno); } + { init_buffer_type(&$$.buffer, $$.buffer.lineno); } | IN { $$.buffer = $1.buffer; } | OUT { $$.buffer = $1.buffer; } | INOUT { $$.buffer = $1.buffer; } @@ -306,15 +319,6 @@ direction: #include <ctype.h> #include <stdio.h> -int g_error = 0; - -int yyerror(char* errstr) -{ - fprintf(stderr, "%s:%d: %s\n", g_currentFilename, yylineno, errstr); - g_error = 1; - return 1; -} - void init_buffer_type(buffer_type* buf, int lineno) { buf->lineno = lineno; @@ -332,3 +336,8 @@ static int count_brackets(const char* s) } return n; } + +void yy::parser::error(const yy::parser::location_type& l, const std::string& errstr) +{ + ps->ReportError(errstr); +} diff --git a/tools/aidl/macros.h b/tools/aidl/macros.h new file mode 100644 index 000000000000..67b8076404a4 --- /dev/null +++ b/tools/aidl/macros.h @@ -0,0 +1,8 @@ +#ifndef AIDL_MACROS_H_ +#define AIDL_MACROS_H_ + +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +#endif // AIDL_MACROS_H_ diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index bea1f86907e0..9824fa1b4702 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -148,6 +148,13 @@ public final class BridgeWindowSession implements IWindowSession { } @Override + public boolean startMoving(IWindow window, float startX, float startY) + throws RemoteException { + // pass for now + return false; + } + + @Override public void reportDropResult(IWindow window, boolean consumed) throws RemoteException { // pass for now } |