diff options
57 files changed, 2007 insertions, 494 deletions
diff --git a/api/current.txt b/api/current.txt index 0925dad6d95c..4c900b7cdc18 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1779,6 +1779,7 @@ package android {    public static final class R.id {      ctor public R.id();      field public static final int accessibilityActionContextClick = 16908348; // 0x102003c +    field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042      field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a      field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039      field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b @@ -8187,18 +8188,17 @@ package android.bluetooth.le {  package android.companion { -  public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable { +  public final class AssociationRequest implements android.os.Parcelable {      method public int describeContents();      method public void writeToParcel(android.os.Parcel, int);      field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;    } -  public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> { -    method public android.companion.AssociationRequest<F> build(); -    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice(); -    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice(); -    method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F); -    method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean); +  public static final class AssociationRequest.Builder { +    ctor public AssociationRequest.Builder(); +    method public android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>); +    method public android.companion.AssociationRequest build(); +    method public android.companion.AssociationRequest.Builder setSingleDevice(boolean);    }    public final class BluetoothDeviceFilter implements android.companion.DeviceFilter { @@ -8217,6 +8217,7 @@ package android.companion {    public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter {      method public int describeContents(); +    method public static int getRenamePrefixLengthLimit();      method public void writeToParcel(android.os.Parcel, int);      field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;    } @@ -8225,11 +8226,13 @@ package android.companion {      ctor public BluetoothLEDeviceFilter.Builder();      method public android.companion.BluetoothLEDeviceFilter build();      method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); +    method public android.companion.BluetoothLEDeviceFilter.Builder setRawDataFilter(byte[], byte[]); +    method public android.companion.BluetoothLEDeviceFilter.Builder setRename(java.lang.String, java.lang.String, int, int, boolean);      method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);    }    public final class CompanionDeviceManager { -    method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler); +    method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);      method public void disassociate(java.lang.String);      method public java.util.List<java.lang.String> getAssociations();      field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE"; @@ -8244,6 +8247,18 @@ package android.companion {    public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {    } +  public final class WifiDeviceFilter implements android.companion.DeviceFilter { +    method public int describeContents(); +    method public void writeToParcel(android.os.Parcel, int); +    field public static final android.os.Parcelable.Creator<android.companion.WifiDeviceFilter> CREATOR; +  } + +  public static final class WifiDeviceFilter.Builder { +    ctor public WifiDeviceFilter.Builder(); +    method public android.companion.WifiDeviceFilter build(); +    method public android.companion.WifiDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); +  } +  }  package android.content { @@ -46949,6 +46964,8 @@ package android.view.accessibility {      field public static final java.lang.String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";      field public static final java.lang.String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING";      field public static final java.lang.String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"; +    field public static final java.lang.String ACTION_ARGUMENT_MOVE_WINDOW_X = "android.view.accessibility.action.ARGUMENT_MOVE_WINDOW_X"; +    field public static final java.lang.String ACTION_ARGUMENT_MOVE_WINDOW_Y = "android.view.accessibility.action.ARGUMENT_MOVE_WINDOW_Y";      field public static final java.lang.String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";      field public static final java.lang.String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";      field public static final java.lang.String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; @@ -47005,6 +47022,7 @@ package android.view.accessibility {      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_LONG_CLICK; +    field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_MOVE_WINDOW;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_HTML_ELEMENT;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_PASTE; @@ -47129,6 +47147,7 @@ package android.view.accessibility {      method public android.view.accessibility.AccessibilityNodeInfo getRoot();      method public java.lang.CharSequence getTitle();      method public int getType(); +    method public boolean inPictureInPicture();      method public boolean isAccessibilityFocused();      method public boolean isActive();      method public boolean isFocused(); diff --git a/api/system-current.txt b/api/system-current.txt index b1c55efe15c9..959adf580c63 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1896,6 +1896,7 @@ package android {    public static final class R.id {      ctor public R.id();      field public static final int accessibilityActionContextClick = 16908348; // 0x102003c +    field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042      field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a      field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039      field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b @@ -8688,18 +8689,17 @@ package android.bluetooth.le {  package android.companion { -  public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable { +  public final class AssociationRequest implements android.os.Parcelable {      method public int describeContents();      method public void writeToParcel(android.os.Parcel, int);      field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;    } -  public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> { -    method public android.companion.AssociationRequest<F> build(); -    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice(); -    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice(); -    method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F); -    method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean); +  public static final class AssociationRequest.Builder { +    ctor public AssociationRequest.Builder(); +    method public android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>); +    method public android.companion.AssociationRequest build(); +    method public android.companion.AssociationRequest.Builder setSingleDevice(boolean);    }    public final class BluetoothDeviceFilter implements android.companion.DeviceFilter { @@ -8718,6 +8718,7 @@ package android.companion {    public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter {      method public int describeContents(); +    method public static int getRenamePrefixLengthLimit();      method public void writeToParcel(android.os.Parcel, int);      field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;    } @@ -8726,11 +8727,13 @@ package android.companion {      ctor public BluetoothLEDeviceFilter.Builder();      method public android.companion.BluetoothLEDeviceFilter build();      method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); +    method public android.companion.BluetoothLEDeviceFilter.Builder setRawDataFilter(byte[], byte[]); +    method public android.companion.BluetoothLEDeviceFilter.Builder setRename(java.lang.String, java.lang.String, int, int, boolean);      method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);    }    public final class CompanionDeviceManager { -    method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler); +    method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);      method public void disassociate(java.lang.String);      method public java.util.List<java.lang.String> getAssociations();      field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE"; @@ -8745,6 +8748,18 @@ package android.companion {    public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {    } +  public final class WifiDeviceFilter implements android.companion.DeviceFilter { +    method public int describeContents(); +    method public void writeToParcel(android.os.Parcel, int); +    field public static final android.os.Parcelable.Creator<android.companion.WifiDeviceFilter> CREATOR; +  } + +  public static final class WifiDeviceFilter.Builder { +    ctor public WifiDeviceFilter.Builder(); +    method public android.companion.WifiDeviceFilter build(); +    method public android.companion.WifiDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); +  } +  }  package android.content { @@ -50412,6 +50427,8 @@ package android.view.accessibility {      field public static final java.lang.String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";      field public static final java.lang.String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING";      field public static final java.lang.String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"; +    field public static final java.lang.String ACTION_ARGUMENT_MOVE_WINDOW_X = "android.view.accessibility.action.ARGUMENT_MOVE_WINDOW_X"; +    field public static final java.lang.String ACTION_ARGUMENT_MOVE_WINDOW_Y = "android.view.accessibility.action.ARGUMENT_MOVE_WINDOW_Y";      field public static final java.lang.String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";      field public static final java.lang.String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";      field public static final java.lang.String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; @@ -50468,6 +50485,7 @@ package android.view.accessibility {      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_LONG_CLICK; +    field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_MOVE_WINDOW;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_HTML_ELEMENT;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_PASTE; @@ -50592,6 +50610,7 @@ package android.view.accessibility {      method public android.view.accessibility.AccessibilityNodeInfo getRoot();      method public java.lang.CharSequence getTitle();      method public int getType(); +    method public boolean inPictureInPicture();      method public boolean isAccessibilityFocused();      method public boolean isActive();      method public boolean isFocused(); diff --git a/api/test-current.txt b/api/test-current.txt index ffe8346f644a..80b63794136c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1779,6 +1779,7 @@ package android {    public static final class R.id {      ctor public R.id();      field public static final int accessibilityActionContextClick = 16908348; // 0x102003c +    field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042      field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a      field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039      field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b @@ -8214,18 +8215,17 @@ package android.bluetooth.le {  package android.companion { -  public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable { +  public final class AssociationRequest implements android.os.Parcelable {      method public int describeContents();      method public void writeToParcel(android.os.Parcel, int);      field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;    } -  public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> { -    method public android.companion.AssociationRequest<F> build(); -    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice(); -    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice(); -    method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F); -    method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean); +  public static final class AssociationRequest.Builder { +    ctor public AssociationRequest.Builder(); +    method public android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>); +    method public android.companion.AssociationRequest build(); +    method public android.companion.AssociationRequest.Builder setSingleDevice(boolean);    }    public final class BluetoothDeviceFilter implements android.companion.DeviceFilter { @@ -8244,6 +8244,7 @@ package android.companion {    public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter {      method public int describeContents(); +    method public static int getRenamePrefixLengthLimit();      method public void writeToParcel(android.os.Parcel, int);      field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;    } @@ -8252,11 +8253,13 @@ package android.companion {      ctor public BluetoothLEDeviceFilter.Builder();      method public android.companion.BluetoothLEDeviceFilter build();      method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); +    method public android.companion.BluetoothLEDeviceFilter.Builder setRawDataFilter(byte[], byte[]); +    method public android.companion.BluetoothLEDeviceFilter.Builder setRename(java.lang.String, java.lang.String, int, int, boolean);      method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);    }    public final class CompanionDeviceManager { -    method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler); +    method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);      method public void disassociate(java.lang.String);      method public java.util.List<java.lang.String> getAssociations();      field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE"; @@ -8271,6 +8274,18 @@ package android.companion {    public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {    } +  public final class WifiDeviceFilter implements android.companion.DeviceFilter { +    method public int describeContents(); +    method public void writeToParcel(android.os.Parcel, int); +    field public static final android.os.Parcelable.Creator<android.companion.WifiDeviceFilter> CREATOR; +  } + +  public static final class WifiDeviceFilter.Builder { +    ctor public WifiDeviceFilter.Builder(); +    method public android.companion.WifiDeviceFilter build(); +    method public android.companion.WifiDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); +  } +  }  package android.content { @@ -47317,6 +47332,8 @@ package android.view.accessibility {      field public static final java.lang.String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";      field public static final java.lang.String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING";      field public static final java.lang.String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"; +    field public static final java.lang.String ACTION_ARGUMENT_MOVE_WINDOW_X = "android.view.accessibility.action.ARGUMENT_MOVE_WINDOW_X"; +    field public static final java.lang.String ACTION_ARGUMENT_MOVE_WINDOW_Y = "android.view.accessibility.action.ARGUMENT_MOVE_WINDOW_Y";      field public static final java.lang.String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";      field public static final java.lang.String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";      field public static final java.lang.String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; @@ -47373,6 +47390,7 @@ package android.view.accessibility {      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_LONG_CLICK; +    field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_MOVE_WINDOW;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_HTML_ELEMENT;      field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_PASTE; @@ -47497,6 +47515,7 @@ package android.view.accessibility {      method public android.view.accessibility.AccessibilityNodeInfo getRoot();      method public java.lang.CharSequence getTitle();      method public int getType(); +    method public boolean inPictureInPicture();      method public boolean isAccessibilityFocused();      method public boolean isActive();      method public boolean isFocused(); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index b4e119e52fb7..64d7d4c1be97 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -1438,7 +1438,7 @@ public abstract class AccessibilityService extends Service {       */      public AccessibilityNodeInfo findFocus(int focus) {          return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, -                AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus); +                AccessibilityWindowInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);      }      /** diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 1d6f42ecdb0b..18e7599e1846 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -381,7 +381,7 @@ public final class UiAutomation {       */      public AccessibilityNodeInfo findFocus(int focus) {          return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, -                AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus); +                AccessibilityWindowInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);      }      /** diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java index b89c64a8cac6..457096bf843e 100644 --- a/core/java/android/bluetooth/le/ScanFilter.java +++ b/core/java/android/bluetooth/le/ScanFilter.java @@ -23,6 +23,8 @@ import android.os.Parcel;  import android.os.ParcelUuid;  import android.os.Parcelable; +import com.android.internal.util.BitUtils; +  import java.util.Arrays;  import java.util.List;  import java.util.Objects; @@ -345,15 +347,7 @@ public final class ScanFilter implements Parcelable {      // Check if the uuid pattern matches the particular service uuid.      private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { -        if (mask == null) { -            return uuid.equals(data); -        } -        if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) != -                (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) { -            return false; -        } -        return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) == -                (data.getMostSignificantBits() & mask.getMostSignificantBits())); +        return BitUtils.maskedEquals(data, uuid, mask);      }      // Check whether the data pattern matches the parsed data. diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index d477f43ac8c2..56f5d4483270 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -16,20 +16,21 @@  package android.companion; -import android.annotation.IntDef;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.os.Parcel;  import android.os.Parcelable;  import android.provider.OneTimeUseBuilder; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.List;  /**   * A request for the user to select a companion device to associate with.   * - * You can optionally set a {@link Builder#setDeviceFilter filter} for which devices to show to the + * You can optionally set {@link Builder#addDeviceFilter filters} for which devices to show to the   * user to select from.   * The exact type and fields of the filter you can set depend on the   * medium type. See {@link Builder}'s static factory methods for specific protocols that are @@ -37,38 +38,22 @@ import java.lang.annotation.RetentionPolicy;   *   * You can also set {@link Builder#setSingleDevice single device} to request a popup with single   * device to be shown instead of a list to choose from - * - * @param <F> Device filter type   */ -public final class AssociationRequest<F extends DeviceFilter> implements Parcelable { - -    /** @hide */ -    public static final int MEDIUM_TYPE_BLUETOOTH = 0; -    /** @hide */ -    public static final int MEDIUM_TYPE_BLUETOOTH_LE = 1; -    /** @hide */ -    public static final int MEDIUM_TYPE_WIFI = 2; - -    /** @hide */ -    @IntDef({MEDIUM_TYPE_BLUETOOTH, MEDIUM_TYPE_BLUETOOTH_LE, MEDIUM_TYPE_WIFI}) -    @Retention(RetentionPolicy.SOURCE) -    public @interface MediumType {} +public final class AssociationRequest implements Parcelable {      private final boolean mSingleDevice; -    private final int mMediumType; -    private final F mDeviceFilter; +    private final List<DeviceFilter<?>> mDeviceFilters; -    private AssociationRequest(boolean singleDevice, int mMediumType, F deviceFilter) { +    private AssociationRequest( +            boolean singleDevice, @Nullable List<DeviceFilter<?>> deviceFilters) {          this.mSingleDevice = singleDevice; -        this.mMediumType = mMediumType; -        this.mDeviceFilter = deviceFilter; +        this.mDeviceFilters = ArrayUtils.emptyIfNull(deviceFilters);      }      private AssociationRequest(Parcel in) {          this(              in.readByte() != 0, -            in.readInt(), -            in.readParcelable(AssociationRequest.class.getClassLoader())); +            in.readParcelableList(new ArrayList<>(), AssociationRequest.class.getClassLoader()));      }      /** @hide */ @@ -77,22 +62,15 @@ public final class AssociationRequest<F extends DeviceFilter> implements Parcela      }      /** @hide */ -    @MediumType -    public int getMediumType() { -        return mMediumType; -    } - -    /** @hide */ -    @Nullable -    public F getDeviceFilter() { -        return mDeviceFilter; +    @NonNull +    public List<DeviceFilter<?>> getDeviceFilters() { +        return mDeviceFilters;      }      @Override      public void writeToParcel(Parcel dest, int flags) {          dest.writeByte((byte) (mSingleDevice ? 1 : 0)); -        dest.writeInt(mMediumType); -        dest.writeParcelable(mDeviceFilter, flags); +        dest.writeParcelableList(mDeviceFilters, flags);      }      @Override @@ -114,45 +92,19 @@ public final class AssociationRequest<F extends DeviceFilter> implements Parcela      /**       * A builder for {@link AssociationRequest} -     * -     * @param <F> the type of filter for the request.       */ -    public static final class Builder<F extends DeviceFilter> -            extends OneTimeUseBuilder<AssociationRequest<F>> { +    public static final class Builder extends OneTimeUseBuilder<AssociationRequest> {          private boolean mSingleDevice = false; -        @MediumType private int mMediumType; -        @Nullable private F mDeviceFilter = null; - -        private Builder() {} - -        /** -         * Create a new builder for an association request with a Bluetooth LE device -         */ -        @NonNull -        public static Builder<BluetoothLEDeviceFilter> createForBluetoothLEDevice() { -            return new Builder<BluetoothLEDeviceFilter>() -                    .setMediumType(MEDIUM_TYPE_BLUETOOTH_LE); -        } - -        /** -         * Create a new builder for an association request with a Bluetooth(non-LE) device -         */ -        @NonNull -        public static Builder<BluetoothDeviceFilter> createForBluetoothDevice() { -            return new Builder<BluetoothDeviceFilter>() -                    .setMediumType(MEDIUM_TYPE_BLUETOOTH); -        } +        @Nullable private ArrayList<DeviceFilter<?>> mDeviceFilters = null; -        //TODO implement, once specific filter classes are available -//        public static Builder<> createForWiFiDevice() -//        public static Builder<> createForNanDevice() +        public Builder() {}          /**           * @param singleDevice if true, scanning for a device will stop as soon as at least one           *                     fitting device is found           */          @NonNull -        public Builder<F> setSingleDevice(boolean singleDevice) { +        public Builder setSingleDevice(boolean singleDevice) {              checkNotUsed();              this.mSingleDevice = singleDevice;              return this; @@ -163,29 +115,20 @@ public final class AssociationRequest<F extends DeviceFilter> implements Parcela           *                     user           */          @NonNull -        public Builder<F> setDeviceFilter(@Nullable F deviceFilter) { +        public Builder addDeviceFilter(@Nullable DeviceFilter<?> deviceFilter) {              checkNotUsed(); -            this.mDeviceFilter = deviceFilter; -            return this; -        } - -        /** -         * @param deviceType A type of medium over which to discover devices -         * -         * @see MediumType -         */ -        @NonNull -        private Builder<F> setMediumType(@MediumType int deviceType) { -            mMediumType = deviceType; +            if (deviceFilter != null) { +                mDeviceFilters = ArrayUtils.add(mDeviceFilters, deviceFilter); +            }              return this;          }          /** @inheritDoc */          @NonNull          @Override -        public AssociationRequest<F> build() { +        public AssociationRequest build() {              markUsed(); -            return new AssociationRequest<>(mSingleDevice, mMediumType, mDeviceFilter); +            return new AssociationRequest(mSingleDevice, mDeviceFilters);          }      }  } diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java index 5a69955429aa..0f16b7b90165 100644 --- a/core/java/android/companion/BluetoothDeviceFilter.java +++ b/core/java/android/companion/BluetoothDeviceFilter.java @@ -16,6 +16,7 @@  package android.companion; +import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;  import static android.companion.BluetoothDeviceFilterUtils.matchesAddress;  import static android.companion.BluetoothDeviceFilterUtils.matchesName;  import static android.companion.BluetoothDeviceFilterUtils.matchesServiceUuids; @@ -40,8 +41,6 @@ import java.util.regex.Pattern;   */  public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice> { -    private static BluetoothDeviceFilter NO_OP; -      private final Pattern mNamePattern;      private final String mAddress;      private final List<ParcelUuid> mServiceUuids; @@ -67,30 +66,27 @@ public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice      }      private static List<ParcelUuid> readUuids(Parcel in) { -        final ArrayList<ParcelUuid> list = new ArrayList<>(); -        in.readParcelableList(list, ParcelUuid.class.getClassLoader()); -        return list; +        return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader());      }      /** @hide */ -    @NonNull -    public static BluetoothDeviceFilter nullsafe(@Nullable BluetoothDeviceFilter nullable) { -        return nullable != null ? nullable : noOp(); +    @Override +    public boolean matches(BluetoothDevice device) { +        return matchesAddress(mAddress, device) +                && matchesServiceUuids(mServiceUuids, mServiceUuidMasks, device) +                && matchesName(getNamePattern(), device);      }      /** @hide */ -    @NonNull -    public static BluetoothDeviceFilter noOp() { -        if (NO_OP == null) NO_OP = new Builder().build(); -        return NO_OP; +    @Override +    public String getDeviceDisplayName(BluetoothDevice device) { +        return getDeviceDisplayNameInternal(device);      }      /** @hide */      @Override -    public boolean matches(BluetoothDevice device) { -        return matchesAddress(mAddress, device) -                && matchesServiceUuids(mServiceUuids, mServiceUuidMasks, device) -                && matchesName(getNamePattern(), device); +    public int getMediumType() { +        return DeviceFilter.MEDIUM_TYPE_BLUETOOTH;      }      /** @hide */ diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java index 289f9953a068..8a316f19af8e 100644 --- a/core/java/android/companion/BluetoothDeviceFilterUtils.java +++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java @@ -23,7 +23,9 @@ import android.annotation.NonNull;  import android.annotation.Nullable;  import android.bluetooth.BluetoothDevice;  import android.bluetooth.le.ScanFilter; +import android.net.wifi.ScanResult;  import android.os.ParcelUuid; +import android.os.Parcelable;  import android.util.Log;  import java.util.Arrays; @@ -96,12 +98,47 @@ public class BluetoothDeviceFilterUtils {          return result;      } +    static boolean matchesName(@Nullable Pattern namePattern, ScanResult device) { +        boolean result; +        if (namePattern == null)  { +            result = true; +        } else if (device == null) { +            result = false; +        } else { +            final String name = device.SSID; +            result = name != null && namePattern.matcher(name).find(); +        } +        if (DEBUG) debugLogMatchResult(result, device, namePattern); +        return result; +    } +      private static void debugLogMatchResult(              boolean result, BluetoothDevice device, Object criteria) { -        Log.i(LOG_TAG, getDeviceDisplayName(device) + (result ? " ~ " : " !~ ") + criteria); +        Log.i(LOG_TAG, getDeviceDisplayNameInternal(device) + (result ? " ~ " : " !~ ") + criteria);      } -    public static String getDeviceDisplayName(@NonNull BluetoothDevice device) { +    private static void debugLogMatchResult( +            boolean result, ScanResult device, Object criteria) { +        Log.i(LOG_TAG, getDeviceDisplayNameInternal(device) + (result ? " ~ " : " !~ ") + criteria); +    } + +    public static String getDeviceDisplayNameInternal(@NonNull BluetoothDevice device) {          return firstNotEmpty(device.getAliasName(), device.getAddress());      } + +    public static String getDeviceDisplayNameInternal(@NonNull ScanResult device) { +        return firstNotEmpty(device.SSID, device.BSSID); +    } + +    public static String getDeviceMacAddress(@NonNull Parcelable device) { +        if (device instanceof BluetoothDevice) { +            return ((BluetoothDevice) device).getAddress(); +        } else if (device instanceof ScanResult) { +            return ((ScanResult) device).BSSID; +        } else if (device instanceof android.bluetooth.le.ScanResult) { +            return getDeviceMacAddress(((android.bluetooth.le.ScanResult) device).getDevice()); +        } else { +            throw new IllegalArgumentException("Unknown device type: " + device); +        } +    }  } diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java index 4a481ca80045..e057fbcc901a 100644 --- a/core/java/android/companion/BluetoothLEDeviceFilter.java +++ b/core/java/android/companion/BluetoothLEDeviceFilter.java @@ -16,18 +16,25 @@  package android.companion; +import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;  import static android.companion.BluetoothDeviceFilterUtils.patternFromString;  import static android.companion.BluetoothDeviceFilterUtils.patternToString; +import static com.android.internal.util.Preconditions.checkArgument; +  import android.annotation.NonNull;  import android.annotation.Nullable; -import android.annotation.SuppressLint;  import android.bluetooth.BluetoothDevice;  import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult;  import android.os.Parcel;  import android.provider.OneTimeUseBuilder; +import android.text.TextUtils; +import com.android.internal.util.BitUtils;  import com.android.internal.util.ObjectUtils; +import com.android.internal.util.Preconditions;  import java.util.regex.Pattern; @@ -36,57 +43,122 @@ import java.util.regex.Pattern;   *   * @see ScanFilter   */ -public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevice> { +public final class BluetoothLEDeviceFilter implements DeviceFilter<ScanResult> { -    private static BluetoothLEDeviceFilter NO_OP; +    private static final int RENAME_PREFIX_LENGTH_LIMIT = 10;      private final Pattern mNamePattern;      private final ScanFilter mScanFilter; +    private final byte[] mRawDataFilter; +    private final byte[] mRawDataFilterMask; +    private final String mRenamePrefix; +    private final String mRenameSuffix; +    private final int mRenameBytesFrom; +    private final int mRenameBytesTo; +    private final boolean mRenameBytesReverseOrder; -    private BluetoothLEDeviceFilter(Pattern namePattern, ScanFilter scanFilter) { +    private BluetoothLEDeviceFilter(Pattern namePattern, ScanFilter scanFilter, +            byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix, +            String renameSuffix, int renameBytesFrom, int renameBytesTo, +            boolean renameBytesReverseOrder) {          mNamePattern = namePattern;          mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY); +        mRawDataFilter = rawDataFilter; +        mRawDataFilterMask = rawDataFilterMask; +        mRenamePrefix = renamePrefix; +        mRenameSuffix = renameSuffix; +        mRenameBytesFrom = renameBytesFrom; +        mRenameBytesTo = renameBytesTo; +        mRenameBytesReverseOrder = renameBytesReverseOrder;      } -    @SuppressLint("ParcelClassLoader") -    private BluetoothLEDeviceFilter(Parcel in) { -        this( -            patternFromString(in.readString()), -            in.readParcelable(null)); +    /** @hide */ +    @Nullable +    public Pattern getNamePattern() { +        return mNamePattern;      }      /** @hide */      @NonNull -    public static BluetoothLEDeviceFilter nullsafe(@Nullable BluetoothLEDeviceFilter nullable) { -        return nullable != null ? nullable : noOp(); +    public ScanFilter getScanFilter() { +        return mScanFilter;      }      /** @hide */ -    @NonNull -    public static BluetoothLEDeviceFilter noOp() { -        if (NO_OP == null) NO_OP = new Builder().build(); -        return NO_OP; +    @Nullable +    public byte[] getRawDataFilter() { +        return mRawDataFilter;      }      /** @hide */      @Nullable -    public Pattern getNamePattern() { -        return mNamePattern; +    public byte[] getRawDataFilterMask() { +        return mRawDataFilterMask;      }      /** @hide */ -    @NonNull -    public ScanFilter getScanFilter() { -        return mScanFilter; +    @Nullable +    public String getRenamePrefix() { +        return mRenamePrefix; +    } + +    /** @hide */ +    @Nullable +    public String getRenameSuffix() { +        return mRenameSuffix; +    } + +    /** @hide */ +    public int getRenameBytesFrom() { +        return mRenameBytesFrom; +    } + +    /** @hide */ +    public int getRenameBytesTo() { +        return mRenameBytesTo; +    } + +    /** @hide */ +    public boolean isRenameBytesReverseOrder() { +        return mRenameBytesReverseOrder; +    } + +    /** @hide */ +    @Override +    @Nullable +    public String getDeviceDisplayName(ScanResult sr) { +        if (mRenameBytesFrom < 0) return getDeviceDisplayNameInternal(sr.getDevice()); +        final byte[] bytes = sr.getScanRecord().getBytes(); +        final StringBuilder sb = new StringBuilder(TextUtils.emptyIfNull(mRenamePrefix)); +        int startInclusive = mRenameBytesFrom; +        int endInclusive = mRenameBytesTo - 1; +        int initial = mRenameBytesReverseOrder ? endInclusive : startInclusive; +        int step = mRenameBytesReverseOrder ? -1 : 1; +        for (int i = initial; startInclusive <= i && i <= endInclusive; i+=step) { +            sb.append(Byte.toHexString(bytes[i], true)); +        } +        return sb.append(TextUtils.emptyIfNull(mRenameSuffix)).toString();      }      /** @hide */      @Override -    public boolean matches(BluetoothDevice device) { +    public boolean matches(ScanResult device) { +        return matches(device.getDevice()) +                && BitUtils.maskedEquals(device.getScanRecord().getBytes(), +                        mRawDataFilter, mRawDataFilterMask); +    } + +    private boolean matches(BluetoothDevice device) {          return BluetoothDeviceFilterUtils.matches(getScanFilter(), device)                  && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device);      } +    /** @hide */ +    @Override +    public int getMediumType() { +        return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE; +    } +      @Override      public void writeToParcel(Parcel dest, int flags) {          dest.writeString(patternToString(getNamePattern())); @@ -102,7 +174,13 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi              = new Creator<BluetoothLEDeviceFilter>() {          @Override          public BluetoothLEDeviceFilter createFromParcel(Parcel in) { -            return new BluetoothLEDeviceFilter(in); +            return new BluetoothLEDeviceFilter.Builder() +                    .setNamePattern(patternFromString(in.readString())) +                    .setScanFilter(in.readParcelable(null)) +                    .setRawDataFilter(in.readBlob(), in.readBlob()) +                    .setRename(in.readString(), in.readString(), +                            in.readInt(), in.readInt(), in.readBoolean()) +                    .build();          }          @Override @@ -111,16 +189,28 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi          }      }; +    public static int getRenamePrefixLengthLimit() { +        return RENAME_PREFIX_LENGTH_LIMIT; +    } +      /**       * Builder for {@link BluetoothLEDeviceFilter}       */      public static final class Builder extends OneTimeUseBuilder<BluetoothLEDeviceFilter> {          private ScanFilter mScanFilter;          private Pattern mNamePattern; +        private byte[] mRawDataFilter; +        private byte[] mRawDataFilterMask; +        private String mRenamePrefix; +        private String mRenameSuffix; +        private int mRenameBytesFrom = -1; +        private int mRenameBytesTo; +        private boolean mRenameBytesReverseOrder = false;          /**           * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the           *              given regular expression will be shown +         * @return self for chaining           */          public Builder setNamePattern(@Nullable Pattern regex) {              checkNotUsed(); @@ -131,6 +221,7 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi          /**           * @param scanFilter a {@link ScanFilter} to filter devices by           * +         * @return self for chaining           * @see ScanFilter for specific details on its various fields           */          @NonNull @@ -140,12 +231,66 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi              return this;          } +        /** +         * Filter devices by raw advertisement data, as obtained by {@link ScanRecord#getBytes} +         * +         * @param rawDataFilter bit values that have to match against advertized data +         * @param rawDataFilterMask bits that have to be matched +         * @return self for chaining +         */ +        @NonNull +        public Builder setRawDataFilter(@NonNull byte[] rawDataFilter, +                @NonNull byte[] rawDataFilterMask) { +            checkNotUsed(); +            checkArgument(rawDataFilter.length == rawDataFilterMask.length, +                    "Mask and filter should be the same length"); +            mRawDataFilter = Preconditions.checkNotNull(rawDataFilter); +            mRawDataFilterMask = Preconditions.checkNotNull(rawDataFilterMask); +            return this; +        } + +        /** +         * Rename the devices shown in the list, using specific bytes from the raw advertisement +         * data ({@link ScanRecord#getBytes}) in hexadecimal format, as well as a custom +         * prefix/suffix around them +         * +         * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters +         * to ensure that there's enough space to display the byte data +         * +         * The range of bytes to be displayed cannot be empty +         * +         * @param prefix to be displayed before the byte data +         * @param suffix to be displayed after the byte data +         * @param bytesFrom the start byte index to be displayed (inclusive) +         * @param bytesTo the end byte index to be displayed (exclusive) +         * @param bytesReverseOrder if true, the byte order of the provided range will be flipped +         *                          when displaying +         * @return self for chaining +         */ +        @NonNull +        public Builder setRename(@NonNull String prefix, @NonNull String suffix, +                int bytesFrom, int bytesTo, boolean bytesReverseOrder) { +            checkNotUsed(); +            checkArgument(TextUtils.length(prefix) >= getRenamePrefixLengthLimit(), +                    "Prefix is too short"); +            mRenamePrefix = prefix; +            mRenameSuffix = suffix; +            checkArgument(bytesFrom < bytesTo, "Byte range must be non-empty"); +            mRenameBytesFrom = bytesFrom; +            mRenameBytesTo = bytesTo; +            mRenameBytesReverseOrder = bytesReverseOrder; +            return this; +        } +          /** @inheritDoc */          @Override          @NonNull          public BluetoothLEDeviceFilter build() {              markUsed(); -            return new BluetoothLEDeviceFilter(mNamePattern, mScanFilter); +            return new BluetoothLEDeviceFilter(mNamePattern, mScanFilter, +                    mRawDataFilter, mRawDataFilterMask, +                    mRenamePrefix, mRenameSuffix, +                    mRenameBytesFrom, mRenameBytesTo, mRenameBytesReverseOrder);          }      }  } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 6fa32b4b6944..5710ad1318d7 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -117,7 +117,7 @@ public final class CompanionDeviceManager {       * @see AssociationRequest       */      public void associate( -            @NonNull AssociationRequest<?> request, +            @NonNull AssociationRequest request,              @NonNull Callback callback,              @Nullable Handler handler) {          final Handler finalHandler = handler != null diff --git a/core/java/android/companion/DeviceFilter.java b/core/java/android/companion/DeviceFilter.java index 8362b2dab8fd..9b4fdfdf5108 100644 --- a/core/java/android/companion/DeviceFilter.java +++ b/core/java/android/companion/DeviceFilter.java @@ -17,17 +17,28 @@  package android.companion; +import android.annotation.IntDef;  import android.annotation.Nullable;  import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +  /**   * A filter for companion devices of type {@code D}   *   * @param <D> Type of devices, filtered by this filter, - *           e.g. {@link android.bluetooth.BluetoothDevice}, {@link android.net.wifi.WifiInfo} + *           e.g. {@link android.bluetooth.BluetoothDevice}, {@link android.net.wifi.ScanResult}   */  public interface DeviceFilter<D extends Parcelable> extends Parcelable { +    /** @hide */ +    int MEDIUM_TYPE_BLUETOOTH = 0; +    /** @hide */ +    int MEDIUM_TYPE_BLUETOOTH_LE = 1; +    /** @hide */ +    int MEDIUM_TYPE_WIFI = 2; +      /**       * @return whether the given device matches this filter       * @@ -35,6 +46,12 @@ public interface DeviceFilter<D extends Parcelable> extends Parcelable {       */      boolean matches(D device); +    /** @hide */ +    String getDeviceDisplayName(D device); + +    /**  @hide */ +    @MediumType int getMediumType(); +      /**       * A nullsafe {@link #matches(Parcelable)}, returning true if the filter is null       * @@ -43,4 +60,9 @@ public interface DeviceFilter<D extends Parcelable> extends Parcelable {      static <D extends Parcelable> boolean matches(@Nullable DeviceFilter<D> filter, D device) {          return filter == null || filter.matches(device);      } + +    /** @hide */ +    @IntDef({MEDIUM_TYPE_BLUETOOTH, MEDIUM_TYPE_BLUETOOTH_LE, MEDIUM_TYPE_WIFI}) +    @Retention(RetentionPolicy.SOURCE) +    @interface MediumType {}  } diff --git a/core/java/android/companion/WifiDeviceFilter.java b/core/java/android/companion/WifiDeviceFilter.java new file mode 100644 index 000000000000..1ab9ce11cb0f --- /dev/null +++ b/core/java/android/companion/WifiDeviceFilter.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion; + +import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal; +import static android.companion.BluetoothDeviceFilterUtils.patternFromString; +import static android.companion.BluetoothDeviceFilterUtils.patternToString; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.ScanFilter; +import android.net.wifi.ScanResult; +import android.os.Parcel; +import android.provider.OneTimeUseBuilder; + +import java.util.regex.Pattern; + +/** + * A filter for Wifi devices + * + * @see ScanFilter + */ +public final class WifiDeviceFilter implements DeviceFilter<ScanResult> { + +    private final Pattern mNamePattern; + +    private WifiDeviceFilter(Pattern namePattern) { +        mNamePattern = namePattern; +    } + +    @SuppressLint("ParcelClassLoader") +    private WifiDeviceFilter(Parcel in) { +        this(patternFromString(in.readString())); +    } + +    /** @hide */ +    @Nullable +    public Pattern getNamePattern() { +        return mNamePattern; +    } + + +    /** @hide */ +    @Override +    public boolean matches(ScanResult device) { +        return BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); +    } + +    /** @hide */ +    @Override +    public String getDeviceDisplayName(ScanResult device) { +        return getDeviceDisplayNameInternal(device); +    } + +    /** @hide */ +    @Override +    public int getMediumType() { +        return MEDIUM_TYPE_WIFI; +    } + +    @Override +    public void writeToParcel(Parcel dest, int flags) { +        dest.writeString(patternToString(getNamePattern())); +    } + +    @Override +    public int describeContents() { +        return 0; +    } + +    public static final Creator<WifiDeviceFilter> CREATOR +            = new Creator<WifiDeviceFilter>() { +        @Override +        public WifiDeviceFilter createFromParcel(Parcel in) { +            return new WifiDeviceFilter(in); +        } + +        @Override +        public WifiDeviceFilter[] newArray(int size) { +            return new WifiDeviceFilter[size]; +        } +    }; + +    /** +     * Builder for {@link WifiDeviceFilter} +     */ +    public static final class Builder extends OneTimeUseBuilder<WifiDeviceFilter> { +        private Pattern mNamePattern; + +        /** +         * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the +         *              given regular expression will be shown +         * @return self for chaining +         */ +        public Builder setNamePattern(@Nullable Pattern regex) { +            checkNotUsed(); +            mNamePattern = regex; +            return this; +        } + +        /** @inheritDoc */ +        @Override +        @NonNull +        public WifiDeviceFilter build() { +            markUsed(); +            return new WifiDeviceFilter(mNamePattern); +        } +    } +} diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index f94e89a2785d..7a39d239f84b 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -601,6 +601,11 @@ public final class Parcel {          nativeWriteString(mNativePtr, val);      } +    /** @hide */ +    public final void writeBoolean(boolean val) { +        writeInt(val ? 1 : 0); +    } +      /**       * Write a CharSequence value into the parcel at the current dataPosition(),       * growing dataCapacity() if needed. @@ -1964,6 +1969,11 @@ public final class Parcel {          return nativeReadString(mNativePtr);      } +    /** @hide */ +    public final boolean readBoolean() { +        return readInt() != 0; +    } +      /**       * Read a CharSequence value from the parcel at the current dataPosition().       * @hide @@ -2490,11 +2500,11 @@ public final class Parcel {       * @see #writeParcelableList(List, int)       * @hide       */ -    public final <T extends Parcelable> void readParcelableList(List<T> list, ClassLoader cl) { +    public final <T extends Parcelable> List<T> readParcelableList(List<T> list, ClassLoader cl) {          final int N = readInt();          if (N == -1) {              list.clear(); -            return; +            return list;          }          final int M = list.size(); @@ -2508,6 +2518,7 @@ public final class Parcel {          for (; i<M; i++) {              list.remove(N);          } +        return list;      }      /** diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index ac9c0d782c53..ee2b38e4f390 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -474,6 +474,11 @@ public class TextUtils {          return !isEmpty(a) ? a : Preconditions.checkStringNotEmpty(b);      } +    /** {@hide} */ +    public static int length(@Nullable String s) { +        return isEmpty(s) ? 0 : s.length(); +    } +      /**       * Returns the length that the specified CharSequence would have if       * spaces and ASCII control characters were trimmed from the start and end, diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 77e9f0f2aa74..1b74c13be518 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -161,7 +161,7 @@ final class AccessibilityInteractionController {              }              mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;              View root = null; -            if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +            if (accessibilityViewId == AccessibilityNodeInfo.ROOT_ITEM_ID) {                  root = mViewRootImpl.mView;              } else {                  root = findViewByAccessibilityId(accessibilityViewId); @@ -217,7 +217,7 @@ final class AccessibilityInteractionController {              }              mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;              View root = null; -            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {                  root = findViewByAccessibilityId(accessibilityViewId);              } else {                  root = mViewRootImpl.mView; @@ -283,7 +283,7 @@ final class AccessibilityInteractionController {              }              mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;              View root = null; -            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {                  root = findViewByAccessibilityId(accessibilityViewId);              } else {                  root = mViewRootImpl.mView; @@ -291,14 +291,9 @@ final class AccessibilityInteractionController {              if (root != null && isShown(root)) {                  AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();                  if (provider != null) { -                    if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { -                        infos = provider.findAccessibilityNodeInfosByText(text, -                                virtualDescendantId); -                    } else { -                        infos = provider.findAccessibilityNodeInfosByText(text, -                                AccessibilityNodeProvider.HOST_VIEW_ID); -                    } -                } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +                    infos = provider.findAccessibilityNodeInfosByText(text, +                            virtualDescendantId); +                } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {                      ArrayList<View> foundViews = mTempArrayList;                      foundViews.clear();                      root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT @@ -376,7 +371,7 @@ final class AccessibilityInteractionController {              }              mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;              View root = null; -            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {                  root = findViewByAccessibilityId(accessibilityViewId);              } else {                  root = mViewRootImpl.mView; @@ -402,7 +397,7 @@ final class AccessibilityInteractionController {                                  focused = AccessibilityNodeInfo.obtain(                                          mViewRootImpl.mAccessibilityFocusedVirtualView);                              } -                        } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +                        } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {                              focused = host.createAccessibilityNodeInfo();                          }                      } break; @@ -471,7 +466,7 @@ final class AccessibilityInteractionController {              }              mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;              View root = null; -            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {                  root = findViewByAccessibilityId(accessibilityViewId);              } else {                  root = mViewRootImpl.mView; @@ -531,7 +526,7 @@ final class AccessibilityInteractionController {              }              mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;              View target = null; -            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +            if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {                  target = findViewByAccessibilityId(accessibilityViewId);              } else {                  target = mViewRootImpl.mView; @@ -544,14 +539,9 @@ final class AccessibilityInteractionController {                  } else {                      AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();                      if (provider != null) { -                        if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { -                            succeeded = provider.performAction(virtualDescendantId, action, -                                    arguments); -                        } else { -                            succeeded = provider.performAction( -                                    AccessibilityNodeProvider.HOST_VIEW_ID, action, arguments); -                        } -                    } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +                        succeeded = provider.performAction(virtualDescendantId, action, +                                arguments); +                    } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {                          succeeded = target.performAccessibilityAction(action, arguments);                      }                  } @@ -711,7 +701,9 @@ final class AccessibilityInteractionController {              applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);              adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);              callback.setFindAccessibilityNodeInfosResult(infos, interactionId); -            infos.clear(); +            if (infos != null) { +                infos.clear(); +            }          } catch (RemoteException re) {              /* ignore - the other side will time out */          } finally { @@ -761,10 +753,8 @@ final class AccessibilityInteractionController {          AccessibilityNodeInfo infoWithSpan = null;          AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();          if (provider != null) { -            int idForNode = (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) -                    ? AccessibilityNodeProvider.HOST_VIEW_ID : virtualDescendantId; -            infoWithSpan = provider.createAccessibilityNodeInfo(idForNode); -        } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +            infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId); +        } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {              infoWithSpan = view.createAccessibilityNodeInfo();          }          if (infoWithSpan == null) { @@ -817,13 +807,12 @@ final class AccessibilityInteractionController {                      }                  }              } else { -                final int idForRoot = (virtualViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) -                        ? AccessibilityNodeProvider.HOST_VIEW_ID : virtualViewId; -                final AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(idForRoot); +                final AccessibilityNodeInfo root = +                        provider.createAccessibilityNodeInfo(virtualViewId);                  if (root != null) {                      if (extraDataRequested != null) {                          provider.addExtraDataToAccessibilityNodeInfo( -                                idForRoot, root, extraDataRequested, arguments); +                                virtualViewId, root, extraDataRequested, arguments);                      }                      outInfos.add(root);                      if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { @@ -1034,15 +1023,10 @@ final class AccessibilityInteractionController {                  }                  final int virtualDescendantId =                      AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); -                if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID +                if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID                          || accessibilityViewId == providerHost.getAccessibilityViewId()) {                      final AccessibilityNodeInfo parent; -                    if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { -                        parent = provider.createAccessibilityNodeInfo(virtualDescendantId); -                    } else { -                        parent = provider.createAccessibilityNodeInfo( -                                AccessibilityNodeProvider.HOST_VIEW_ID); -                    } +                    parent = provider.createAccessibilityNodeInfo(virtualDescendantId);                      if (parent == null) {                          // Going up the parent relation we found a null predecessor,                          // so remove these disconnected nodes form the result. @@ -1072,15 +1056,10 @@ final class AccessibilityInteractionController {                  AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);              final int parentVirtualDescendantId =                  AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); -            if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID +            if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID                      || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { -                final AccessibilityNodeInfo parent; -                if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { -                    parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId); -                } else { -                    parent = provider.createAccessibilityNodeInfo( -                            AccessibilityNodeProvider.HOST_VIEW_ID); -                } +                final AccessibilityNodeInfo parent = +                        provider.createAccessibilityNodeInfo(parentVirtualDescendantId);                  if (parent != null) {                      final int childCount = parent.getChildCount();                      for (int i = 0; i < childCount; i++) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index de816762c348..aa1cbf2d642e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -94,6 +94,7 @@ import android.view.accessibility.AccessibilityManager;  import android.view.accessibility.AccessibilityNodeInfo;  import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;  import android.view.accessibility.AccessibilityNodeProvider; +import android.view.accessibility.AccessibilityWindowInfo;  import android.view.animation.Animation;  import android.view.animation.AnimationUtils;  import android.view.animation.Transformation; @@ -8102,7 +8103,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,       */      public int getAccessibilityWindowId() {          return mAttachInfo != null ? mAttachInfo.mAccessibilityWindowId -                : AccessibilityNodeInfo.UNDEFINED_ITEM_ID; +                : AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;      }      /** @@ -24657,7 +24658,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,          /**           * The id of the window for accessibility purposes.           */ -        int mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; +        int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;          /**           * Flags related to accessibility processing. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f9863b0a6761..580888c4c3bc 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -82,6 +82,7 @@ import android.view.accessibility.AccessibilityManager.HighTextContrastChangeLis  import android.view.accessibility.AccessibilityNodeInfo;  import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;  import android.view.accessibility.AccessibilityNodeProvider; +import android.view.accessibility.AccessibilityWindowInfo;  import android.view.accessibility.IAccessibilityInteractionConnection;  import android.view.accessibility.IAccessibilityInteractionConnectionCallback;  import android.view.animation.AccelerateDecelerateInterpolator; @@ -6880,12 +6881,7 @@ public final class ViewRootImpl implements ViewParent,                          final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(                                  sourceNodeId);                          final AccessibilityNodeInfo node; -                        if (virtualNodeId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { -                            node = provider.createAccessibilityNodeInfo( -                                    AccessibilityNodeProvider.HOST_VIEW_ID); -                        } else { -                            node = provider.createAccessibilityNodeInfo(virtualNodeId); -                        } +                        node = provider.createAccessibilityNodeInfo(virtualNodeId);                          setAccessibilityFocus(source, node);                      }                  } @@ -6971,10 +6967,6 @@ public final class ViewRootImpl implements ViewParent,          final long focusedSourceNodeId = mAccessibilityFocusedVirtualView.getSourceNodeId();          int focusedChildId = AccessibilityNodeInfo.getVirtualDescendantId(focusedSourceNodeId); -        if (focusedChildId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { -            // TODO: Should we clear the focused virtual view? -            focusedChildId = AccessibilityNodeProvider.HOST_VIEW_ID; -        }          // Refresh the node for the focused virtual view.          final Rect oldBounds = mTempRect; @@ -7495,8 +7487,8 @@ public final class ViewRootImpl implements ViewParent,          }          public void ensureConnection() { -            final boolean registered = -                    mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; +            final boolean registered = mAttachInfo.mAccessibilityWindowId +                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;              if (!registered) {                  mAttachInfo.mAccessibilityWindowId =                          mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, @@ -7505,10 +7497,10 @@ public final class ViewRootImpl implements ViewParent,          }          public void ensureNoConnection() { -            final boolean registered = -                mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; +            final boolean registered = mAttachInfo.mAccessibilityWindowId +                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;              if (registered) { -                mAttachInfo.mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; +                mAttachInfo.mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;                  mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);              }          } diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java index 737e46071313..95a63944b3bc 100644 --- a/core/java/android/view/WindowInfo.java +++ b/core/java/android/view/WindowInfo.java @@ -46,6 +46,7 @@ public class WindowInfo implements Parcelable {      public List<IBinder> childTokens;      public CharSequence title;      public int accessibilityIdOfAnchor = View.NO_ID; +    public boolean inPictureInPicture;      private WindowInfo() {          /* do nothing - hide constructor */ @@ -69,6 +70,7 @@ public class WindowInfo implements Parcelable {          window.boundsInScreen.set(other.boundsInScreen);          window.title = other.title;          window.accessibilityIdOfAnchor = other.accessibilityIdOfAnchor; +        window.inPictureInPicture = other.inPictureInPicture;          if (other.childTokens != null && !other.childTokens.isEmpty()) {              if (window.childTokens == null) { @@ -101,6 +103,7 @@ public class WindowInfo implements Parcelable {          boundsInScreen.writeToParcel(parcel, flags);          parcel.writeCharSequence(title);          parcel.writeInt(accessibilityIdOfAnchor); +        parcel.writeInt(inPictureInPicture ? 1 : 0);          if (childTokens != null && !childTokens.isEmpty()) {              parcel.writeInt(1); @@ -136,6 +139,7 @@ public class WindowInfo implements Parcelable {          boundsInScreen.readFromParcel(parcel);          title = parcel.readCharSequence();          accessibilityIdOfAnchor = parcel.readInt(); +        inPictureInPicture = (parcel.readInt() == 1);          final boolean hasChildren = (parcel.readInt() == 1);          if (hasChildren) { @@ -156,6 +160,7 @@ public class WindowInfo implements Parcelable {          if (childTokens != null) {              childTokens.clear();          } +        inPictureInPicture = false;      }      public static final Parcelable.Creator<WindowInfo> CREATOR = diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 828583cdfe81..19213ca06c5e 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -158,7 +158,7 @@ public final class AccessibilityInteractionClient       */      public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {          return findAccessibilityNodeInfoByAccessibilityId(connectionId, -                AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, +                AccessibilityWindowInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,                  false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);      } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 45302b6cbdce..fe888ec56c09 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -21,6 +21,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_  import android.Manifest;  import android.accessibilityservice.AccessibilityServiceInfo;  import android.annotation.NonNull; +import android.annotation.Nullable;  import android.content.ComponentName;  import android.content.Context;  import android.content.pm.PackageManager; @@ -822,6 +823,31 @@ public final class AccessibilityManager {          }      } +    /** +     * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture +     * window. Intended for use by the System UI only. +     * +     * @param connection The connection to handle the actions. Set to {@code null} to avoid +     * affecting the actions. +     * +     * @hide +     */ +    public void setPictureInPictureActionReplacingConnection( +            @Nullable IAccessibilityInteractionConnection connection) { +        final IAccessibilityManager service; +        synchronized (mLock) { +            service = getServiceLocked(); +            if (service == null) { +                return; +            } +        } +        try { +            service.setPictureInPictureActionReplacingConnection(connection); +        } catch (RemoteException re) { +            Log.e(LOG_TAG, "Error setting picture in picture action replacement", re); +        } +    } +      private IAccessibilityManager getServiceLocked() {          if (mService == null) {              tryConnectToServiceLocked(null); diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 8094fa6a1c71..50f17e089585 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -85,17 +85,19 @@ public class AccessibilityNodeInfo implements Parcelable {      /** @hide */      public static final int UNDEFINED_SELECTION_INDEX = -1; +    /* Special IDs for node source IDs */      /** @hide */      public static final int UNDEFINED_ITEM_ID = Integer.MAX_VALUE;      /** @hide */ -    public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID); +    public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;      /** @hide */ -    public static final int ACTIVE_WINDOW_ID = UNDEFINED_ITEM_ID; +    public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);      /** @hide */ -    public static final int ANY_WINDOW_ID = -2; +    public static final long ROOT_NODE_ID = makeNodeId(ROOT_ITEM_ID, +            AccessibilityNodeProvider.HOST_VIEW_ID);      /** @hide */      public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001; @@ -474,6 +476,34 @@ public class AccessibilityNodeInfo implements Parcelable {              "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";      /** +     * Argument for specifying the x coordinate to which to move a window. +     * <p> +     * <strong>Type:</strong> int<br> +     * <strong>Actions:</strong> +     * <ul> +     *     <li>{@link AccessibilityAction#ACTION_MOVE_WINDOW}</li> +     * </ul> +     * +     * @see AccessibilityAction#ACTION_MOVE_WINDOW +     */ +    public static final String ACTION_ARGUMENT_MOVE_WINDOW_X = +            "android.view.accessibility.action.ARGUMENT_MOVE_WINDOW_X"; + +    /** +     * Argument for specifying the y coordinate to which to move a window. +     * <p> +     * <strong>Type:</strong> int<br> +     * <strong>Actions:</strong> +     * <ul> +     *     <li>{@link AccessibilityAction#ACTION_MOVE_WINDOW}</li> +     * </ul> +     * +     * @see AccessibilityAction#ACTION_MOVE_WINDOW +     */ +    public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y = +            "android.view.accessibility.action.ARGUMENT_MOVE_WINDOW_Y"; + +    /**       * Argument to pass the {@link AccessibilityClickableSpan}.       * For use with R.id.accessibilityActionClickOnClickableSpan       * @hide @@ -654,13 +684,6 @@ public class AccessibilityNodeInfo implements Parcelable {       * @hide       */      public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) { -        // We changed the value for undefined node to positive due to wrong -        // global id composition (two 32-bin ints into one 64-bit long) but -        // the value used for the host node provider view has id -1 so we -        // remap it here. -        if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { -            virtualDescendantId = UNDEFINED_ITEM_ID; -        }          return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;      } @@ -673,12 +696,12 @@ public class AccessibilityNodeInfo implements Parcelable {      // Data.      private int mWindowId = UNDEFINED_ITEM_ID; -    private long mSourceNodeId = ROOT_NODE_ID; -    private long mParentNodeId = ROOT_NODE_ID; -    private long mLabelForId = ROOT_NODE_ID; -    private long mLabeledById = ROOT_NODE_ID; -    private long mTraversalBefore = ROOT_NODE_ID; -    private long mTraversalAfter = ROOT_NODE_ID; +    private long mSourceNodeId = UNDEFINED_NODE_ID; +    private long mParentNodeId = UNDEFINED_NODE_ID; +    private long mLabelForId = UNDEFINED_NODE_ID; +    private long mLabeledById = UNDEFINED_NODE_ID; +    private long mTraversalBefore = UNDEFINED_NODE_ID; +    private long mTraversalAfter = UNDEFINED_NODE_ID;      private int mBooleanProperties;      private final Rect mBoundsInParent = new Rect(); @@ -733,7 +756,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @param source The info source.       */      public void setSource(View source) { -        setSource(source, UNDEFINED_ITEM_ID); +        setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID);      }      /** @@ -950,7 +973,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @throws IllegalStateException If called from an AccessibilityService.       */      public void addChild(View child) { -        addChildInternal(child, UNDEFINED_ITEM_ID, true); +        addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, true);      }      /** @@ -960,7 +983,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @hide       */      public void addChildUnchecked(View child) { -        addChildInternal(child, UNDEFINED_ITEM_ID, false); +        addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, false);      }      /** @@ -978,7 +1001,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @throws IllegalStateException If called from an AccessibilityService.       */      public boolean removeChild(View child) { -        return removeChild(child, UNDEFINED_ITEM_ID); +        return removeChild(child, AccessibilityNodeProvider.HOST_VIEW_ID);      }      /** @@ -1205,6 +1228,17 @@ public class AccessibilityNodeInfo implements Parcelable {      }      /** +     * Removes all actions. +     * +     * @hide +     */ +    public void removeAllActions() { +        if (mActions != null) { +            mActions.clear(); +        } +    } + +    /**       * Gets the node before which this one is visited during traversal. A screen-reader       * must visit the content of this node before the content of the one it precedes.       * @@ -1233,7 +1267,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @see #getTraversalBefore()       */      public void setTraversalBefore(View view) { -        setTraversalBefore(view, UNDEFINED_ITEM_ID); +        setTraversalBefore(view, AccessibilityNodeProvider.HOST_VIEW_ID);      }      /** @@ -1294,7 +1328,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @see #getTraversalAfter()       */      public void setTraversalAfter(View view) { -        setTraversalAfter(view, UNDEFINED_ITEM_ID); +        setTraversalAfter(view, AccessibilityNodeProvider.HOST_VIEW_ID);      }      /** @@ -1572,7 +1606,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @throws IllegalStateException If called from an AccessibilityService.       */      public void setParent(View parent) { -        setParent(parent, UNDEFINED_ITEM_ID); +        setParent(parent, AccessibilityNodeProvider.HOST_VIEW_ID);      }      /** @@ -2533,7 +2567,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @param labeled The view for which this info serves as a label.       */      public void setLabelFor(View labeled) { -        setLabelFor(labeled, UNDEFINED_ITEM_ID); +        setLabelFor(labeled, AccessibilityNodeProvider.HOST_VIEW_ID);      }      /** @@ -2585,7 +2619,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * @param label The view that labels this node's source.       */      public void setLabeledBy(View label) { -        setLabeledBy(label, UNDEFINED_ITEM_ID); +        setLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);      }      /** @@ -2815,6 +2849,20 @@ public class AccessibilityNodeInfo implements Parcelable {      }      /** +     * Sets the id of the source node. +     * +     * @param sourceId The id. +     * @param windowId The window id. +     * +     * @hide +     */ +    public void setSourceNodeId(long sourceId, int windowId) { +        enforceNotSealed(); +        mSourceNodeId = sourceId; +        mWindowId = windowId; +    } + +    /**       * Gets the id of the source node.       *       * @return The id. @@ -3299,12 +3347,12 @@ public class AccessibilityNodeInfo implements Parcelable {       */      private void clear() {          mSealed = false; -        mSourceNodeId = ROOT_NODE_ID; -        mParentNodeId = ROOT_NODE_ID; -        mLabelForId = ROOT_NODE_ID; -        mLabeledById = ROOT_NODE_ID; -        mTraversalBefore = ROOT_NODE_ID; -        mTraversalAfter = ROOT_NODE_ID; +        mSourceNodeId = UNDEFINED_NODE_ID; +        mParentNodeId = UNDEFINED_NODE_ID; +        mLabelForId = UNDEFINED_NODE_ID; +        mLabeledById = UNDEFINED_NODE_ID; +        mTraversalBefore = UNDEFINED_NODE_ID; +        mTraversalAfter = UNDEFINED_NODE_ID;          mWindowId = UNDEFINED_ITEM_ID;          mConnectionId = UNDEFINED_CONNECTION_ID;          mMaxTextLength = -1; @@ -3324,9 +3372,7 @@ public class AccessibilityNodeInfo implements Parcelable {          mError = null;          mContentDescription = null;          mViewIdResourceName = null; -        if (mActions != null) { -            mActions.clear(); -        } +        removeAllActions();          mTextSelectionStart = UNDEFINED_SELECTION_INDEX;          mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;          mInputType = InputType.TYPE_NULL; @@ -3975,6 +4021,16 @@ public class AccessibilityNodeInfo implements Parcelable {          public static final AccessibilityAction ACTION_SET_PROGRESS =                  new AccessibilityAction(R.id.accessibilityActionSetProgress, null); +        /** +         * Action to move a window to a new location. +         * <p> +         * <strong>Arguments:</strong> +         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_X} +         * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_Y} +         */ +        public static final AccessibilityAction ACTION_MOVE_WINDOW = +                new AccessibilityAction(R.id.accessibilityActionMoveWindow, null); +          private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<>();          static {              sStandardActions.add(ACTION_FOCUS); diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index f2979bb82dcd..3f1fece3eff4 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -91,7 +91,7 @@ public class AccessibilityRecord {      int mAddedCount= UNDEFINED;      int mRemovedCount = UNDEFINED;      AccessibilityNodeInfo mSourceNode; -    int mSourceWindowId = UNDEFINED; +    int mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;      CharSequence mClassName;      CharSequence mContentDescription; @@ -136,11 +136,12 @@ public class AccessibilityRecord {      public void setSource(View root, int virtualDescendantId) {          enforceNotSealed();          boolean important = true; -        mSourceWindowId = UNDEFINED; +        mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;          clearSourceNode();          if (root != null) { -            if (virtualDescendantId == UNDEFINED || -                    virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { +            if (virtualDescendantId == View.NO_ID +                    || virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID +                    || virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {                  important = root.isImportantForAccessibility();                  mSourceNode = root.createAccessibilityNodeInfo();              } else { @@ -156,6 +157,25 @@ public class AccessibilityRecord {      }      /** +     * Set the source directly to an AccessibilityNodeInfo rather than indirectly via a View +     * +     * @param info The source +     * +     * @hide +     */ +    public void setSource(AccessibilityNodeInfo info) { +        enforceNotSealed(); +        clearSourceNode(); +        mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; +        if (info != null) { +            mSourceNode = AccessibilityNodeInfo.obtain(info); +            setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, +                    mSourceNode.isImportantForAccessibility()); +            mSourceWindowId = info.getWindowId(); +        } +    } + +    /**       * Gets the {@link AccessibilityNodeInfo} of the event source.       * <p>       *   <strong>Note:</strong> It is a client responsibility to recycle the received info @@ -833,6 +853,7 @@ public class AccessibilityRecord {              mSourceNode.recycle();              mSourceNode = null;          } +      }      @Override diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index c3904060247a..2a7537b08921 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -74,7 +74,15 @@ public final class AccessibilityWindowInfo implements Parcelable {       */      public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; -    private static final int UNDEFINED = -1; +    /* Special values for window IDs */ +    /** @hide */ +    public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE; +    /** @hide */ +    public static final int UNDEFINED_WINDOW_ID = -1; +    /** @hide */ +    public static final int ANY_WINDOW_ID = -2; +    /** @hide */ +    public static final int PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID = -3;      private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;      private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1; @@ -87,17 +95,18 @@ public final class AccessibilityWindowInfo implements Parcelable {      private static AtomicInteger sNumInstancesInUse;      // Data. -    private int mType = UNDEFINED; -    private int mLayer = UNDEFINED; +    private int mType = UNDEFINED_WINDOW_ID; +    private int mLayer = UNDEFINED_WINDOW_ID;      private int mBooleanProperties; -    private int mId = UNDEFINED; -    private int mParentId = UNDEFINED; +    private int mId = UNDEFINED_WINDOW_ID; +    private int mParentId = UNDEFINED_WINDOW_ID;      private final Rect mBoundsInScreen = new Rect();      private LongArray mChildIds;      private CharSequence mTitle; -    private int mAnchorId = UNDEFINED; +    private int mAnchorId = UNDEFINED_WINDOW_ID; +    private boolean mInPictureInPicture; -    private int mConnectionId = UNDEFINED; +    private int mConnectionId = UNDEFINED_WINDOW_ID;      private AccessibilityWindowInfo() {          /* do nothing - hide constructor */ @@ -177,7 +186,7 @@ public final class AccessibilityWindowInfo implements Parcelable {       * @return The root node.       */      public AccessibilityNodeInfo getRoot() { -        if (mConnectionId == UNDEFINED) { +        if (mConnectionId == UNDEFINED_WINDOW_ID) {              return null;          }          AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); @@ -203,7 +212,8 @@ public final class AccessibilityWindowInfo implements Parcelable {       * @return The anchor node, or {@code null} if none exists.       */      public AccessibilityNodeInfo getAnchor() { -        if ((mConnectionId == UNDEFINED) || (mAnchorId == UNDEFINED) || (mParentId == UNDEFINED)) { +        if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID) +                || (mParentId == UNDEFINED_WINDOW_ID)) {              return null;          } @@ -212,13 +222,27 @@ public final class AccessibilityWindowInfo implements Parcelable {                  mParentId, mAnchorId, true, 0, null);      } +    /** @hide */ +    public void setPictureInPicture(boolean pictureInPicture) { +        mInPictureInPicture = pictureInPicture; +    } + +    /** +     * Check if the window is in picture-in-picture mode. +     * +     * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise. +     */ +    public boolean inPictureInPicture() { +        return mInPictureInPicture; +    } +      /**       * Gets the parent window.       *       * @return The parent window, or {@code null} if none exists.       */      public AccessibilityWindowInfo getParent() { -        if (mConnectionId == UNDEFINED || mParentId == UNDEFINED) { +        if (mConnectionId == UNDEFINED_WINDOW_ID || mParentId == UNDEFINED_WINDOW_ID) {              return null;          }          AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); @@ -371,7 +395,7 @@ public final class AccessibilityWindowInfo implements Parcelable {          if (mChildIds == null) {              throw new IndexOutOfBoundsException();          } -        if (mConnectionId == UNDEFINED) { +        if (mConnectionId == UNDEFINED_WINDOW_ID) {              return null;          }          final int childId = (int) mChildIds.get(index); @@ -429,6 +453,7 @@ public final class AccessibilityWindowInfo implements Parcelable {          infoClone.mBoundsInScreen.set(info.mBoundsInScreen);          infoClone.mTitle = info.mTitle;          infoClone.mAnchorId = info.mAnchorId; +        infoClone.mInPictureInPicture = info.mInPictureInPicture;          if (info.mChildIds != null && info.mChildIds.size() > 0) {              if (infoClone.mChildIds == null) { @@ -486,6 +511,7 @@ public final class AccessibilityWindowInfo implements Parcelable {          mBoundsInScreen.writeToParcel(parcel, flags);          parcel.writeCharSequence(mTitle);          parcel.writeInt(mAnchorId); +        parcel.writeInt(mInPictureInPicture ? 1 : 0);          final LongArray childIds = mChildIds;          if (childIds == null) { @@ -510,6 +536,7 @@ public final class AccessibilityWindowInfo implements Parcelable {          mBoundsInScreen.readFromParcel(parcel);          mTitle = parcel.readCharSequence();          mAnchorId = parcel.readInt(); +        mInPictureInPicture = parcel.readInt() == 1;          final int childCount = parcel.readInt();          if (childCount > 0) { @@ -556,6 +583,7 @@ public final class AccessibilityWindowInfo implements Parcelable {          builder.append(", bounds=").append(mBoundsInScreen);          builder.append(", focused=").append(isFocused());          builder.append(", active=").append(isActive()); +        builder.append(", pictureInPicture=").append(inPictureInPicture());          if (DEBUG) {              builder.append(", parent=").append(mParentId);              builder.append(", children=["); @@ -572,8 +600,8 @@ public final class AccessibilityWindowInfo implements Parcelable {              }              builder.append(']');          } else { -            builder.append(", hasParent=").append(mParentId != UNDEFINED); -            builder.append(", isAnchored=").append(mAnchorId != UNDEFINED); +            builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID); +            builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID);              builder.append(", hasChildren=").append(mChildIds != null                      && mChildIds.size() > 0);          } @@ -585,17 +613,18 @@ public final class AccessibilityWindowInfo implements Parcelable {       * Clears the internal state.       */      private void clear() { -        mType = UNDEFINED; -        mLayer = UNDEFINED; +        mType = UNDEFINED_WINDOW_ID; +        mLayer = UNDEFINED_WINDOW_ID;          mBooleanProperties = 0; -        mId = UNDEFINED; -        mParentId = UNDEFINED; +        mId = UNDEFINED_WINDOW_ID; +        mParentId = UNDEFINED_WINDOW_ID;          mBoundsInScreen.setEmpty();          if (mChildIds != null) {              mChildIds.clear();          } -        mConnectionId = UNDEFINED; -        mAnchorId = UNDEFINED; +        mConnectionId = UNDEFINED_WINDOW_ID; +        mAnchorId = UNDEFINED_WINDOW_ID; +        mInPictureInPicture = false;          mTitle = null;      } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 8fde47a30b2b..157980f6cb83 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -45,10 +45,13 @@ interface IAccessibilityManager {      List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);      int addAccessibilityInteractionConnection(IWindow windowToken, -        in IAccessibilityInteractionConnection connection, int userId); +            in IAccessibilityInteractionConnection connection, int userId);      void removeAccessibilityInteractionConnection(IWindow windowToken); +    void setPictureInPictureActionReplacingConnection( +            in IAccessibilityInteractionConnection connection); +      void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,          in AccessibilityServiceInfo info, int flags); diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 7ff115bee1d2..a8e16c96acfa 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -16,6 +16,7 @@  package com.android.internal.policy; +import android.view.accessibility.AccessibilityNodeInfo;  import com.android.internal.R;  import com.android.internal.policy.PhoneWindow.PanelFeatureState;  import com.android.internal.policy.PhoneWindow.PhoneWindowMenuCallback; @@ -2251,6 +2252,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind      }      @Override +    public int getAccessibilityViewId() { +        return AccessibilityNodeInfo.ROOT_ITEM_ID; +    } + +    @Override      public String toString() {          return "DecorView@" + Integer.toHexString(this.hashCode()) + "["                  + getTitleSuffix(mWindow.getAttributes()) + "]"; diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index be69d9f808e2..d0fbe7c8a666 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -237,6 +237,35 @@ public class ArrayUtils {          return false;      } +    @NonNull +    public static <T> List<T> filter(@Nullable List<?> list, Class<T> c) { +        if (isEmpty(list)) return Collections.emptyList(); +        ArrayList<T> result = null; +        for (int i = 0; i < list.size(); i++) { +            final Object item = list.get(i); +            if (c.isInstance(item)) { +                result = add(result, (T) item); +            } +        } +        return emptyIfNull(result); +    } + +    public static <T> boolean any(@Nullable List<T> items, +            java.util.function.Predicate<T> predicate) { +        return find(items, predicate) != null; +    } + +    @Nullable +    public static <T> T find(@Nullable List<T> items, +            java.util.function.Predicate<T> predicate) { +        if (isEmpty(items)) return null; +        for (int i = 0; i < items.size(); i++) { +            final T item = items.get(i); +            if (predicate.test(item)) return item; +        } +        return null; +    } +      public static long total(@Nullable long[] array) {          long total = 0;          if (array != null) { diff --git a/core/java/com/android/internal/util/BitUtils.java b/core/java/com/android/internal/util/BitUtils.java new file mode 100644 index 000000000000..a208ccb8f35f --- /dev/null +++ b/core/java/com/android/internal/util/BitUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.internal.util; + +import android.annotation.Nullable; + +import libcore.util.Objects; + +import java.util.Arrays; +import java.util.UUID; + +public class BitUtils { +    private BitUtils() {} + +    public static boolean maskedEquals(long a, long b, long mask) { +        return (a & mask) == (b & mask); +    } + +    public static boolean maskedEquals(byte a, byte b, byte mask) { +        return (a & mask) == (b & mask); +    } + +    public static boolean maskedEquals(byte[] a, byte[] b, @Nullable byte[] mask) { +        if (a == null || b == null) return a == b; +        Preconditions.checkArgument(a.length == b.length, "Inputs must be of same size"); +        if (mask == null) return Arrays.equals(a, b); +        Preconditions.checkArgument(a.length == mask.length, "Mask must be of same size as inputs"); +        for (int i = 0; i < mask.length; i++) { +            if (!maskedEquals(a[i], b[i], mask[i])) return false; +        } +        return true; +    } + +    public static boolean maskedEquals(UUID a, UUID b, @Nullable UUID mask) { +        if (mask == null) { +            return Objects.equal(a, b); +        } +        return maskedEquals(a.getLeastSignificantBits(), b.getLeastSignificantBits(), +                    mask.getLeastSignificantBits()) +                && maskedEquals(a.getMostSignificantBits(), b.getMostSignificantBits(), +                    mask.getMostSignificantBits()); +    } +} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index c9f9d6f6dfb0..95b2593201e6 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -197,7 +197,6 @@ LOCAL_C_INCLUDES += \      $(JNI_H_INCLUDE) \      $(LOCAL_PATH)/android/graphics \      $(LOCAL_PATH)/../../libs/hwui \ -    $(LOCAL_PATH)/../../../native/opengl/libs \      $(LOCAL_PATH)/../../../native/vulkan/include \      $(call include-path-for, bluedroid) \      $(call include-path-for, libhardware)/hardware \ diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 99edf6ef944e..e1c0a211f7f9 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -27,7 +27,7 @@  #include <EGL/egl.h>  #include <EGL/eglext.h> -#include <EGL/egl_cache.h> +#include <private/EGL/cache.h>  #include <utils/Looper.h>  #include <utils/RefBase.h> @@ -887,7 +887,7 @@ static void android_view_ThreadedRenderer_removeFrameMetricsObserver(JNIEnv* env  static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,          jstring diskCachePath) {      const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); -    egl_cache_t::get()->setCacheFilename(cacheArray); +    android::egl_set_cache_filename(cacheArray);      env->ReleaseStringUTFChars(diskCachePath, cacheArray);  } diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp index 56bec186b8a3..1ac05f91b3c0 100644 --- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp +++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp @@ -22,9 +22,9 @@  #include <utils/misc.h> -#include <EGL/egl_display.h>  #include <EGL/egl.h>  #include <GLES/gl.h> +#include <private/EGL/display.h>  #include <gui/Surface.h>  #include <gui/GLConsumer.h> @@ -180,8 +180,7 @@ static jboolean jni_eglQuerySurface(JNIEnv *_env, jobject _this, jobject display  static jint jni_getInitCount(JNIEnv *_env, jobject _clazz, jobject display) {      EGLDisplay dpy = getDisplay(_env, display); -    egl_display_t* eglDisplay = get_display_nowake(dpy); -    return eglDisplay ? eglDisplay->getRefsCount() : 0; +    return android::egl_get_init_count(dpy);  }  static jboolean jni_eglReleaseThread(JNIEnv *_env, jobject _this) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ac36d835a2f5..c991f22b6294 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2306,6 +2306,10 @@      <permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN"          android:protectionLevel="signature" /> +    <!-- @hide Allows an application to modify accessibility information from another app. --> +    <permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" +                android:protectionLevel="signature" /> +      <!-- @hide Allows an application to collect frame statistics -->      <permission android:name="android.permission.FRAME_STATS"           android:protectionLevel="signature" /> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 613616fa0ea6..f8a071d04372 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -135,4 +135,7 @@    <!-- ItemTouchHelper uses this id to save a View's original elevation. -->    <item type="id" name="item_touch_helper_previous_elevation"/> + +  <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}. --> +  <item type="id" name="accessibilityActionMoveWindow" />  </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index eca3afdb7fd6..359fbcb08266 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2812,6 +2812,7 @@      <public-group type="id" first-id="0x01020041">          <public name="textAssist" /> +        <public name="accessibilityActionMoveWindow" />      </public-group> diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index 65cac09a09b3..34bc4ebcd0aa 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -26,6 +26,8 @@      <uses-permission android:name="android.permission.BLUETOOTH"/>      <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>      <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> +    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> +    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>      <application          android:allowClearUserData="true" diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java index 12bab18c88c9..14b9de59bf5a 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java @@ -16,10 +16,9 @@  package com.android.companiondevicemanager; -import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayName; +import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;  import android.app.Activity; -import android.bluetooth.BluetoothDevice;  import android.companion.CompanionDeviceManager;  import android.content.Intent;  import android.content.pm.PackageManager; @@ -34,6 +33,8 @@ import android.widget.ListView;  import android.widget.ProgressBar;  import android.widget.TextView; +import com.android.companiondevicemanager.DeviceDiscoveryService.DeviceFilterPair; +  public class DeviceChooserActivity extends Activity {      private static final boolean DEBUG = false; @@ -55,11 +56,11 @@ public class DeviceChooserActivity extends Activity {          if (getService().mRequest.isSingleDevice()) {              setContentView(R.layout.device_confirmation); -            final BluetoothDevice selectedDevice = getService().mDevicesFound.get(0); +            final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);              setTitle(Html.fromHtml(getString(                      R.string.confirmation_title,                      getCallingAppName(), -                    getDeviceDisplayName(selectedDevice)), 0)); +                    selectedDevice.getDisplayName()), 0));              getService().mSelectedDevice = selectedDevice;          } else {              setContentView(R.layout.device_chooser); @@ -127,10 +128,11 @@ public class DeviceChooserActivity extends Activity {          return DeviceDiscoveryService.sInstance;      } -    protected void onPairTapped(BluetoothDevice selectedDevice) { -        getService().onDeviceSelected(getCallingPackage(), selectedDevice.getAddress()); +   protected void onPairTapped(DeviceFilterPair selectedDevice) { +        getService().onDeviceSelected( +                getCallingPackage(), getDeviceMacAddress(selectedDevice.device));          setResult(RESULT_OK, -                new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice)); +                new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device));          finish();      }  }
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index f0f910848943..e1e60bb99374 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -16,8 +16,10 @@  package com.android.companiondevicemanager; -import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayName; -import static android.companion.BluetoothLEDeviceFilter.nullsafe; +import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal; +import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress; + +import static com.android.internal.util.ArrayUtils.isEmpty;  import android.annotation.NonNull;  import android.annotation.Nullable; @@ -32,28 +34,38 @@ import android.bluetooth.le.ScanFilter;  import android.bluetooth.le.ScanResult;  import android.bluetooth.le.ScanSettings;  import android.companion.AssociationRequest; +import android.companion.BluetoothDeviceFilter; +import android.companion.BluetoothDeviceFilterUtils;  import android.companion.BluetoothLEDeviceFilter;  import android.companion.CompanionDeviceManager; +import android.companion.DeviceFilter;  import android.companion.ICompanionDeviceDiscoveryService;  import android.companion.ICompanionDeviceDiscoveryServiceCallback;  import android.companion.IFindDeviceCallback; +import android.companion.WifiDeviceFilter;  import android.content.BroadcastReceiver;  import android.content.Context;  import android.content.Intent;  import android.content.IntentFilter;  import android.graphics.Color;  import android.graphics.drawable.Drawable; +import android.net.wifi.WifiManager;  import android.os.IBinder; +import android.os.Parcelable;  import android.os.RemoteException; +import android.text.TextUtils;  import android.util.Log;  import android.view.View;  import android.view.ViewGroup;  import android.widget.ArrayAdapter;  import android.widget.TextView; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; +  import java.util.ArrayList; -import java.util.Collections;  import java.util.List; +import java.util.Objects;  public class DeviceDiscoveryService extends Service { @@ -63,12 +75,16 @@ public class DeviceDiscoveryService extends Service {      static DeviceDiscoveryService sInstance;      private BluetoothAdapter mBluetoothAdapter; -    private BluetoothLEDeviceFilter mFilter; -    private ScanFilter mScanFilter; +    private WifiManager mWifiManager;      private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build(); -    AssociationRequest<?> mRequest; -    List<BluetoothDevice> mDevicesFound; -    BluetoothDevice mSelectedDevice; +    private List<DeviceFilter<?>> mFilters; +    private List<BluetoothLEDeviceFilter> mBLEFilters; +    private List<BluetoothDeviceFilter> mBluetoothFilters; +    private List<WifiDeviceFilter> mWifiFilters; +    private List<ScanFilter> mBLEScanFilters; +    AssociationRequest mRequest; +    List<DeviceFilterPair> mDevicesFound; +    DeviceFilterPair mSelectedDevice;      DevicesAdapter mDevicesAdapter;      IFindDeviceCallback mFindCallback;      ICompanionDeviceDiscoveryServiceCallback mServiceCallback; @@ -95,11 +111,13 @@ public class DeviceDiscoveryService extends Service {      private final ScanCallback mBLEScanCallback = new ScanCallback() {          @Override          public void onScanResult(int callbackType, ScanResult result) { -            final BluetoothDevice device = result.getDevice(); +            final DeviceFilterPair<ScanResult> deviceFilterPair +                    = DeviceFilterPair.findMatch(result, mBLEFilters); +            if (deviceFilterPair == null) return;              if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) { -                onDeviceLost(device); +                onDeviceLost(deviceFilterPair);              } else { -                onDeviceFound(device); +                onDeviceFound(deviceFilterPair);              }          }      }; @@ -109,15 +127,35 @@ public class DeviceDiscoveryService extends Service {      private BroadcastReceiver mBluetoothDeviceFoundBroadcastReceiver = new BroadcastReceiver() {          @Override          public void onReceive(Context context, Intent intent) { -            final BluetoothDevice device = intent.getParcelableExtra( -                    BluetoothDevice.EXTRA_DEVICE); -            if (!mFilter.matches(device)) return; // ignore device - +            final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); +            final DeviceFilterPair<BluetoothDevice> deviceFilterPair +                    = DeviceFilterPair.findMatch(device, mBluetoothFilters); +            if (deviceFilterPair == null) return;              if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) { -                onDeviceFound(device); +                onDeviceFound(deviceFilterPair);              } else { -                onDeviceLost(device); +                onDeviceLost(deviceFilterPair); +            } +        } +    }; + +    private BroadcastReceiver mWifiDeviceFoundBroadcastReceiver = new BroadcastReceiver() { +        @Override +        public void onReceive(Context context, Intent intent) { +            if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { +                List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults(); + +                if (DEBUG) { +                    Log.i(LOG_TAG, "Wifi scan results: " + TextUtils.join("\n", scanResults)); +                } + +                for (int i = 0; i < scanResults.size(); i++) { +                    DeviceFilterPair<android.net.wifi.ScanResult> deviceFilterPair = +                            DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters); +                    if (deviceFilterPair != null) onDeviceFound(deviceFilterPair); +                }              } +          }      }; @@ -135,6 +173,7 @@ public class DeviceDiscoveryService extends Service {          mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter();          mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner(); +        mWifiManager = getSystemService(WifiManager.class);          mDevicesFound = new ArrayList<>();          mDevicesAdapter = new DevicesAdapter(); @@ -142,23 +181,39 @@ public class DeviceDiscoveryService extends Service {          sInstance = this;      } -    private void startDiscovery(AssociationRequest<?> request) { -        //TODO support other protocols as well +    private void startDiscovery(AssociationRequest request) {          mRequest = request; -        mFilter = nullsafe((BluetoothLEDeviceFilter) request.getDeviceFilter()); -        mScanFilter = mFilter.getScanFilter(); + +        mFilters = request.getDeviceFilters(); +        mWifiFilters = ArrayUtils.filter(mFilters, WifiDeviceFilter.class); +        mBluetoothFilters = ArrayUtils.filter(mFilters, BluetoothDeviceFilter.class); +        mBLEFilters = ArrayUtils.filter(mFilters, BluetoothLEDeviceFilter.class); +        mBLEScanFilters = ArrayUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);          reset(); -        final IntentFilter intentFilter = new IntentFilter(); -        intentFilter.addAction(BluetoothDevice.ACTION_FOUND); -        intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED); +        if (shouldScan(mBluetoothFilters)) { +            final IntentFilter intentFilter = new IntentFilter(); +            intentFilter.addAction(BluetoothDevice.ACTION_FOUND); +            intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED); -        registerReceiver(mBluetoothDeviceFoundBroadcastReceiver, intentFilter); -        mBluetoothAdapter.startDiscovery(); +            registerReceiver(mBluetoothDeviceFoundBroadcastReceiver, intentFilter); +            mBluetoothAdapter.startDiscovery(); +        } -        mBLEScanner.startScan( -                Collections.singletonList(mScanFilter), mDefaultScanSettings, mBLEScanCallback); +        if (shouldScan(mBLEFilters)) { +            mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback); +        } + +        if (shouldScan(mWifiFilters)) { +            registerReceiver(mWifiDeviceFoundBroadcastReceiver, +                    new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); +            mWifiManager.startScan(); +        } +    } + +    private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) { +        return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters);      }      private void reset() { @@ -178,25 +233,18 @@ public class DeviceDiscoveryService extends Service {          mBluetoothAdapter.cancelDiscovery();          mBLEScanner.stopScan(mBLEScanCallback);          unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver); +        unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);          stopSelf();      } -    private void onDeviceFound(BluetoothDevice device) { +    private void onDeviceFound(@Nullable DeviceFilterPair device) {          if (mDevicesFound.contains(device)) {              return;          } -        if (DEBUG) { -            Log.i(LOG_TAG, "Considering device " + getDeviceDisplayName(device)); -        } +        if (DEBUG) Log.i(LOG_TAG, "Found device " + device.getDisplayName() + " " +                + getDeviceMacAddress(device.device)); -        if (!mFilter.matches(device)) { -            return; -        } - -        if (DEBUG) { -            Log.i(LOG_TAG, "Found device " + getDeviceDisplayName(device)); -        }          if (mDevicesFound.isEmpty()) {              onReadyToShowUI();          } @@ -217,12 +265,10 @@ public class DeviceDiscoveryService extends Service {          }      } -    private void onDeviceLost(BluetoothDevice device) { +    private void onDeviceLost(@Nullable DeviceFilterPair device) {          mDevicesFound.remove(device);          mDevicesAdapter.notifyDataSetChanged(); -        if (DEBUG) { -            Log.i(LOG_TAG, "Lost device " + getDeviceDisplayName(device)); -        } +        if (DEBUG) Log.i(LOG_TAG, "Lost device " + device.getDisplayName());      }      void onDeviceSelected(String callingPackage, String deviceAddress) { @@ -236,7 +282,8 @@ public class DeviceDiscoveryService extends Service {          }      } -    class DevicesAdapter extends ArrayAdapter<BluetoothDevice> { +    class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> { +        //TODO wifi icon          private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);          private Drawable icon(int drawableRes) { @@ -261,8 +308,8 @@ public class DeviceDiscoveryService extends Service {              return view;          } -        private void bind(TextView textView, BluetoothDevice device) { -            textView.setText(getDeviceDisplayName(device)); +        private void bind(TextView textView, DeviceFilterPair device) { +            textView.setText(device.getDisplayName());              textView.setBackgroundColor(                      device.equals(mSelectedDevice)                              ? Color.GRAY @@ -285,4 +332,62 @@ public class DeviceDiscoveryService extends Service {              return textView;          }      } + +    /** +     * A pair of device and a filter that matched this device if any. +     * +     * @param <T> device type +     */ +    static class DeviceFilterPair<T extends Parcelable> { +        public final T device; +        @Nullable +        public final DeviceFilter<T> filter; + +        private DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) { +            this.device = device; +            this.filter = filter; +        } + +        /** +         * {@code (device, null)} if the filters list is empty or null +         * {@code null} if none of the provided filters match the device +         * {@code (device, filter)} where filter is among the list of filters and matches the device +         */ +        @Nullable +        public static <T extends Parcelable> DeviceFilterPair<T> findMatch( +                T dev, @Nullable List<? extends DeviceFilter<T>> filters) { +            if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null); +            final DeviceFilter<T> matchingFilter = ArrayUtils.find(filters, (f) -> f.matches(dev)); +            return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null; +        } + +        public String getDisplayName() { +            if (filter == null) { +                Preconditions.checkNotNull(device); +                if (device instanceof BluetoothDevice) { +                    return getDeviceDisplayNameInternal((BluetoothDevice) device); +                } else if (device instanceof android.net.wifi.ScanResult) { +                    return getDeviceDisplayNameInternal((android.net.wifi.ScanResult) device); +                } else if (device instanceof ScanResult) { +                    return getDeviceDisplayNameInternal(((ScanResult) device).getDevice()); +                } else { +                    throw new IllegalArgumentException("Unknown device type: " + device.getClass()); +                } +            } +            return filter.getDeviceDisplayName(device); +        } + +        @Override +        public boolean equals(Object o) { +            if (this == o) return true; +            if (o == null || getClass() != o.getClass()) return false; +            DeviceFilterPair<?> that = (DeviceFilterPair<?>) o; +            return Objects.equals(getDeviceMacAddress(device), getDeviceMacAddress(that.device)); +        } + +        @Override +        public int hashCode() { +            return Objects.hash(getDeviceMacAddress(device)); +        } +    }  } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b2e2a2c5f2ff..dcc5501365b6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -177,6 +177,9 @@      <uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" /> +    <!-- accessibility --> +    <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" /> +      <application          android:name=".SystemUIApplication"          android:persistent="true" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c5bcb6e91702..1541649ca24c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1818,6 +1818,9 @@      <!-- App label of the instant apps notification [CHAR LIMIT=60] -->      <string name="instant_apps">Instant Apps</string> +    <!-- Title of menu shown over picture-in-picture. Used for accessibility. --> +    <string name="pip_menu_title">Picture in picture menu</string> +      <!-- Message of the instant apps notification indicating they don't need install [CHAR LIMIT=NONE] -->      <string name="instant_apps_message">Instant apps don\'t require installation.</string> diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java index 7a1849ed741e..762d6bcb5188 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java @@ -44,6 +44,14 @@ public class InputConsumerController {      }      /** +     * Listener interface for callers to learn when this class is registered or unregistered with +     * window manager +     */ +    public interface RegistrationListener { +        void onRegistrationChanged(boolean isRegistered); +    } + +    /**       * Input handler used for the PiP input consumer.       */      private final class PipInputEventReceiver extends InputEventReceiver { @@ -71,6 +79,7 @@ public class InputConsumerController {      private PipInputEventReceiver mInputEventReceiver;      private TouchListener mListener; +    private RegistrationListener mRegistrationListener;      public InputConsumerController(IWindowManager windowManager) {          mWindowManager = windowManager; @@ -85,6 +94,22 @@ public class InputConsumerController {      }      /** +     * Sets the registration listener. +     */ +    public void setRegistrationListener(RegistrationListener listener) { +        mRegistrationListener = listener; +    } + +    /** +     * Check if the InputConsumer is currently registered with WindowManager +     * +     * @return {@code true} if registered, {@code false} if not. +     */ +    public boolean isRegistered() { +        return mInputEventReceiver != null; +    } + +    /**       * Registers the input consumer.       */      public void registerInputConsumer() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 138f6e71ac96..86e2c4956070 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -49,6 +49,7 @@ import android.view.View;  import android.view.ViewConfiguration;  import android.view.ViewGroup;  import android.view.WindowManager.LayoutParams; +import android.view.accessibility.AccessibilityManager;  import android.widget.FrameLayout;  import android.widget.ImageView;  import android.widget.LinearLayout; @@ -143,6 +144,7 @@ public class PipMenuActivity extends Activity {          mExpandButton = (ImageView) findViewById(R.id.expand_button);          updateFromIntent(getIntent()); +        setTitle(R.string.pip_menu_title);          notifyActivityCallback(mMessenger);      } @@ -262,6 +264,9 @@ public class PipMenuActivity extends Activity {                      if (animationFinishedRunnable != null) {                          animationFinishedRunnable.run();                      } +                    if (getSystemService(AccessibilityManager.class).isEnabled()) { +                        finish(); +                    }                  }              });              mMenuContainerAnimator.start(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 7eaecdf7991a..17c344853638 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -21,14 +21,22 @@ import android.content.Context;  import android.graphics.Point;  import android.graphics.PointF;  import android.graphics.Rect; +import android.graphics.Region; +import android.os.Bundle;  import android.os.Handler;  import android.os.RemoteException;  import android.util.Log;  import android.util.Size;  import android.view.IPinnedStackController; -import android.view.IWindowManager; +import android.view.MagnificationSpec;  import android.view.MotionEvent;  import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; +import android.view.accessibility.IAccessibilityInteractionConnection; +import android.view.accessibility.IAccessibilityInteractionConnectionCallback;  import com.android.internal.logging.MetricsLogger;  import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -37,6 +45,8 @@ import com.android.systemui.R;  import com.android.systemui.statusbar.FlingAnimationUtils;  import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List;  /**   * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding @@ -64,6 +74,7 @@ public class PipTouchHandler {      private final PipMenuActivityController mMenuController;      private final PipDismissViewController mDismissViewController;      private final PipSnapAlgorithm mSnapAlgorithm; +    private final AccessibilityManager mAccessibilityManager;      // The current movement bounds      private Rect mMovementBounds = new Rect(); @@ -85,12 +96,20 @@ public class PipTouchHandler {          }      }; +    private Runnable mShowMenuRunnable = new Runnable() { +        @Override +        public void run() { +            mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds); +        } +    }; +      // Behaviour states      private boolean mIsMenuVisible;      private boolean mIsMinimized;      private boolean mIsImeShowing;      private int mImeHeight;      private float mSavedSnapFraction = -1f; +    private boolean mSendingHoverAccessibilityEvents;      // Touch state      private final PipTouchState mTouchState; @@ -138,6 +157,7 @@ public class PipTouchHandler {          // Initialize the Pip input consumer          mContext = context;          mActivityManager = activityManager; +        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);          mViewConfig = ViewConfiguration.get(context);          mMenuController = menuController;          mMenuController.addListener(mMenuListener); @@ -155,6 +175,8 @@ public class PipTouchHandler {          // Register the listener for input consumer touch events          inputConsumerController.setTouchListener(this::handleTouchEvent); +        inputConsumerController.setRegistrationListener(this::onRegistrationChanged); +        onRegistrationChanged(inputConsumerController.isRegistered());      }      public void setTouchEnabled(boolean enabled) { @@ -239,6 +261,11 @@ public class PipTouchHandler {          updateMovementBounds(mIsMenuVisible);      } +    private void onRegistrationChanged(boolean isRegistered) { +        mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered +                ? new AccessibilityInteractionConnection() : null); +    } +      private boolean handleTouchEvent(MotionEvent ev) {          // Skip touch handling until we are bound to the controller          if (mPinnedStackController == null) { @@ -282,6 +309,31 @@ public class PipTouchHandler {                  mTouchState.reset();                  break;              } +            case MotionEvent.ACTION_HOVER_ENTER: +            case MotionEvent.ACTION_HOVER_MOVE: { +                if (!mSendingHoverAccessibilityEvents) { +                    AccessibilityEvent event = AccessibilityEvent.obtain( +                            AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); +                    AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); +                    event.setSource(info); +                    info.recycle(); +                    mAccessibilityManager.sendAccessibilityEvent(event); +                    mSendingHoverAccessibilityEvents = true; +                } +                break; +            } +            case MotionEvent.ACTION_HOVER_EXIT: { +                if (mSendingHoverAccessibilityEvents) { +                    AccessibilityEvent event = AccessibilityEvent.obtain( +                            AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); +                    AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); +                    event.setSource(info); +                    info.recycle(); +                    mAccessibilityManager.sendAccessibilityEvent(event); +                    mSendingHoverAccessibilityEvents = false; +                } +                break; +            }          }          return !mIsMenuVisible;      } @@ -572,4 +624,144 @@ public class PipTouchHandler {          mTouchState.dump(pw, innerPrefix);          mMotionHelper.dump(pw, innerPrefix);      } + +    private static AccessibilityNodeInfo obtainRootAccessibilityNodeInfo() { +        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); +        info.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, +                AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); +        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); +        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); +        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_MOVE_WINDOW); +        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); +        info.setImportantForAccessibility(true); +        info.setClickable(true); +        info.setVisibleToUser(true); +        return info; +    } + +    /** +     * Expose the touch actions to accessibility as if this object were a window with a single view. +     * That pseudo-view exposes all of the actions this object can perform. +     */ +    class AccessibilityInteractionConnection extends IAccessibilityInteractionConnection.Stub { +        static final long ACCESSIBILITY_NODE_ID = 1; +        List<AccessibilityNodeInfo> mAccessibilityNodeInfoList; + +        @Override +        public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, +                Region interactiveRegion, int interactionId, +                IAccessibilityInteractionConnectionCallback callback, int flags, +                int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) { +            try { +                callback.setFindAccessibilityNodeInfosResult( +                        (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) +                                ? getNodeList() : null, interactionId); +            } catch (RemoteException re) { +                    /* best effort - ignore */ +            } +        } + +        @Override +        public void performAccessibilityAction(long accessibilityNodeId, int action, +                Bundle arguments, int interactionId, +                IAccessibilityInteractionConnectionCallback callback, int flags, +                int interrogatingPid, long interrogatingTid) { +            // We only support one view. A request for anything else is invalid +            boolean result = false; +            if (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) { +                switch (action) { +                    case AccessibilityNodeInfo.ACTION_CLICK: +                        mHandler.post(mShowMenuRunnable); +                        result = true; +                        break; +                    case AccessibilityNodeInfo.ACTION_DISMISS: +                        mMotionHelper.dismissPip(); +                        result = true; +                        break; +                    case com.android.internal.R.id.accessibilityActionMoveWindow: +                        int newX = arguments.getInt( +                                AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_X); +                        int newY = arguments.getInt( +                                AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_Y); +                        Rect pipBounds = new Rect(); +                        pipBounds.set(mMotionHelper.getBounds()); +                        mTmpBounds.offsetTo(newX, newY); +                        mMotionHelper.movePip(mTmpBounds); +                        result = true; +                        break; +                    case AccessibilityNodeInfo.ACTION_EXPAND: +                        mMotionHelper.expandPip(); +                        result = true; +                        break; +                    default: +                        // Leave result as false +                } +            } +            try { +                callback.setPerformAccessibilityActionResult(result, interactionId); +            } catch (RemoteException re) { +                    /* best effort - ignore */ +            } +        } + +        @Override +        public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, +                String viewId, Region interactiveRegion, int interactionId, +                IAccessibilityInteractionConnectionCallback callback, int flags, +                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { +            // We have no view with a proper ID +            try { +                callback.setFindAccessibilityNodeInfoResult(null, interactionId); +            } catch (RemoteException re) { +                /* best effort - ignore */ +            } +        } + +        @Override +        public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, +                Region interactiveRegion, int interactionId, +                IAccessibilityInteractionConnectionCallback callback, int flags, +                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { +            // We have no view with text +            try { +                callback.setFindAccessibilityNodeInfoResult(null, interactionId); +            } catch (RemoteException re) { +                /* best effort - ignore */ +            } +        } + +        @Override +        public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion, +                int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, +                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { +            // We have no view that can take focus +            try { +                callback.setFindAccessibilityNodeInfoResult(null, interactionId); +            } catch (RemoteException re) { +                /* best effort - ignore */ +            } +        } + +        @Override +        public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion, +                int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, +                int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { +            // We have no view that can take focus +            try { +                callback.setFindAccessibilityNodeInfoResult(null, interactionId); +            } catch (RemoteException re) { +                /* best effort - ignore */ +            } +        } + +        private List<AccessibilityNodeInfo> getNodeList() { +            if (mAccessibilityNodeInfoList == null) { +                mAccessibilityNodeInfoList = new ArrayList<>(1); +            } +            AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); +            mAccessibilityNodeInfoList.clear(); +            mAccessibilityNodeInfoList.add(info); +            return mAccessibilityNodeInfoList; +        } +    }  } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 12332fb79059..47468aeaeb97 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -22,6 +22,7 @@ import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;  import static android.app.ActivityManager.StackId.HOME_STACK_ID;  import static android.app.ActivityManager.StackId.INVALID_STACK_ID;  import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;  import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;  import android.annotation.NonNull; @@ -1057,15 +1058,18 @@ public class SystemServicesProxy {      }      /** -     * Returns the window rect for the RecentsActivity, based on the dimensions of the home stack. +     * Returns the window rect for the RecentsActivity, based on the dimensions of the recents stack       */      public Rect getWindowRect() {          Rect windowRect = new Rect();          if (mIam == null) return windowRect;          try { -            // Use the home stack bounds -            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(HOME_STACK_ID); +            // Use the recents stack bounds, fallback to fullscreen stack if it is null +            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID); +            if (stackInfo == null) { +                stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID); +            }              if (stackInfo != null) {                  windowRect.set(stackInfo.bounds);              } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 6f59fe2001a1..97506e6df1c2 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -746,15 +746,18 @@ public class DividerView extends FrameLayout implements OnTouchListener,              if (mStableInsets.isEmpty()) {                  SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);              } +            mMinimizedSnapAlgorithm = null; +            mDockedStackMinimized = minimized; +            initializeSnapAlgorithm();              if (!mIsInMinimizeInteraction && minimized) {                  mIsInMinimizeInteraction = true;                  mDividerPositionBeforeMinimized = DockedDividerUtils.calculateMiddlePosition(                          isHorizontalDivision(), mStableInsets, mDisplayWidth, mDisplayHeight,                          mDividerSize); + +                int position = mMinimizedSnapAlgorithm.getMiddleTarget().position; +                resizeStack(position, position, mMinimizedSnapAlgorithm.getMiddleTarget());              } -            mMinimizedSnapAlgorithm = null; -            mDockedStackMinimized = minimized; -            initializeSnapAlgorithm();          }      } @@ -1140,7 +1143,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,                          && dockSideBottomRight(mDockSide))) {              return StackId.DOCKED_STACK_ID;          } else { -            return StackId.HOME_STACK_ID; +            return StackId.RECENTS_STACK_ID;          }      } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 44afe1dbf4df..e0d78063ffb8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -82,6 +82,7 @@ import android.view.InputDevice;  import android.view.KeyCharacterMap;  import android.view.KeyEvent;  import android.view.MagnificationSpec; +import android.view.View;  import android.view.WindowInfo;  import android.view.WindowManager;  import android.view.WindowManagerInternal; @@ -97,6 +98,7 @@ import android.view.accessibility.IAccessibilityManager;  import android.view.accessibility.IAccessibilityManagerClient;  import com.android.internal.R; +import com.android.internal.annotations.GuardedBy;  import com.android.internal.content.PackageMonitor;  import com.android.internal.os.SomeArgs;  import com.android.server.LocalServices; @@ -149,6 +151,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {      private static final String GET_WINDOW_TOKEN = "getWindowToken"; +    private static final String SET_PIP_ACTION_REPLACEMENT = +            "setPictureInPictureActionReplacingConnection"; +      private static final ComponentName sFakeAccessibilityServiceComponentName =              new ComponentName("foo.bar", "FakeService"); @@ -158,8 +163,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {      private static final int OWN_PROCESS_ID = android.os.Process.myPid(); -    private static final int WINDOW_ID_UNKNOWN = -1; -      // Each service has an ID. Also provide one for magnification gesture handling      public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0; @@ -220,6 +223,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {      private final SparseArray<AccessibilityConnectionWrapper> mGlobalInteractionConnections =              new SparseArray<>(); +    private AccessibilityConnectionWrapper mPictureInPictureActionReplacingConnection; +      private final SparseArray<IBinder> mGlobalWindowTokens = new SparseArray<>();      private final SparseArray<UserState> mUserStates = new SparseArray<>(); @@ -465,6 +470,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {          boolean dispatchEvent = false;          synchronized (mLock) { +            if (event.getWindowId() == +                AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID) { +                // The replacer window isn't shown to services. Move its events into the pip. +                AccessibilityWindowInfo pip = mSecurityPolicy.getPictureInPictureWindow(); +                if (pip != null) { +                    int pipId = pip.getId(); +                    event.setWindowId(pipId); +                    event.setSealed(true); +                    AccessibilityNodeInfo info = event.getSource(); +                    info.setSealed(false); +                    event.setSealed(false); +                    if (info != null) { +                        info.setSourceNodeId(info.getSourceNodeId(), pipId); +                        event.setSource(info); +                        info.recycle(); +                    } +                } +            } +              // We treat calls from a profile as if made by its parent as profiles              // share the accessibility state of the parent. The call below              // performs the current profile parent resolution.. @@ -694,6 +718,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {      }      @Override +    public void setPictureInPictureActionReplacingConnection( +            IAccessibilityInteractionConnection connection) throws RemoteException { +        mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA, +                SET_PIP_ACTION_REPLACEMENT); +        synchronized (mLock) { +            if (mPictureInPictureActionReplacingConnection != null) { +                mPictureInPictureActionReplacingConnection.unlinkToDeath(); +                mPictureInPictureActionReplacingConnection = null; +            } +            if (connection != null) { +                AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( +                        AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID, +                        connection, UserHandle.USER_ALL); +                mPictureInPictureActionReplacingConnection = wrapper; +                wrapper.linkToDeath(); +            } +            mSecurityPolicy.notifyWindowsChanged(); +        } +    } + +    @Override      public void registerUiTestAutomationService(IBinder owner,              IAccessibilityServiceClient serviceClient,              AccessibilityServiceInfo accessibilityServiceInfo, @@ -1293,9 +1338,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              return false;          } -        if (event.getWindowId() != WINDOW_ID_UNKNOWN && !event.isImportantForAccessibility() -                && (service.mFetchFlags -                        & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) { +        if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) +                && !event.isImportantForAccessibility() +                && (service.mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) +                        == 0) {              return false;          } @@ -2035,6 +2081,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {          }      } +    @GuardedBy("mLock")      private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {          IBinder windowToken = mGlobalWindowTokens.get(windowId);          if (windowToken == null) { @@ -2846,6 +2893,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              final int resolvedWindowId;              IAccessibilityInteractionConnection connection = null;              Region partialInteractiveRegion = Region.obtain(); +            MagnificationSpec spec;              synchronized (mLock) {                  mUsesAccessibilityCache = true;                  if (!isCalledForCurrentUserLocked()) { @@ -2867,10 +2915,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {                      partialInteractiveRegion.recycle();                      partialInteractiveRegion = null;                  } +                spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              }              final int interrogatingPid = Binder.getCallingPid(); +            callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, +                    interrogatingPid, interrogatingTid);              final long identityToken = Binder.clearCallingIdentity(); -            MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              try {                  connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName,                          partialInteractiveRegion, interactionId, callback, mFetchFlags, @@ -2898,6 +2948,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              final int resolvedWindowId;              IAccessibilityInteractionConnection connection = null;              Region partialInteractiveRegion = Region.obtain(); +            MagnificationSpec spec;              synchronized (mLock) {                  mUsesAccessibilityCache = true;                  if (!isCalledForCurrentUserLocked()) { @@ -2919,10 +2970,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {                      partialInteractiveRegion.recycle();                      partialInteractiveRegion = null;                  } +                spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              }              final int interrogatingPid = Binder.getCallingPid(); +            callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, +                    interrogatingPid, interrogatingTid);              final long identityToken = Binder.clearCallingIdentity(); -            MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              try {                  connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,                          partialInteractiveRegion, interactionId, callback, mFetchFlags, @@ -2950,6 +3003,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              final int resolvedWindowId;              IAccessibilityInteractionConnection connection = null;              Region partialInteractiveRegion = Region.obtain(); +            MagnificationSpec spec;              synchronized (mLock) {                  mUsesAccessibilityCache = true;                  if (!isCalledForCurrentUserLocked()) { @@ -2971,10 +3025,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {                      partialInteractiveRegion.recycle();                      partialInteractiveRegion = null;                  } +                spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              }              final int interrogatingPid = Binder.getCallingPid(); +            callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, +                    interrogatingPid, interrogatingTid);              final long identityToken = Binder.clearCallingIdentity(); -            MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              try {                  connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,                          partialInteractiveRegion, interactionId, callback, mFetchFlags | flags, @@ -3002,6 +3058,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              final int resolvedWindowId;              IAccessibilityInteractionConnection connection = null;              Region partialInteractiveRegion = Region.obtain(); +            MagnificationSpec spec;              synchronized (mLock) {                  if (!isCalledForCurrentUserLocked()) {                      return false; @@ -3023,10 +3080,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {                      partialInteractiveRegion.recycle();                      partialInteractiveRegion = null;                  } +                spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              }              final int interrogatingPid = Binder.getCallingPid(); +            callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, +                    interrogatingPid, interrogatingTid);              final long identityToken = Binder.clearCallingIdentity(); -            MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              try {                  connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion,                          interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, @@ -3054,6 +3113,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              final int resolvedWindowId;              IAccessibilityInteractionConnection connection = null;              Region partialInteractiveRegion = Region.obtain(); +            MagnificationSpec spec;              synchronized (mLock) {                  if (!isCalledForCurrentUserLocked()) {                      return false; @@ -3074,10 +3134,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {                      partialInteractiveRegion.recycle();                      partialInteractiveRegion = null;                  } +                spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              }              final int interrogatingPid = Binder.getCallingPid(); +            callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, +                    interrogatingPid, interrogatingTid);              final long identityToken = Binder.clearCallingIdentity(); -            MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);              try {                  connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion,                          interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, @@ -3149,6 +3211,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {                      if (connection == null) {                          return false;                      } +                    AccessibilityWindowInfo windowInfo = +                            mSecurityPolicy.findWindowById(resolvedWindowId); +                    if ((windowInfo != null) && windowInfo.inPictureInPicture()) { +                        boolean isA11yFocusAction = +                                (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) +                                || (action == +                                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); +                        if ((mPictureInPictureActionReplacingConnection != null) +                                && !isA11yFocusAction) { +                            connection = mPictureInPictureActionReplacingConnection.mConnection; +                        } +                    }                  }              }              final int interrogatingPid = Binder.getCallingPid(); @@ -3562,7 +3636,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {                  if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) {                      event.setConnectionId(mId);                  } else { -                    event.setSource(null); +                    event.setSource((View) null);                  }                  event.setSealed(true);              } @@ -3790,17 +3864,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {          }          private int resolveAccessibilityWindowIdLocked(int accessibilityWindowId) { -            if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) { +            if (accessibilityWindowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {                  return mSecurityPolicy.getActiveWindowId();              }              return accessibilityWindowId;          }          private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) { -            if (windowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) { +            if (windowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {                  return mSecurityPolicy.mActiveWindowId;              } -            if (windowId == AccessibilityNodeInfo.ANY_WINDOW_ID) { +            if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {                  if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {                      return mSecurityPolicy.mFocusedWindowId;                  } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) { @@ -3810,6 +3884,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              return windowId;          } +        private IAccessibilityInteractionConnectionCallback replaceCallbackIfNeeded( +                IAccessibilityInteractionConnectionCallback originalCallback, +                int resolvedWindowId, int interactionId, int interrogatingPid, +                long interrogatingTid) { +            AccessibilityWindowInfo windowInfo = mSecurityPolicy.findWindowById(resolvedWindowId); +            if ((windowInfo == null) || !windowInfo.inPictureInPicture() +                    || (mPictureInPictureActionReplacingConnection == null)) { +                return originalCallback; +            } +            return new ActionReplacingCallback(originalCallback, +                    mPictureInPictureActionReplacingConnection.mConnection, interactionId, +                    interrogatingPid, interrogatingTid); +        } +          private final class InvocationHandler extends Handler {              public static final int MSG_ON_GESTURE = 1;              public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2; @@ -3960,6 +4048,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              reportedWindow.setBoundsInScreen(window.boundsInScreen);              reportedWindow.setTitle(window.title);              reportedWindow.setAnchorId(window.accessibilityIdOfAnchor); +            reportedWindow.setPictureInPicture(window.inPictureInPicture);              final int parentId = findWindowIdLocked(window.parentToken);              if (parentId >= 0) { @@ -4157,7 +4246,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED              | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY; +        // In Z order          public List<AccessibilityWindowInfo> mWindows; +        public SparseArray<AccessibilityWindowInfo> mWindowsById = new SparseArray<>();          public int mActiveWindowId = INVALID_WINDOW_ID;          public int mFocusedWindowId = INVALID_WINDOW_ID; @@ -4217,6 +4308,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              for (int i = oldWindowCount - 1; i >= 0; i--) {                  mWindows.remove(i).recycle();              } +            mWindowsById.clear();              mFocusedWindowId = INVALID_WINDOW_ID;              if (!mTouchInteractionInProgress) { @@ -4245,6 +4337,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {                          }                      }                      mWindows.add(window); +                    mWindowsById.put(windowId, window);                  }                  if (mTouchInteractionInProgress && activeWindowGone) { @@ -4306,7 +4399,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {          public void updateEventSourceLocked(AccessibilityEvent event) {              if ((event.getEventType() & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) { -                event.setSource(null); +                event.setSource((View) null);              }          } @@ -4446,7 +4539,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {              }          } -        private void notifyWindowsChanged() { +        public void notifyWindowsChanged() {              if (mWindowsForAccessibilityCallback == null) {                  return;              } @@ -4560,11 +4653,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {          }          private AccessibilityWindowInfo findWindowById(int windowId) { +            return mWindowsById.get(windowId); +        } + +        private AccessibilityWindowInfo getPictureInPictureWindow() {              if (mWindows != null) {                  final int windowCount = mWindows.size();                  for (int i = 0; i < windowCount; i++) {                      AccessibilityWindowInfo window = mWindows.get(i); -                    if (window.getId() == windowId) { +                    if (window.inPictureInPicture()) {                          return window;                      }                  } diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java new file mode 100644 index 000000000000..0e30fb28f3d7 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import android.os.Binder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.MagnificationSpec; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import android.view.accessibility.IAccessibilityInteractionConnection; +import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import com.android.internal.annotations.GuardedBy; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * If we are stripping and/or replacing the actions from a window, we need to intercept the + * nodes heading back to the service and swap out the actions. + */ +public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub { +    private static final boolean DEBUG = false; +    private static final String LOG_TAG = "ActionReplacingCallback"; + +    private final IAccessibilityInteractionConnectionCallback mServiceCallback; +    private final IAccessibilityInteractionConnection mConnectionWithReplacementActions; +    private final int mInteractionId; +    private final Object mLock = new Object(); + +    @GuardedBy("mLock") +    List<AccessibilityNodeInfo> mNodesWithReplacementActions; + +    @GuardedBy("mLock") +    List<AccessibilityNodeInfo> mNodesFromOriginalWindow; + +    @GuardedBy("mLock") +    AccessibilityNodeInfo mNodeFromOriginalWindow; + +    // Keep track of whether or not we've been called back for a single node +    @GuardedBy("mLock") +    boolean mSingleNodeCallbackHappened; + +    // Keep track of whether or not we've been called back for multiple node +    @GuardedBy("mLock") +    boolean mMultiNodeCallbackHappened; + +    // We shouldn't get any more callbacks after we've called back the original service, but +    // keep track to make sure we catch such strange things +    @GuardedBy("mLock") +    boolean mDone; + +    public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback, +            IAccessibilityInteractionConnection connectionWithReplacementActions, +            int interactionId, int interrogatingPid, long interrogatingTid) { +        mServiceCallback = serviceCallback; +        mConnectionWithReplacementActions = connectionWithReplacementActions; +        mInteractionId = interactionId; + +        // Request the root node of the replacing window +        final long identityToken = Binder.clearCallingIdentity(); +        try { +            mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId( +                    AccessibilityNodeInfo.ROOT_NODE_ID, null, interactionId + 1, this, 0, +                    interrogatingPid, interrogatingTid, null, null); +        } catch (RemoteException re) { +            if (DEBUG) { +                Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); +            } +            // Pretend we already got a (null) list of replacement nodes +            mMultiNodeCallbackHappened = true; +        } finally { +            Binder.restoreCallingIdentity(identityToken); +        } +    } + +    @Override +    public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) { +        boolean readyForCallback; +        synchronized(mLock) { +            if (interactionId == mInteractionId) { +                mNodeFromOriginalWindow = info; +            } else { +                Slog.e(LOG_TAG, "Callback with unexpected interactionId"); +                throw new RuntimeException("Callback with unexpected interactionId"); // Remove +            } + +            mSingleNodeCallbackHappened = true; +            readyForCallback = mMultiNodeCallbackHappened; +        } +        if (readyForCallback) { +            replaceInfoActionsAndCallService(); +        } +    } + +    @Override +    public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, +            int interactionId) { +        boolean callbackForSingleNode; +        boolean callbackForMultipleNodes; +        synchronized(mLock) { +            if (interactionId == mInteractionId) { +                mNodesFromOriginalWindow = infos; +            } else if (interactionId == mInteractionId + 1) { +                mNodesWithReplacementActions = infos; +            } else { +                Slog.e(LOG_TAG, "Callback with unexpected interactionId"); +                throw new RuntimeException("Callback with unexpected interactionId"); // Remove +            } +            callbackForSingleNode = mSingleNodeCallbackHappened; +            callbackForMultipleNodes = mMultiNodeCallbackHappened; +            mMultiNodeCallbackHappened = true; +        } +        if (callbackForSingleNode) { +            replaceInfoActionsAndCallService(); +        } +        if (callbackForMultipleNodes) { +            replaceInfosActionsAndCallService(); +        } +    } + +    @Override +    public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) +            throws RemoteException { +        // There's no reason to use this class when performing actions. Do something reasonable. +        mServiceCallback.setPerformAccessibilityActionResult(succeeded, interactionId); +    } + +    private void replaceInfoActionsAndCallService() { +        final AccessibilityNodeInfo nodeToReturn; +        synchronized (mLock) { +            if (mDone) { +                if (DEBUG) { +                    Slog.e(LOG_TAG, "Extra callback"); +                } +                throw new RuntimeException("Extra callback"); // Replace with return before submit +            } +            if (mNodeFromOriginalWindow != null) { +                replaceActionsOnInfoLocked(mNodeFromOriginalWindow); +            } +            recycleReplaceActionNodesLocked(); +            nodeToReturn = mNodeFromOriginalWindow; +            mDone = true; +        } +        try { +            mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId); +        } catch (RemoteException re) { +            if (DEBUG) { +                Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult"); +            } +        } +    } + +    private void replaceInfosActionsAndCallService() { +        final List<AccessibilityNodeInfo> nodesToReturn; +        synchronized (mLock) { +            if (mDone) { +                if (DEBUG) { +                    Slog.e(LOG_TAG, "Extra callback"); +                } +                throw new RuntimeException("Extra callback"); // Replace with return before submit +            } +            if (mNodesFromOriginalWindow != null) { +                for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) { +                    replaceActionsOnInfoLocked(mNodesFromOriginalWindow.get(i)); +                } +            } +            recycleReplaceActionNodesLocked(); +            nodesToReturn = mNodesFromOriginalWindow; +            mDone = true; +        } +        try { +            mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId); +        } catch (RemoteException re) { +            if (DEBUG) { +                Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); +            } +        } +    } + +    @GuardedBy("mLock") +    private void replaceActionsOnInfoLocked(AccessibilityNodeInfo info) { +        info.removeAllActions(); +        // We currently only replace actions for the root node +        if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) +                && mNodesWithReplacementActions != null) { +            // This list should always contain a single node with the root ID +            for (int i = 0; i < mNodesWithReplacementActions.size(); i++) { +                AccessibilityNodeInfo nodeWithReplacementActions = +                        mNodesWithReplacementActions.get(i); +                if (nodeWithReplacementActions.getSourceNodeId() +                        == AccessibilityNodeInfo.ROOT_NODE_ID) { +                    List<AccessibilityAction> actions = nodeWithReplacementActions.getActionList(); +                    if (actions != null) { +                        for (int j = 0; j < actions.size(); j++) { +                            info.addAction(actions.get(j)); +                        } +                        // The PIP needs to be able to take accessibility focus +                        info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS); +                        info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS); +                    } +                } +            } +        } +    } + +    @GuardedBy("mLock") +    private void recycleReplaceActionNodesLocked() { +        for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) { +            AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i); +            nodeWithReplacementAction.recycle(); +        } +        mNodesWithReplacementActions = null; +    } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 50882e1c55f8..fef7b513b7bc 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -127,10 +127,11 @@ import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.FO  import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;  import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;  import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; +import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_ONLY; +import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;  import static com.android.server.am.ActivityStackSupervisor.ON_TOP;  import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;  import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; -import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS;  import static com.android.server.am.TaskRecord.INVALID_TASK_ID;  import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;  import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; @@ -9654,7 +9655,7 @@ public class ActivityManagerService extends IActivityManager.Stub              enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,                      "getTaskThumbnail()");              final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked( -                    id, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +                    id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);              if (tr != null) {                  return tr.getTaskThumbnailLocked();              } @@ -9667,8 +9668,8 @@ public class ActivityManagerService extends IActivityManager.Stub          synchronized (this) {              enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,                      "getTaskDescription()"); -            final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked( -                    id, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +            final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id, +                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);              if (tr != null) {                  return tr.lastTaskDescription;              } @@ -9780,7 +9781,7 @@ public class ActivityManagerService extends IActivityManager.Stub      public void setTaskResizeable(int taskId, int resizeableMode) {          synchronized (this) {              final TaskRecord task = mStackSupervisor.anyTaskForIdLocked( -                    taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +                    taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);              if (task == null) {                  Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found");                  return; @@ -9842,8 +9843,8 @@ public class ActivityManagerService extends IActivityManager.Stub          Rect rect = new Rect();          try {              synchronized (this) { -                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked( -                        taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, +                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);                  if (task == null) {                      Slog.w(TAG, "getTaskBounds: taskId=" + taskId + " not found");                      return rect; @@ -9874,8 +9875,8 @@ public class ActivityManagerService extends IActivityManager.Stub          final long ident = Binder.clearCallingIdentity();          try {              synchronized (this) { -                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked( -                        taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, +                        MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);                  if (task == null) {                      Slog.w(TAG, "cancelTaskWindowTransition: taskId=" + taskId + " not found");                      return; @@ -9893,8 +9894,8 @@ public class ActivityManagerService extends IActivityManager.Stub          final long ident = Binder.clearCallingIdentity();          try {              synchronized (this) { -                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked( -                        taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, +                        MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);                  if (task == null) {                      Slog.w(TAG, "cancelTaskThumbnailTransition: taskId=" + taskId + " not found");                      return; @@ -9913,8 +9914,8 @@ public class ActivityManagerService extends IActivityManager.Stub          try {              final TaskRecord task;              synchronized (this) { -                task = mStackSupervisor.anyTaskForIdLocked( -                        taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +                task = mStackSupervisor.anyTaskForIdLocked(taskId, +                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);                  if (task == null) {                      Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");                      return null; @@ -19900,14 +19901,11 @@ public class ActivityManagerService extends IActivityManager.Stub      /** Helper method that requests bounds from WM and applies them to stack. */      private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {          final Rect newStackBounds = new Rect(); -        final Rect newTempTaskBounds = new Rect(); -        mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(newStackBounds, -                newTempTaskBounds); +        mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(newStackBounds);          mStackSupervisor.resizeStackLocked(                  stackId, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */, -                !newTempTaskBounds.isEmpty() ? newTempTaskBounds : null /* tempTaskBounds */, -                null /* tempTaskInsetBounds */, false /* preserveWindows */, -                false /* allowResizeInDockedMode */, deferResume); +                null /* tempTaskBounds */, null /* tempTaskInsetBounds */, +                false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);      }      /** diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 15a9efc9ac57..2d91cadbd586 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -120,6 +120,7 @@ import com.android.server.am.ActivityManagerService.ItemMatcher;  import com.android.server.am.ActivityStackSupervisor.ActivityContainer;  import com.android.server.wm.StackWindowController;  import com.android.server.wm.StackWindowListener; +import com.android.server.wm.TaskStack;  import com.android.server.wm.WindowManagerService;  import java.io.FileDescriptor; @@ -544,10 +545,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai          mActivityContainer.mActivityDisplay.mDisplay.getSize(out);      } -    void getStackDockedModeBounds(Rect outBounds, Rect outTempBounds, Rect outTempInsetBounds, -            boolean ignoreVisibility) { -        mWindowContainerController.getStackDockedModeBounds(outBounds, outTempBounds, -                outTempInsetBounds, ignoreVisibility); +    /** +     * @see ActivityStack.getStackDockedModeBounds(Rect, Rect, Rect, boolean) +     */ +    void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds, +            Rect outTempTaskBounds, boolean ignoreVisibility) { +        mWindowContainerController.getStackDockedModeBounds(currentTempTaskBounds, +                outStackBounds, outTempTaskBounds, ignoreVisibility);      }      void prepareFreezingTaskBounds() { @@ -562,8 +566,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai          outBounds.setEmpty();      } -    void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) { -        mWindowContainerController.getBoundsForNewConfiguration(outBounds, outTempBounds); +    void getBoundsForNewConfiguration(Rect outBounds) { +        mWindowContainerController.getBoundsForNewConfiguration(outBounds);      }      void positionChildWindowContainerAtTop(TaskRecord child) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 59f58d7e54e0..2ae815e305c1 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -21,8 +21,6 @@ import static android.Manifest.permission.START_TASKS_FROM_RECENTS;  import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;  import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;  import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; -import static android.app.ActivityManager.RESIZE_MODE_FORCED; -import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;  import static android.app.ActivityManager.START_TASK_TO_FRONT;  import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;  import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID; @@ -97,6 +95,7 @@ import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS  import static java.lang.Integer.MAX_VALUE;  import android.Manifest; +import android.annotation.IntDef;  import android.annotation.NonNull;  import android.annotation.UserIdInt;  import android.app.Activity; @@ -105,7 +104,6 @@ import android.app.ActivityManager.RunningTaskInfo;  import android.app.ActivityManager.StackId;  import android.app.ActivityManager.StackInfo;  import android.app.ActivityOptions; -import android.app.AppGlobals;  import android.app.AppOpsManager;  import android.app.IActivityContainerCallback;  import android.app.ProfilerInfo; @@ -179,10 +177,11 @@ import com.android.server.wm.WindowManagerService;  import java.io.FileDescriptor;  import java.io.IOException;  import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy;  import java.util.ArrayList;  import java.util.Arrays;  import java.util.List; -import java.util.Objects;  import java.util.Set;  public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener { @@ -248,15 +247,31 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D      // Force the focus to change to the stack we are moving a task to..      static final boolean FORCE_FOCUS = true; -    // Restore task from the saved recents if it can't be found in any live stack. -    static final boolean RESTORE_FROM_RECENTS = true; -      // Don't execute any calls to resume.      static final boolean DEFER_RESUME = true;      // Used to indicate that a task is removed it should also be removed from recents.      static final boolean REMOVE_FROM_RECENTS = true; +    /** +     * The modes which affect which tasks are returned when calling +     * {@link ActivityStackSupervisor#anyTaskForIdLocked(int)}. +     */ +    @Retention(RetentionPolicy.SOURCE) +    @IntDef({ +            MATCH_TASK_IN_STACKS_ONLY, +            MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, +            MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE +    }) +    public @interface AnyTaskForIdMatchTaskMode {} +    // Match only tasks in the current stacks +    static final int MATCH_TASK_IN_STACKS_ONLY = 0; +    // Match either tasks in the current stacks, or in the recent tasks if not found in the stacks +    static final int MATCH_TASK_IN_STACKS_OR_RECENT_TASKS = 1; +    // Match either tasks in the current stacks, or in the recent tasks, restoring it to the +    // provided stack id +    static final int MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE = 2; +      // Activity actions an app cannot start if it uses a permission which is not granted.      private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION =              new ArrayMap<>(); @@ -713,18 +728,26 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D      }      TaskRecord anyTaskForIdLocked(int id) { -        return anyTaskForIdLocked(id, RESTORE_FROM_RECENTS, INVALID_STACK_ID); +        return anyTaskForIdLocked(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, +                INVALID_STACK_ID);      }      /**       * Returns a {@link TaskRecord} for the input id if available. Null otherwise.       * @param id Id of the task we would like returned. -     * @param restoreFromRecents If the id was not in the active list, but was found in recents, -     *                           restore the task from recents to the active list. +     * @param matchMode The mode to match the given task id in.       * @param stackId The stack to restore the task to (default launch stack will be used if -     *                stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}). +     *                stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}). Only +     *                valid if the matchMode is +     *                {@link #MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE}.       */ -    TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents, int stackId) { +    TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode, int stackId) { +        // If there is a stack id set, ensure that we are attempting to actually restore a task +        if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE && +                stackId != INVALID_STACK_ID) { +            throw new IllegalArgumentException("Should not specify stackId for non-restore lookup"); +        } +          int numDisplays = mActivityDisplays.size();          for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {              ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; @@ -737,18 +760,23 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D              }          } -        // Don't give up! Look in recents. -        if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents"); -        TaskRecord task = mRecentTasks.taskForIdLocked(id); -        if (task == null) { -            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents"); +        // If we are matching stack tasks only, return now +        if (matchMode == MATCH_TASK_IN_STACKS_ONLY) {              return null;          } -        if (!restoreFromRecents) { +        // Otherwise, check the recent tasks and return if we find it there and we are not restoring +        // the task from recents +        if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents"); +        TaskRecord task = mRecentTasks.taskForIdLocked(id); +        if (matchMode == MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) { +            if (DEBUG_RECENTS && task == null) { +                Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents"); +            }              return task;          } +        // Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE          if (!restoreRecentTaskLocked(task, stackId)) {              if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,                      "Couldn't restore task id=" + id + " found in recents"); @@ -844,7 +872,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D          // was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.          int candidateTaskId = nextTaskIdForUser(currentTaskId, userId);          while (mRecentTasks.taskIdTakenForUserLocked(candidateTaskId, userId) -                || anyTaskForIdLocked(candidateTaskId, !RESTORE_FROM_RECENTS, +                || anyTaskForIdLocked(candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,                          INVALID_STACK_ID) != null) {              candidateTaskId = nextTaskIdForUser(candidateTaskId, userId);              if (candidateTaskId == currentTaskId) { @@ -2249,7 +2277,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D          for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {              final int taskId = mResizingTasksDuringAnimation.valueAt(i);              final TaskRecord task = -                    anyTaskForIdLocked(taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +                    anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);              if (task != null) {                  task.setTaskDockedResizing(false);              } @@ -2375,19 +2403,19 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D                  // static stacks need to be adjusted so they don't overlap with the docked stack.                  // We get the bounds to use from window manager which has been adjusted for any                  // screen controls and is also the same for all stacks. -                final Rect tempOtherTaskRect = new Rect(); -                final Rect tempOtherTaskInsetRect = new Rect(); +                final Rect otherTaskRect = new Rect();                  for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {                      final ActivityStack current = getStack(i);                      if (current != null && StackId.isResizeableByDockedStack(i)) { -                        current.getStackDockedModeBounds(tempRect, tempOtherTaskRect, -                                tempOtherTaskInsetRect, true /* ignoreVisibility */); +                        current.getStackDockedModeBounds( +                                tempOtherTaskBounds /* currentTempTaskBounds */, +                                tempRect /* outStackBounds */, +                                otherTaskRect /* outTempTaskBounds */, true /* ignoreVisibility */); +                          resizeStackLocked(i, tempRect, -                                !tempOtherTaskRect.isEmpty() ? tempOtherTaskRect : -                                        tempOtherTaskBounds, -                                !tempOtherTaskInsetRect.isEmpty() ? tempOtherTaskInsetRect : -                                        tempOtherTaskInsetBounds, -                                preserveWindows, true /* allowResizeInDockedMode */, deferResume); +                                !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds, +                                tempOtherTaskInsetBounds, preserveWindows, +                                true /* allowResizeInDockedMode */, deferResume);                      }                  }              } @@ -2493,7 +2521,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D       * @return Returns true if the given task was found and removed.       */      boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) { -        final TaskRecord tr = anyTaskForIdLocked(taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); +        final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, +                INVALID_STACK_ID);          if (tr != null) {              tr.removeTaskActivitiesLocked();              cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents); @@ -4784,7 +4813,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D              mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);          } -        task = anyTaskForIdLocked(taskId, RESTORE_FROM_RECENTS, launchStackId); +        task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, +                launchStackId);          if (task == null) {              continueUpdateBounds(HOME_STACK_ID);              mWindowManager.executeAppTransition(); diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index 9dde39e6e9ce..9deabb3d6de9 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -55,6 +55,9 @@ import java.util.Comparator;  import java.util.List;  import static android.app.ActivityManager.StackId.HOME_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; + +import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;  public class TaskPersister {      static final String TAG = "TaskPersister"; @@ -452,7 +455,8 @@ public class TaskPersister {                                  final int taskId = task.taskId;                                  if (mStackSupervisor.anyTaskForIdLocked(taskId, -                                        /* restoreFromRecents= */ false, HOME_STACK_ID) != null) { +                                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, +                                        INVALID_STACK_ID) != null) {                                      // Should not happen.                                      Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");                                  } else if (userId != task.userId) { diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index aa8e37742767..fc377973221b 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -109,7 +109,6 @@ public class DockedStackDividerController implements DimLayerUser {      private final Rect mTmpRect2 = new Rect();      private final Rect mTmpRect3 = new Rect();      private final Rect mLastRect = new Rect(); -    private final Rect mMiddlePositionDockedStackRect = new Rect();      private boolean mLastVisibility = false;      private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners              = new RemoteCallbackList<>(); @@ -186,16 +185,6 @@ public class DockedStackDividerController implements DimLayerUser {          return (int) (minWidth / mDisplayContent.getDisplayMetrics().density);      } -    /** -     * The middlePositionDockedStackRect is half the screen area that sits at the top (portrait) or -     * left (landscape). -     * -     * @return fixed rect for temp stack -     */ -    Rect getMiddlePositionDockedStackRect() { -        return mMinimizedDock && isHomeStackResizable() ? mMiddlePositionDockedStackRect : null; -    } -      void getHomeStackBoundsInDockedMode(Rect outBounds) {          final DisplayInfo di = mDisplayContent.getDisplayInfo();          mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, @@ -264,29 +253,8 @@ public class DockedStackDividerController implements DimLayerUser {          initSnapAlgorithmForRotations();      } -    /** -     * Calculates the constant rects {@link mMiddlePositionDockedStackRect} based on orientation, -     * stable insets and display size. -     */ -    private void updateConstantRects() { -        final DisplayInfo di = mDisplayContent.getDisplayInfo(); -        mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, -                mTmpRect); -        int dividerSize = mDividerWindowWidth - 2 * mDividerInsets; -        Configuration configuration = mDisplayContent.getConfiguration(); -        boolean isHorizontal = configuration.orientation == Configuration.ORIENTATION_PORTRAIT; -        int middlePosition = DockedDividerUtils.calculateMiddlePosition(isHorizontal, mTmpRect, -                di.logicalWidth, di.logicalHeight, dividerSize); -        if (isHorizontal) { -            mMiddlePositionDockedStackRect.set(0, 0, di.logicalWidth, middlePosition); -        } else { -            mMiddlePositionDockedStackRect.set(0, 0, middlePosition, di.logicalHeight); -        } -    } -      void onConfigurationChanged() {          loadDimens(); -        updateConstantRects();      }      boolean isResizing() { diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java index b0e115bce451..8186d30a55ff 100644 --- a/services/core/java/com/android/server/wm/StackWindowController.java +++ b/services/core/java/com/android/server/wm/StackWindowController.java @@ -199,17 +199,19 @@ public class StackWindowController          }      } -    public void getStackDockedModeBounds(Rect outBounds, Rect outTempBounds, -            Rect outTempInsetBounds, boolean ignoreVisibility) { +    /** +     * @see TaskStack.getStackDockedModeBoundsLocked(Rect, Rect, Rect, boolean) +     */ +   public void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds, +           Rect outTempTaskBounds, boolean ignoreVisibility) {          synchronized (mWindowMap) {              if (mContainer != null) { -                mContainer.getStackDockedModeBoundsLocked(outBounds, outTempBounds, -                        outTempInsetBounds, ignoreVisibility); +                mContainer.getStackDockedModeBoundsLocked(currentTempTaskBounds, outStackBounds, +                        outTempTaskBounds, ignoreVisibility);                  return;              } -            outBounds.setEmpty(); -            outTempBounds.setEmpty(); -            outTempInsetBounds.setEmpty(); +            outStackBounds.setEmpty(); +            outTempTaskBounds.setEmpty();          }      } @@ -241,9 +243,9 @@ public class StackWindowController          }      } -    public void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) { +    public void getBoundsForNewConfiguration(Rect outBounds) {          synchronized(mWindowMap) { -            mContainer.getBoundsForNewConfiguration(outBounds, outTempBounds); +            mContainer.getBoundsForNewConfiguration(outBounds);          }      } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 7588b71dced7..442cd54a2773 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -429,18 +429,9 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye          return true;      } -    void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) { +    void getBoundsForNewConfiguration(Rect outBounds) {          outBounds.set(mBoundsAfterRotation);          mBoundsAfterRotation.setEmpty(); -        final DockedStackDividerController controller = getDisplayContent() -                .mDividerControllerLocked; -        if (mStackId == DOCKED_STACK_ID) { -            final Rect dockedStackRect = controller.getMiddlePositionDockedStackRect(); - -            if (dockedStackRect != null) { -                outTempBounds.set(dockedStackRect); -            } -        }      }      /** @@ -686,21 +677,42 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye          super.onDisplayChanged(dc);      } -    void getStackDockedModeBoundsLocked(Rect outBounds, Rect outTempBounds, -            Rect outTempInsetBounds, boolean ignoreVisibility) { +    /** +     * Determines the stack and task bounds of the other stack when in docked mode. The current task +     * bounds is passed in but depending on the stack, the task and stack must match. Only in +     * minimized mode with resizable launcher, the other stack ignores calculating the stack bounds +     * and uses the task bounds passed in as the stack and task bounds, otherwise the stack bounds +     * is calculated and is also used for its task bounds. +     * If any of the out bounds are empty, it represents default bounds +     * +     * @param currentTempTaskBounds the current task bounds of the other stack +     * @param outStackBounds the calculated stack bounds of the other stack +     * @param outTempTaskBounds the calculated task bounds of the other stack +     * @param ignoreVisibility ignore visibility in getting the stack bounds +     */ +    void getStackDockedModeBoundsLocked(Rect currentTempTaskBounds, Rect outStackBounds, +            Rect outTempTaskBounds, boolean ignoreVisibility) { +        outTempTaskBounds.setEmpty(); + +        // When the home stack is resizable, should always have the same stack and task bounds          if (mStackId == HOME_STACK_ID && findHomeTask().isResizeable()) {              // Calculate the home stack bounds when in docked mode              getDisplayContent().mDividerControllerLocked -                    .getHomeStackBoundsInDockedMode(outTempBounds); -            outTempInsetBounds.set(outTempBounds); -        } else { -            outTempBounds.setEmpty(); -            outTempInsetBounds.setEmpty(); +                    .getHomeStackBoundsInDockedMode(outStackBounds); +            outTempTaskBounds.set(outStackBounds); +            return; +        } + +        // When minimized state, the stack bounds for all non-home and docked stack bounds should +        // match the passed task bounds +        if (isMinimizedDockAndHomeStackResizable() && currentTempTaskBounds != null) { +            outStackBounds.set(currentTempTaskBounds); +            return;          }          if ((mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId))                  || mDisplayContent == null) { -            outBounds.set(mBounds); +            outStackBounds.set(mBounds);              return;          } @@ -714,7 +726,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye              // The docked stack is being dismissed, but we caught before it finished being              // dismissed. In that case we want to treat it as if it is not occupying any space and              // let others occupy the whole display. -            mDisplayContent.getLogicalDisplayRect(outBounds); +            mDisplayContent.getLogicalDisplayRect(outStackBounds);              return;          } @@ -722,14 +734,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye          if (dockedSide == DOCKED_INVALID) {              // Not sure how you got here...Only thing we can do is return current bounds.              Slog.e(TAG_WM, "Failed to get valid docked side for docked stack=" + dockedStack); -            outBounds.set(mBounds); +            outStackBounds.set(mBounds);              return;          }          mDisplayContent.getLogicalDisplayRect(mTmpRect);          dockedStack.getRawBounds(mTmpRect2);          final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT; -        getStackDockedModeBounds(mTmpRect, outBounds, mStackId, mTmpRect2, +        getStackDockedModeBounds(mTmpRect, outStackBounds, mStackId, mTmpRect2,                  mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);      } @@ -812,8 +824,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye          final Rect bounds = new Rect();          final Rect tempBounds = new Rect(); -        final Rect tempInsetBounds = new Rect(); -        getStackDockedModeBoundsLocked(bounds, tempBounds, tempInsetBounds, true /*ignoreVisibility*/); +        getStackDockedModeBoundsLocked(null /* currentTempTaskBounds */, bounds, tempBounds, +                true /*ignoreVisibility*/);          getController().requestResize(bounds);      } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 48060686a1de..7825903db4bd 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -74,7 +74,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER; @@ -82,7 +81,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -3839,6 +3837,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP          windowInfo.title = mAttrs.accessibilityTitle;          windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;          windowInfo.focused = isFocused(); +        Task task = getTask(); +        windowInfo.inPictureInPicture = (task != null) && task.inPinnedWorkspace();          if (mIsChildWindow) {              windowInfo.parentToken = getParentWindow().mClient.asBinder(); diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java index ad64e4e6e64d..e6e2cb3d99c9 100644 --- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java +++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java @@ -141,7 +141,7 @@ public class CompanionDeviceManagerService extends SystemService {      }      private ServiceConnection getServiceConnection( -            final AssociationRequest<?> request, +            final AssociationRequest request,              final IFindDeviceCallback findDeviceCallback,              final String callingPackage) {          return new ServiceConnection() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java new file mode 100644 index 000000000000..8afe85389548 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import android.graphics.Region; +import android.os.RemoteException; +import android.view.MagnificationSpec; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import android.view.accessibility.AccessibilityWindowInfo; +import android.view.accessibility.IAccessibilityInteractionConnection; +import android.view.accessibility.IAccessibilityInteractionConnectionCallback; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS; +import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS; +import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK; +import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE; +import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND; +import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CONTEXT_CLICK; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +/** + * Tests for ActionReplacingCallback + */ +public class ActionReplacingCallbackTest { +    private static final int INTERACTION_ID = 0xBEEF; +    private static final int INTERROGATING_PID = 0xFEED; +    private static final int APP_WINDOW_ID = 0xACE; +    private static final int NON_ROOT_NODE_ID = 0xAAAA5555; +    private static final long INTERROGATING_TID = 0x1234FACE; + +    private static final AccessibilityAction[] ACTIONS_FROM_REPLACER = +            {ACTION_CLICK, ACTION_EXPAND}; +    private static final AccessibilityAction[] A11Y_FOCUS_ACTIONS = +            {ACTION_ACCESSIBILITY_FOCUS, ACTION_CLEAR_ACCESSIBILITY_FOCUS}; +    // We expect both the replacer actions and a11y focus actions to appear +    private static final AccessibilityAction[] REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE = +            {ACTION_CLICK, ACTION_EXPAND, ACTION_ACCESSIBILITY_FOCUS, +                    ACTION_CLEAR_ACCESSIBILITY_FOCUS}; + +    @Mock IAccessibilityInteractionConnectionCallback mMockServiceCallback; +    @Mock IAccessibilityInteractionConnection mMockReplacerConnection; + +    @Captor private ArgumentCaptor<Integer> mInteractionIdCaptor; +    @Captor private ArgumentCaptor<AccessibilityNodeInfo> mInfoCaptor; +    @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mInfoListCaptor; + +    private ActionReplacingCallback mActionReplacingCallback; +    private int mReplacerInteractionId; + +    @Before +    public void setUp() throws RemoteException { +        initMocks(this); +        mActionReplacingCallback = new ActionReplacingCallback( +                mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID, +                INTERROGATING_TID); +        verify(mMockReplacerConnection).findAccessibilityNodeInfoByAccessibilityId( +                eq(AccessibilityNodeInfo.ROOT_NODE_ID), (Region) anyObject(), +                mInteractionIdCaptor.capture(), eq(mActionReplacingCallback), eq(0), +                eq(INTERROGATING_PID), eq(INTERROGATING_TID), (MagnificationSpec) anyObject(), +                eq(null)); +        mReplacerInteractionId = mInteractionIdCaptor.getValue().intValue(); +    } + +    @Test +    public void testConstructor_registersToGetRootNodeOfActionReplacer() throws RemoteException { +        assertNotEquals(INTERACTION_ID, mReplacerInteractionId); +        verifyNoMoreInteractions(mMockServiceCallback); +    } + +    @Test +    public void testCallbacks_singleRootNodeThenReplacer_returnsNodeWithReplacedActions() +            throws RemoteException { +        AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain(); +        infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID); +        infoFromApp.addAction(ACTION_CONTEXT_CLICK); +        mActionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID); +        verifyNoMoreInteractions(mMockServiceCallback); + +        mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), +                mReplacerInteractionId); + +        verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(), +                eq(INTERACTION_ID)); +        AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue(); +        assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId()); +        assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE); +    } + +    public void testCallbacks_singleNonrootNodeThenReplacer_returnsNodeWithNoActions() +            throws RemoteException { +        AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain(); +        infoFromApp.setSourceNodeId(NON_ROOT_NODE_ID, APP_WINDOW_ID); +        infoFromApp.addAction(ACTION_CONTEXT_CLICK); +        mActionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID); +        verifyNoMoreInteractions(mMockServiceCallback); + +        mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), +                mReplacerInteractionId); + +        verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(), +                eq(INTERACTION_ID)); +        AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue(); +        assertEquals(NON_ROOT_NODE_ID, infoSentToService.getSourceNodeId()); +        assertTrue(infoSentToService.getActionList().isEmpty()); +    } + +    public void testCallbacks_replacerThenSingleRootNode_returnsNodeWithReplacedActions() +            throws RemoteException { +        mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), +                mReplacerInteractionId); +        verifyNoMoreInteractions(mMockServiceCallback); + +        AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain(); +        infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID); +        infoFromApp.addAction(ACTION_CONTEXT_CLICK); +        mActionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID); + +        verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(), +                eq(INTERACTION_ID)); +        AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue(); +        assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId()); +        assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE); +    } + +    public void testCallbacks_multipleNodesThenReplacer_clearsActionsAndAddsSomeToRoot() +            throws RemoteException { +        mActionReplacingCallback +                .setFindAccessibilityNodeInfosResult(getAppNodeList(), INTERACTION_ID); +        verifyNoMoreInteractions(mMockServiceCallback); + +        mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), +                mReplacerInteractionId); + +        verify(mMockServiceCallback).setFindAccessibilityNodeInfosResult(mInfoListCaptor.capture(), +                eq(INTERACTION_ID)); +        assertEquals(2, mInfoListCaptor.getValue().size()); +        AccessibilityNodeInfo rootInfoSentToService = getNodeWithIdFromList( +                mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID); +        AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList( +                mInfoListCaptor.getValue(), NON_ROOT_NODE_ID); +        assertInfoHasExactlyTheseActions( +                rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE); +        assertTrue(otherInfoSentToService.getActionList().isEmpty()); +    } + +    public void testCallbacks_replacerThenMultipleNodes_clearsActionsAndAddsSomeToRoot() +            throws RemoteException { +        mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(), +                mReplacerInteractionId); +        verifyNoMoreInteractions(mMockServiceCallback); + +        mActionReplacingCallback +                .setFindAccessibilityNodeInfosResult(getAppNodeList(), INTERACTION_ID); + +        verify(mMockServiceCallback).setFindAccessibilityNodeInfosResult(mInfoListCaptor.capture(), +                eq(INTERACTION_ID)); +        assertEquals(2, mInfoListCaptor.getValue().size()); +        AccessibilityNodeInfo rootInfoSentToService = getNodeWithIdFromList( +                mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID); +        AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList( +                mInfoListCaptor.getValue(), NON_ROOT_NODE_ID); +        assertInfoHasExactlyTheseActions( +                rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE); +        assertTrue(otherInfoSentToService.getActionList().isEmpty()); +    } + +    public void testConstructor_actionReplacerThrowsException_passesDataToService() +            throws RemoteException { +        doThrow(RemoteException.class).when(mMockReplacerConnection) +                .findAccessibilityNodeInfoByAccessibilityId(eq(AccessibilityNodeInfo.ROOT_NODE_ID), +                        (Region) anyObject(), mInteractionIdCaptor.capture(), +                        eq(mActionReplacingCallback), eq(0), eq(INTERROGATING_PID), +                        eq(INTERROGATING_TID), (MagnificationSpec) anyObject(), eq(null)); +        ActionReplacingCallback actionReplacingCallback = new ActionReplacingCallback( +                mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID, +                INTERROGATING_TID); + +        verifyNoMoreInteractions(mMockServiceCallback); +        AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain(); +        infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID); +        infoFromApp.addAction(ACTION_CONTEXT_CLICK); +        actionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID); + +        verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(), +                eq(INTERACTION_ID)); +        AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue(); +        assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId()); +        assertEquals(1, infoSentToService.getActionList().size()); +        assertEquals(ACTION_CONTEXT_CLICK, infoSentToService.getActionList().get(0)); +    } + +    public void testSetPerformAccessibilityActionResult_actsAsPassThrough() throws RemoteException { +        mActionReplacingCallback.setPerformAccessibilityActionResult(true, INTERACTION_ID); +        verify(mMockServiceCallback).setPerformAccessibilityActionResult(true, INTERACTION_ID); +        mActionReplacingCallback.setPerformAccessibilityActionResult(false, INTERACTION_ID); +        verify(mMockServiceCallback).setPerformAccessibilityActionResult(false, INTERACTION_ID); +    } + + +    private List<AccessibilityNodeInfo> getReplacerNodes() { +        AccessibilityNodeInfo root = AccessibilityNodeInfo.obtain(); +        root.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, +                AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); +        for (AccessibilityAction action : ACTIONS_FROM_REPLACER) { +            root.addAction(action); +        } + +        // Second node should have no effect +        AccessibilityNodeInfo other = AccessibilityNodeInfo.obtain(); +        other.setSourceNodeId(NON_ROOT_NODE_ID, +                AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); +        other.addAction(ACTION_COLLAPSE); + +        return Arrays.asList(root, other); +    } + +    private void assertInfoHasExactlyTheseActions( +            AccessibilityNodeInfo info, AccessibilityAction[] actions) { +        List<AccessibilityAction> nodeActions = info.getActionList(); +        assertEquals(new HashSet<AccessibilityAction>(nodeActions), +                new HashSet<AccessibilityAction>(Arrays.asList(actions))); +    } + +    private AccessibilityNodeInfo getNodeWithIdFromList( +            List<AccessibilityNodeInfo> infos, long id) { +        for (AccessibilityNodeInfo info : infos) { +            if (info.getSourceNodeId() == id) { +                return info; +            } +        } +        assertTrue("Didn't find node", false); +        return null; +    } + +    private List<AccessibilityNodeInfo> getAppNodeList() { +        AccessibilityNodeInfo rootInfoFromApp = AccessibilityNodeInfo.obtain(); +        rootInfoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID); +        rootInfoFromApp.addAction(ACTION_CONTEXT_CLICK); +        AccessibilityNodeInfo otherInfoFromApp = AccessibilityNodeInfo.obtain(); +        otherInfoFromApp.setSourceNodeId(NON_ROOT_NODE_ID, APP_WINDOW_ID); +        otherInfoFromApp.addAction(ACTION_CLICK); +        return Arrays.asList(rootInfoFromApp, otherInfoFromApp); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index 12495ce90cf4..ec99a9a2eb95 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -396,7 +396,7 @@ public class MotionEventInjectorTest {                  hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));          // Timing will restart when the gesture continues          long secondSequenceStart = events.get(2).getEventTime(); -        assertTrue(secondSequenceStart > events.get(1).getEventTime()); +        assertTrue(secondSequenceStart >= events.get(1).getEventTime());          assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE));          assertThat(events.get(3), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_MOVE,                  hasEventTime(secondSequenceStart + CONTINUED_LINE_INTERVAL)));  |