diff options
38 files changed, 2457 insertions, 56 deletions
diff --git a/Android.mk b/Android.mk index 1da4783bcac5..a1e9ed9abca0 100644 --- a/Android.mk +++ b/Android.mk @@ -295,6 +295,10 @@ LOCAL_SRC_FILES += \ core/java/android/print/IWriteResultCallback.aidl \ core/java/android/printservice/IPrintService.aidl \ core/java/android/printservice/IPrintServiceClient.aidl \ + core/java/android/companion/ICompanionDeviceManager.aidl \ + core/java/android/companion/ICompanionDeviceManagerService.aidl \ + core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl \ + core/java/android/companion/IOnAssociateCallback.aidl \ core/java/android/service/dreams/IDreamManager.aidl \ core/java/android/service/dreams/IDreamService.aidl \ core/java/android/service/persistentdata/IPersistentDataBlockService.aidl \ diff --git a/api/current.txt b/api/current.txt index e315958badeb..0f0c16e9c558 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7950,6 +7950,65 @@ package android.bluetooth.le { } +package android.companion { + + public final class AssociationRequest<F extends android.companion.DeviceFilter> 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 final class BluetoothDeviceFilter 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.BluetoothDeviceFilter> CREATOR; + } + + public static final class BluetoothDeviceFilter.Builder { + ctor public BluetoothDeviceFilter.Builder(); + method public android.companion.BluetoothDeviceFilter.Builder addServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid); + method public android.companion.BluetoothDeviceFilter build(); + method public android.companion.BluetoothDeviceFilter.Builder setAddress(java.lang.String); + method public android.companion.BluetoothDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); + } + + public final class BluetoothLEDeviceFilter 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.BluetoothLEDeviceFilter> CREATOR; + } + + public static final class BluetoothLEDeviceFilter.Builder { + 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 setScanFilter(android.bluetooth.le.ScanFilter); + } + + public final class CompanionDeviceManager { + method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler); + field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE"; + } + + public static abstract class CompanionDeviceManager.Callback { + ctor public CompanionDeviceManager.Callback(); + method public abstract void onDeviceFound(android.content.IntentSender); + method public abstract void onFailure(java.lang.CharSequence); + } + + public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable { + } + +} + package android.content { public abstract class AbstractThreadedSyncAdapter { @@ -8562,6 +8621,7 @@ package android.content { field public static final java.lang.String CAPTIONING_SERVICE = "captioning"; field public static final java.lang.String CARRIER_CONFIG_SERVICE = "carrier_config"; field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard"; + field public static final java.lang.String COMPANION_DEVICE_SERVICE = "companion_device"; field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity"; field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir"; field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index ae145dc9b037..50d61aedf85c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8340,6 +8340,65 @@ package android.bluetooth.le { } +package android.companion { + + public final class AssociationRequest<F extends android.companion.DeviceFilter> 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 final class BluetoothDeviceFilter 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.BluetoothDeviceFilter> CREATOR; + } + + public static final class BluetoothDeviceFilter.Builder { + ctor public BluetoothDeviceFilter.Builder(); + method public android.companion.BluetoothDeviceFilter.Builder addServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid); + method public android.companion.BluetoothDeviceFilter build(); + method public android.companion.BluetoothDeviceFilter.Builder setAddress(java.lang.String); + method public android.companion.BluetoothDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); + } + + public final class BluetoothLEDeviceFilter 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.BluetoothLEDeviceFilter> CREATOR; + } + + public static final class BluetoothLEDeviceFilter.Builder { + 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 setScanFilter(android.bluetooth.le.ScanFilter); + } + + public final class CompanionDeviceManager { + method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler); + field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE"; + } + + public static abstract class CompanionDeviceManager.Callback { + ctor public CompanionDeviceManager.Callback(); + method public abstract void onDeviceFound(android.content.IntentSender); + method public abstract void onFailure(java.lang.CharSequence); + } + + public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable { + } + +} + package android.content { public abstract class AbstractThreadedSyncAdapter { @@ -8959,6 +9018,7 @@ package android.content { field public static final java.lang.String CAPTIONING_SERVICE = "captioning"; field public static final java.lang.String CARRIER_CONFIG_SERVICE = "carrier_config"; field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard"; + field public static final java.lang.String COMPANION_DEVICE_SERVICE = "companion_device"; field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity"; field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir"; field public static final java.lang.String CONTEXTHUB_SERVICE = "contexthub"; diff --git a/api/test-current.txt b/api/test-current.txt index 1e3b6db92185..73e80504f79a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -7973,6 +7973,65 @@ package android.bluetooth.le { } +package android.companion { + + public final class AssociationRequest<F extends android.companion.DeviceFilter> 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 final class BluetoothDeviceFilter 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.BluetoothDeviceFilter> CREATOR; + } + + public static final class BluetoothDeviceFilter.Builder { + ctor public BluetoothDeviceFilter.Builder(); + method public android.companion.BluetoothDeviceFilter.Builder addServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid); + method public android.companion.BluetoothDeviceFilter build(); + method public android.companion.BluetoothDeviceFilter.Builder setAddress(java.lang.String); + method public android.companion.BluetoothDeviceFilter.Builder setNamePattern(java.util.regex.Pattern); + } + + public final class BluetoothLEDeviceFilter 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.BluetoothLEDeviceFilter> CREATOR; + } + + public static final class BluetoothLEDeviceFilter.Builder { + 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 setScanFilter(android.bluetooth.le.ScanFilter); + } + + public final class CompanionDeviceManager { + method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler); + field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE"; + } + + public static abstract class CompanionDeviceManager.Callback { + ctor public CompanionDeviceManager.Callback(); + method public abstract void onDeviceFound(android.content.IntentSender); + method public abstract void onFailure(java.lang.CharSequence); + } + + public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable { + } + +} + package android.content { public abstract class AbstractThreadedSyncAdapter { @@ -8587,6 +8646,7 @@ package android.content { field public static final java.lang.String CAPTIONING_SERVICE = "captioning"; field public static final java.lang.String CARRIER_CONFIG_SERVICE = "carrier_config"; field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard"; + field public static final java.lang.String COMPANION_DEVICE_SERVICE = "companion_device"; field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity"; field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir"; field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2 diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index fc1d6136838e..44db326ba33a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -30,6 +30,8 @@ import android.app.usage.StorageStatsManager; import android.app.usage.UsageStatsManager; import android.appwidget.AppWidgetManager; import android.bluetooth.BluetoothManager; +import android.companion.CompanionDeviceManager; +import android.companion.ICompanionDeviceManager; import android.content.ClipboardManager; import android.content.Context; import android.content.IRestrictionsManager; @@ -634,6 +636,18 @@ final class SystemServiceRegistry { UserHandle.getAppId(Process.myUid())); }}); + registerService(Context.COMPANION_DEVICE_SERVICE, CompanionDeviceManager.class, + new CachedServiceFetcher<CompanionDeviceManager>() { + @Override + public CompanionDeviceManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder iBinder = + ServiceManager.getServiceOrThrow(Context.COMPANION_DEVICE_SERVICE); + ICompanionDeviceManager service = + ICompanionDeviceManager.Stub.asInterface(iBinder); + return new CompanionDeviceManager(service, ctx); + }}); + registerService(Context.CONSUMER_IR_SERVICE, ConsumerIrManager.class, new CachedServiceFetcher<ConsumerIrManager>() { @Override diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java index 17a802d59f4f..b89c64a8cac6 100644 --- a/core/java/android/bluetooth/le/ScanFilter.java +++ b/core/java/android/bluetooth/le/ScanFilter.java @@ -67,7 +67,9 @@ public final class ScanFilter implements Parcelable { private final byte[] mManufacturerData; @Nullable private final byte[] mManufacturerDataMask; - private static final ScanFilter EMPTY = new ScanFilter.Builder().build() ; + + /** @hide */ + public static final ScanFilter EMPTY = new ScanFilter.Builder().build() ; private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, @@ -318,8 +320,12 @@ public final class ScanFilter implements Parcelable { return true; } - // Check if the uuid pattern is contained in a list of parcel uuids. - private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, + /** + * Check if the uuid pattern is contained in a list of parcel uuids. + * + * @hide + */ + public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids) { if (uuid == null) { return true; @@ -338,7 +344,7 @@ public final class ScanFilter implements Parcelable { } // Check if the uuid pattern matches the particular service uuid. - private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { + private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { if (mask == null) { return uuid.equals(data); } diff --git a/core/java/android/companion/AssociationRequest.aidl b/core/java/android/companion/AssociationRequest.aidl new file mode 100644 index 000000000000..6c9106209735 --- /dev/null +++ b/core/java/android/companion/AssociationRequest.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable AssociationRequest; diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java new file mode 100644 index 000000000000..d477f43ac8c2 --- /dev/null +++ b/core/java/android/companion/AssociationRequest.java @@ -0,0 +1,191 @@ +/* + * 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 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; + +/** + * 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 + * 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 + * supported. + * + * 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 {} + + private final boolean mSingleDevice; + private final int mMediumType; + private final F mDeviceFilter; + + private AssociationRequest(boolean singleDevice, int mMediumType, F deviceFilter) { + this.mSingleDevice = singleDevice; + this.mMediumType = mMediumType; + this.mDeviceFilter = deviceFilter; + } + + private AssociationRequest(Parcel in) { + this( + in.readByte() != 0, + in.readInt(), + in.readParcelable(AssociationRequest.class.getClassLoader())); + } + + /** @hide */ + public boolean isSingleDevice() { + return mSingleDevice; + } + + /** @hide */ + @MediumType + public int getMediumType() { + return mMediumType; + } + + /** @hide */ + @Nullable + public F getDeviceFilter() { + return mDeviceFilter; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mSingleDevice ? 1 : 0)); + dest.writeInt(mMediumType); + dest.writeParcelable(mDeviceFilter, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<AssociationRequest> CREATOR = new Creator<AssociationRequest>() { + @Override + public AssociationRequest createFromParcel(Parcel in) { + return new AssociationRequest(in); + } + + @Override + public AssociationRequest[] newArray(int size) { + return new AssociationRequest[size]; + } + }; + + /** + * 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>> { + 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); + } + + //TODO implement, once specific filter classes are available +// public static Builder<> createForWiFiDevice() +// public static Builder<> createForNanDevice() + + /** + * @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) { + checkNotUsed(); + this.mSingleDevice = singleDevice; + return this; + } + + /** + * @param deviceFilter if set, only devices matching the given filter will be shown to the + * user + */ + @NonNull + public Builder<F> setDeviceFilter(@Nullable F 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; + return this; + } + + /** @inheritDoc */ + @NonNull + @Override + public AssociationRequest<F> build() { + markUsed(); + return new AssociationRequest<>(mSingleDevice, mMediumType, mDeviceFilter); + } + } +} diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java new file mode 100644 index 000000000000..5a69955429aa --- /dev/null +++ b/core/java/android/companion/BluetoothDeviceFilter.java @@ -0,0 +1,202 @@ +/* + * 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.matchesAddress; +import static android.companion.BluetoothDeviceFilterUtils.matchesName; +import static android.companion.BluetoothDeviceFilterUtils.matchesServiceUuids; +import static android.companion.BluetoothDeviceFilterUtils.patternFromString; +import static android.companion.BluetoothDeviceFilterUtils.patternToString; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.bluetooth.BluetoothDevice; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.provider.OneTimeUseBuilder; + +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * A filter for Bluetooth(non-LE) devices + */ +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; + private final List<ParcelUuid> mServiceUuidMasks; + + private BluetoothDeviceFilter( + Pattern namePattern, + String address, + List<ParcelUuid> serviceUuids, + List<ParcelUuid> serviceUuidMasks) { + mNamePattern = namePattern; + mAddress = address; + mServiceUuids = ArrayUtils.emptyIfNull(serviceUuids); + mServiceUuidMasks = ArrayUtils.emptyIfNull(serviceUuidMasks); + } + + private BluetoothDeviceFilter(Parcel in) { + this( + patternFromString(in.readString()), + in.readString(), + readUuids(in), + readUuids(in)); + } + + private static List<ParcelUuid> readUuids(Parcel in) { + final ArrayList<ParcelUuid> list = new ArrayList<>(); + in.readParcelableList(list, ParcelUuid.class.getClassLoader()); + return list; + } + + /** @hide */ + @NonNull + public static BluetoothDeviceFilter nullsafe(@Nullable BluetoothDeviceFilter nullable) { + return nullable != null ? nullable : noOp(); + } + + /** @hide */ + @NonNull + public static BluetoothDeviceFilter noOp() { + if (NO_OP == null) NO_OP = new Builder().build(); + return NO_OP; + } + + /** @hide */ + @Override + public boolean matches(BluetoothDevice device) { + return matchesAddress(mAddress, device) + && matchesServiceUuids(mServiceUuids, mServiceUuidMasks, device) + && matchesName(getNamePattern(), device); + } + + /** @hide */ + @Nullable + public Pattern getNamePattern() { + return mNamePattern; + } + + /** @hide */ + @Nullable + public String getAddress() { + return mAddress; + } + + /** @hide */ + @NonNull + public List<ParcelUuid> getServiceUuids() { + return mServiceUuids; + } + + /** @hide */ + @NonNull + public List<ParcelUuid> getServiceUuidMasks() { + return mServiceUuidMasks; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(patternToString(getNamePattern())); + dest.writeString(mAddress); + dest.writeParcelableList(mServiceUuids, flags); + dest.writeParcelableList(mServiceUuidMasks, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<BluetoothDeviceFilter> CREATOR + = new Creator<BluetoothDeviceFilter>() { + @Override + public BluetoothDeviceFilter createFromParcel(Parcel in) { + return new BluetoothDeviceFilter(in); + } + + @Override + public BluetoothDeviceFilter[] newArray(int size) { + return new BluetoothDeviceFilter[size]; + } + }; + + /** + * A builder for {@link BluetoothDeviceFilter} + */ + public static final class Builder extends OneTimeUseBuilder<BluetoothDeviceFilter> { + private Pattern mNamePattern; + private String mAddress; + private ArrayList<ParcelUuid> mServiceUuid; + private ArrayList<ParcelUuid> mServiceUuidMask; + + /** + * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the + * given regular expression will be shown + */ + public Builder setNamePattern(@Nullable Pattern regex) { + checkNotUsed(); + mNamePattern = regex; + return this; + } + + /** + * @param address if set, only devices with MAC address exactly matching the given one will + * pass the filter + */ + @NonNull + public Builder setAddress(@Nullable String address) { + checkNotUsed(); + mAddress = address; + return this; + } + + /** + * Add filtering by certain bits of {@link BluetoothDevice#getUuids()} + * + * A device with any uuid matching the given bits is considered passing + * + * @param serviceUuid the values for the bits to match + * @param serviceUuidMask if provided, only those bits would have to match. + */ + @NonNull + public Builder addServiceUuid( + @Nullable ParcelUuid serviceUuid, @Nullable ParcelUuid serviceUuidMask) { + checkNotUsed(); + mServiceUuid = ArrayUtils.add(mServiceUuid, serviceUuid); + mServiceUuidMask = ArrayUtils.add(mServiceUuidMask, serviceUuidMask); + return this; + } + + /** @inheritDoc */ + @Override + @NonNull + public BluetoothDeviceFilter build() { + markUsed(); + return new BluetoothDeviceFilter( + mNamePattern, mAddress, mServiceUuid, mServiceUuidMask); + } + } +} diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java new file mode 100644 index 000000000000..289f9953a068 --- /dev/null +++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java @@ -0,0 +1,107 @@ +/* + * 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.text.TextUtils.firstNotEmpty; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.ScanFilter; +import android.os.ParcelUuid; +import android.util.Log; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +/** @hide */ +public class BluetoothDeviceFilterUtils { + private BluetoothDeviceFilterUtils() {} + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "BluetoothDeviceFilterUtil"; + + @Nullable + static String patternToString(@Nullable Pattern p) { + return p == null ? null : p.pattern(); + } + + @Nullable + static Pattern patternFromString(@Nullable String s) { + return s == null ? null : Pattern.compile(s); + } + + static boolean matches(ScanFilter filter, BluetoothDevice device) { + return matchesAddress(filter.getDeviceAddress(), device) + && matchesServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask(), device); + } + + static boolean matchesAddress(String deviceAddress, BluetoothDevice device) { + final boolean result = deviceAddress == null + || (device == null || !deviceAddress.equals(device.getAddress())); + if (DEBUG) debugLogMatchResult(result, device, deviceAddress); + return result; + } + + static boolean matchesServiceUuids(List<ParcelUuid> serviceUuids, + List<ParcelUuid> serviceUuidMasks, BluetoothDevice device) { + for (int i = 0; i < serviceUuids.size(); i++) { + ParcelUuid uuid = serviceUuids.get(i); + ParcelUuid uuidMask = serviceUuidMasks.get(i); + if (!matchesServiceUuid(uuid, uuidMask, device)) { + return false; + } + } + return true; + } + + static boolean matchesServiceUuid(ParcelUuid serviceUuid, ParcelUuid serviceUuidMask, + BluetoothDevice device) { + final boolean result = serviceUuid == null || + ScanFilter.matchesServiceUuids( + serviceUuid, + serviceUuidMask, + Arrays.asList(device.getUuids())); + if (DEBUG) debugLogMatchResult(result, device, serviceUuid); + return result; + } + + static boolean matchesName(@Nullable Pattern namePattern, BluetoothDevice device) { + boolean result; + if (namePattern == null) { + result = true; + } else if (device == null) { + result = false; + } else { + final String name = device.getName(); + 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); + } + + public static String getDeviceDisplayName(@NonNull BluetoothDevice device) { + return firstNotEmpty(device.getAliasName(), device.getAddress()); + } +} diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.aidl b/core/java/android/companion/BluetoothLEDeviceFilter.aidl new file mode 100644 index 000000000000..628cf7b2bdfc --- /dev/null +++ b/core/java/android/companion/BluetoothLEDeviceFilter.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable BluetoothLEDeviceFilter; diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java new file mode 100644 index 000000000000..4a481ca80045 --- /dev/null +++ b/core/java/android/companion/BluetoothLEDeviceFilter.java @@ -0,0 +1,151 @@ +/* + * 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.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.os.Parcel; +import android.provider.OneTimeUseBuilder; + +import com.android.internal.util.ObjectUtils; + +import java.util.regex.Pattern; + +/** + * A filter for Bluetooth LE devices + * + * @see ScanFilter + */ +public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevice> { + + private static BluetoothLEDeviceFilter NO_OP; + + private final Pattern mNamePattern; + private final ScanFilter mScanFilter; + + private BluetoothLEDeviceFilter(Pattern namePattern, ScanFilter scanFilter) { + mNamePattern = namePattern; + mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY); + } + + @SuppressLint("ParcelClassLoader") + private BluetoothLEDeviceFilter(Parcel in) { + this( + patternFromString(in.readString()), + in.readParcelable(null)); + } + + /** @hide */ + @NonNull + public static BluetoothLEDeviceFilter nullsafe(@Nullable BluetoothLEDeviceFilter nullable) { + return nullable != null ? nullable : noOp(); + } + + /** @hide */ + @NonNull + public static BluetoothLEDeviceFilter noOp() { + if (NO_OP == null) NO_OP = new Builder().build(); + return NO_OP; + } + + /** @hide */ + @Nullable + public Pattern getNamePattern() { + return mNamePattern; + } + + /** @hide */ + @NonNull + public ScanFilter getScanFilter() { + return mScanFilter; + } + + /** @hide */ + @Override + public boolean matches(BluetoothDevice device) { + return BluetoothDeviceFilterUtils.matches(getScanFilter(), device) + && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(patternToString(getNamePattern())); + dest.writeParcelable(mScanFilter, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<BluetoothLEDeviceFilter> CREATOR + = new Creator<BluetoothLEDeviceFilter>() { + @Override + public BluetoothLEDeviceFilter createFromParcel(Parcel in) { + return new BluetoothLEDeviceFilter(in); + } + + @Override + public BluetoothLEDeviceFilter[] newArray(int size) { + return new BluetoothLEDeviceFilter[size]; + } + }; + + /** + * Builder for {@link BluetoothLEDeviceFilter} + */ + public static final class Builder extends OneTimeUseBuilder<BluetoothLEDeviceFilter> { + private ScanFilter mScanFilter; + private Pattern mNamePattern; + + /** + * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the + * given regular expression will be shown + */ + public Builder setNamePattern(@Nullable Pattern regex) { + checkNotUsed(); + mNamePattern = regex; + return this; + } + + /** + * @param scanFilter a {@link ScanFilter} to filter devices by + * + * @see ScanFilter for specific details on its various fields + */ + @NonNull + public Builder setScanFilter(@Nullable ScanFilter scanFilter) { + checkNotUsed(); + mScanFilter = scanFilter; + return this; + } + + /** @inheritDoc */ + @Override + @NonNull + public BluetoothLEDeviceFilter build() { + markUsed(); + return new BluetoothLEDeviceFilter(mNamePattern, mScanFilter); + } + } +} diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java new file mode 100644 index 000000000000..b379c7c57554 --- /dev/null +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -0,0 +1,141 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.content.Context; +import android.content.IntentSender; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; + +/** + * System level service for managing companion devices + * + * Usage: + * To obtain an instance call + * {@link Context#getSystemService}({@link Context#COMPANION_DEVICE_SERVICE}) + * + * Then, call {@link #associate} to initiate the flow of associating current package + * with a device selected by user + * + * @see AssociationRequest + */ +public final class CompanionDeviceManager { + + /** + * A device, returned in the activity result of the {@link IntentSender} received in + * {@link Callback#onDeviceFound} + */ + public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; + + /** + * A callback to receive once at least one suitable device is found, or the search failed + * (e.g. timed out) + */ + public abstract static class Callback { + + /** + * Called once at least one suitable device is found + * + * @param chooserLauncher a {@link IntentSender} to launch the UI for user to select a + * device + */ + public abstract void onDeviceFound(IntentSender chooserLauncher); + + /** + * Called if there was an error looking for device(s), e.g. timeout + * + * @param error the cause of the error + */ + public abstract void onFailure(CharSequence error); + } + + private final ICompanionDeviceManager mService; + private final Context mContext; + + /** @hide */ + public CompanionDeviceManager( + @NonNull ICompanionDeviceManager service, @NonNull Context context) { + mService = service; + mContext = context; + } + + /** + * Associate this app with a companion device, selected by user + * + * Once at least one appropriate device is found, {@code callback} will be called with a + * {@link PendingIntent} that can be used to show the list of available devices for the user + * to select. + * It should be started for result (i.e. using + * {@link android.app.Activity#startIntentSenderForResult}), as the resulting + * {@link android.content.Intent} will contain extra {@link #EXTRA_DEVICE}, with the selected + * device. (e.g. {@link android.bluetooth.BluetoothDevice}) + * + * @param request specific details about this request + * @param callback will be called once there's at least one device found for user to choose from + * @param handler A handler to control which thread the callback will be delivered on, or null, + * to deliver it on main thread + * + * @see AssociationRequest + */ + public void associate( + @NonNull AssociationRequest<?> request, + @NonNull Callback callback, + @Nullable Handler handler) { + final Handler finalHandler = handler != null + ? handler + : new Handler(Looper.getMainLooper()); + try { + mService.associate( + request, + new IOnAssociateCallback.Stub() { + + @Override + public void onSuccess(PendingIntent launcher) throws RemoteException { + finalHandler.post(() -> { + callback.onDeviceFound(launcher.getIntentSender()); + }); + } + + @Override + public void onFailure(CharSequence reason) throws RemoteException { + finalHandler.post(() -> callback.onFailure(reason)); + } + }, + mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void requestNotificationAccess() { + //TODO implement + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** @hide */ + public boolean haveNotificationAccess() { + //TODO implement + throw new UnsupportedOperationException("Not yet implemented"); + } + +} diff --git a/core/java/android/companion/DeviceFilter.java b/core/java/android/companion/DeviceFilter.java new file mode 100644 index 000000000000..8362b2dab8fd --- /dev/null +++ b/core/java/android/companion/DeviceFilter.java @@ -0,0 +1,46 @@ +/* + * 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 android.annotation.Nullable; +import android.os.Parcelable; + +/** + * 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} + */ +public interface DeviceFilter<D extends Parcelable> extends Parcelable { + + /** + * @return whether the given device matches this filter + * + * @hide + */ + boolean matches(D device); + + /** + * A nullsafe {@link #matches(Parcelable)}, returning true if the filter is null + * + * @hide + */ + static <D extends Parcelable> boolean matches(@Nullable DeviceFilter<D> filter, D device) { + return filter == null || filter.matches(device); + } +} diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl new file mode 100644 index 000000000000..065e31be9adf --- /dev/null +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -0,0 +1,35 @@ +/* + * 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 android.companion.IOnAssociateCallback; +import android.companion.AssociationRequest; + +/** + * Interface for communication with the core companion device manager service. + * + * @hide + */ +interface ICompanionDeviceManager { + void associate(in AssociationRequest request, + in IOnAssociateCallback callback, + in String callingPackage); //TODO int userId? + + //TODO add these +// boolean haveNotificationAccess(String packageName); +// oneway void requestNotificationAccess(String packageName); +} diff --git a/core/java/android/companion/ICompanionDeviceManagerService.aidl b/core/java/android/companion/ICompanionDeviceManagerService.aidl new file mode 100644 index 000000000000..ff2a7eb957f0 --- /dev/null +++ b/core/java/android/companion/ICompanionDeviceManagerService.aidl @@ -0,0 +1,29 @@ +/* + * 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 android.companion.AssociationRequest; +import android.companion.IOnAssociateCallback; + + +/** @hide */ +interface ICompanionDeviceManagerService { + void startDiscovery( + in AssociationRequest request, + in IOnAssociateCallback callback, + in String callingPackage); +} diff --git a/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl b/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl new file mode 100644 index 000000000000..c9dd345341b2 --- /dev/null +++ b/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl @@ -0,0 +1,24 @@ +/* + * 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 per missions and + * limitations under the License. + */ + +package android.companion; + +import android.bluetooth.BluetoothDevice; + +/** @hide */ +interface ICompanionDeviceManagerServiceCallback { + void onDeviceSelected(in BluetoothDevice device); +} diff --git a/core/java/android/companion/IOnAssociateCallback.aidl b/core/java/android/companion/IOnAssociateCallback.aidl new file mode 100644 index 000000000000..4867eadd2577 --- /dev/null +++ b/core/java/android/companion/IOnAssociateCallback.aidl @@ -0,0 +1,25 @@ +/* + * 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 per missions and + * limitations under the License. + */ + +package android.companion; + +import android.app.PendingIntent; + +/** @hide */ +interface IOnAssociateCallback { + void onSuccess(in PendingIntent launcher); + void onFailure(in CharSequence reason); +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index a7d5ac6b219a..06f73033acd5 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3600,6 +3600,14 @@ public abstract class Context { public static final String PRINT_SERVICE = "print"; /** + * {@link android.companion.CompanionDeviceManager} for managing companion devices + * + * @see #getSystemService + * @see android.companion.CompanionDeviceManager + */ + public static final String COMPANION_DEVICE_SERVICE = "companion_device"; + + /** * Use with {@link #getSystemService} to retrieve a * {@link android.hardware.ConsumerIrManager} for transmitting infrared * signals from the device. diff --git a/core/java/android/provider/OneTimeUseBuilder.java b/core/java/android/provider/OneTimeUseBuilder.java new file mode 100644 index 000000000000..682e9c747322 --- /dev/null +++ b/core/java/android/provider/OneTimeUseBuilder.java @@ -0,0 +1,51 @@ +/* + * 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.provider; + +/** + * A builder that facilitates prohibiting its use after an instance was created with it. + * + * Suggested usage: + * call {@link #checkNotUsed} in each setter, and {@link #markUsed} in {@link #build} + * + * @param <T> Type of object being built + * @hide + */ +public abstract class OneTimeUseBuilder<T> { + private boolean used = false; + + protected void markUsed() { + checkNotUsed(); + used = true; + } + + protected void checkNotUsed() { + if (used) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + + /** + * Builds the instance + * + * Once this method is called, this builder should no longer be used. Any subsequent calls to a + * setter or {@code build()} will throw an exception + */ + public abstract T build(); +} diff --git a/core/java/android/provider/SettingsStringUtil.java b/core/java/android/provider/SettingsStringUtil.java new file mode 100644 index 000000000000..f242d7936776 --- /dev/null +++ b/core/java/android/provider/SettingsStringUtil.java @@ -0,0 +1,174 @@ +/* + * 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.provider; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.text.TextUtils; + +import com.android.internal.util.ArrayUtils; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.function.Function; + +/** + * Utilities for dealing with {@link String} values in {@link Settings} + * + * @hide + */ +public class SettingsStringUtil { + private SettingsStringUtil() {} + + public static final String DELIMITER = ":"; + + /** + * A {@link HashSet} of items, that uses a common convention of setting string + * serialization/deserialization of separating multiple items with {@link #DELIMITER} + */ + public static abstract class ColonDelimitedSet<T> extends HashSet<T> { + + public ColonDelimitedSet(String colonSeparatedItems) { + for (String cn : + TextUtils.split(TextUtils.emptyIfNull(colonSeparatedItems), DELIMITER)) { + add(itemFromString(cn)); + } + } + + protected abstract T itemFromString(String s); + protected String itemToString(T item) { + return String.valueOf(item); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + Iterator<T> it = iterator(); + if (it.hasNext()) { + sb.append(it.next()); + while (it.hasNext()) { + sb.append(DELIMITER); + sb.append(itemToString(it.next())); + } + } + return sb.toString(); + } + + + public static class OfStrings extends ColonDelimitedSet<String> { + public OfStrings(String colonSeparatedItems) { + super(colonSeparatedItems); + } + + @Override + protected String itemFromString(String s) { + return s; + } + + public static String add(String delimitedElements, String element) { + final ColonDelimitedSet<String> set + = new ColonDelimitedSet.OfStrings(delimitedElements); + if (set.contains(element)) { + return delimitedElements; + } + set.add(element); + return set.toString(); + } + + public static String remove(String delimitedElements, String element) { + final ColonDelimitedSet<String> set + = new ColonDelimitedSet.OfStrings(delimitedElements); + if (!set.contains(element)) { + return delimitedElements; + } + set.remove(element); + return set.toString(); + } + + public static boolean contains(String delimitedElements, String element) { + final String[] elements = TextUtils.split(delimitedElements, DELIMITER); + return ArrayUtils.indexOf(elements, element) != -1; + } + } + } + + public static class ComponentNameSet extends ColonDelimitedSet<ComponentName> { + public ComponentNameSet(String colonSeparatedPackageNames) { + super(colonSeparatedPackageNames); + } + + @Override + protected ComponentName itemFromString(String s) { + return ComponentName.unflattenFromString(s); + } + + @Override + protected String itemToString(ComponentName item) { + return item.flattenToString(); + } + + public static String add(String delimitedElements, ComponentName element) { + final ComponentNameSet set = new ComponentNameSet(delimitedElements); + if (set.contains(element)) { + return delimitedElements; + } + set.add(element); + return set.toString(); + } + + public static String remove(String delimitedElements, ComponentName element) { + final ComponentNameSet set = new ComponentNameSet(delimitedElements); + if (!set.contains(element)) { + return delimitedElements; + } + set.remove(element); + return set.toString(); + } + + public static boolean contains(String delimitedElements, ComponentName element) { + return ColonDelimitedSet.OfStrings.contains( + delimitedElements, element.flattenToString()); + } + } + + public static class SettingStringHelper { + private final ContentResolver mContentResolver; + private final String mSettingName; + private final int mUserId; + + public SettingStringHelper(ContentResolver contentResolver, String name, int userId) { + mContentResolver = contentResolver; + mUserId = userId; + mSettingName = name; + } + + public String read() { + return Settings.Secure.getStringForUser( + mContentResolver, mSettingName, mUserId); + } + + public boolean write(String value) { + return Settings.Secure.putStringForUser( + mContentResolver, mSettingName, value, mUserId); + } + + public boolean modify(Function<String, String> change) { + return write(change.apply(read())); + } + } +} diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index bb952ab93077..55aeb1e600a0 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -62,6 +62,7 @@ import android.view.View; import com.android.internal.R; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; import java.lang.reflect.Array; import java.util.Iterator; @@ -466,6 +467,16 @@ public class TextUtils { return isEmpty(str) ? null : str; } + /** {@hide} */ + public static String emptyIfNull(@Nullable String str) { + return str == null ? "" : str; + } + + /** {@hide} */ + public static String firstNotEmpty(@Nullable String a, @NonNull String b) { + return !isEmpty(a) ? a : Preconditions.checkStringNotEmpty(b); + } + /** * 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/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index a9a6364eb3fe..cb3a250a377d 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -435,10 +435,6 @@ public class ArrayUtils { } } - public static <T> boolean contains(@Nullable ArraySet<T> cur, T val) { - return (cur != null) ? cur.contains(val) : false; - } - public static @NonNull <T> ArrayList<T> add(@Nullable ArrayList<T> cur, T val) { if (cur == null) { cur = new ArrayList<>(); @@ -459,6 +455,16 @@ public class ArrayUtils { } } + /** + * Returns the given list, or an immutable empty list if the provided list is null + * + * @see Collections#emptyList + */ + + public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) { + return cur == null ? Collections.emptyList() : cur; + } + public static <T> boolean contains(@Nullable Collection<T> cur, T val) { return (cur != null) ? cur.contains(val) : false; } diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java new file mode 100644 index 000000000000..d109a5a1cae5 --- /dev/null +++ b/core/java/com/android/internal/util/ObjectUtils.java @@ -0,0 +1,31 @@ +/* + * 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.NonNull; +import android.annotation.Nullable; + +/** @hide */ +public class ObjectUtils { + private ObjectUtils() {} + + @NonNull + public static <T> T firstNotNull(@Nullable T a, @NonNull T b) { + return a != null ? a : Preconditions.checkNotNull(b); + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 11eb47b30e60..a6e43ffe0d0c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2365,6 +2365,11 @@ <permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE" android:protectionLevel="signature" /> + <!-- Must be required by the CompanionDeviceManager to ensure that only the system can bind to it. + @hide --> + <permission android:name="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE" + android:protectionLevel="signature" /> + <!-- @SystemApi Must be required by the RuntimePermissionPresenterService to ensure that only the system can bind to it. @hide --> diff --git a/packages/CompanionDeviceManager/Android.mk b/packages/CompanionDeviceManager/Android.mk new file mode 100644 index 000000000000..f730356e6947 --- /dev/null +++ b/packages/CompanionDeviceManager/Android.mk @@ -0,0 +1,27 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := CompanionDeviceManager + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml new file mode 100644 index 000000000000..3eede5454d6f --- /dev/null +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (c) 2017 Google Inc. + * + * 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. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.companiondevicemanager"> + + <permission + android:name="com.android.companiondevicemanager.permission.BIND" + android:protectionLevel="signature" /> + + <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> + + <application + android:allowClearUserData="true" + android:label="@string/app_label" + android:allowBackup="false" + android:supportsRtl="true"> + + <service + android:name=".DeviceDiscoveryService" + android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE" + android:exported="true"> + </service> + + <activity + android:name=".DeviceChooserActivity" + android:theme="@*android:style/Theme.Dialog.NoFrame" + android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"> + <!--TODO include url scheme filter similar to PrintSpooler --> + <intent-filter> + <action android:name="android.companiondevice.START_DISCOVERY" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + + </application> + +</manifest> diff --git a/packages/CompanionDeviceManager/MODULE_LICENSE_APACHE2 b/packages/CompanionDeviceManager/MODULE_LICENSE_APACHE2 new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/CompanionDeviceManager/MODULE_LICENSE_APACHE2 diff --git a/packages/CompanionDeviceManager/NOTICE b/packages/CompanionDeviceManager/NOTICE new file mode 100644 index 000000000000..c5b1efa7aac7 --- /dev/null +++ b/packages/CompanionDeviceManager/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/packages/CompanionDeviceManager/res/layout/device_chooser.xml b/packages/CompanionDeviceManager/res/layout/device_chooser.xml new file mode 100644 index 000000000000..ee08582ba2df --- /dev/null +++ b/packages/CompanionDeviceManager/res/layout/device_chooser.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="0.1" + > + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@android:color/black" + style="@*android:style/TextAppearance.Widget.Toolbar.Title" + /> + + <ListView + android:id="@+id/device_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/title" + android:layout_above="@+id/buttons" + style="@android:style/Widget.Material.Light.ListView" + /> + + <LinearLayout + android:id="@+id/buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:gravity="end" + > + <Button + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Cancel" + style="@android:style/Widget.Material.Light.Button.Borderless.Colored" + /> + <Button + android:id="@+id/button_pair" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Pair" + style="@android:style/Widget.Material.Light.Button.Borderless.Colored" + /> + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/dimens.xml b/packages/CompanionDeviceManager/res/values/dimens.xml new file mode 100644 index 000000000000..da7b0d1447c1 --- /dev/null +++ b/packages/CompanionDeviceManager/res/values/dimens.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <!-- Padding applied on most UI elements --> + <dimen name="padding">12dp</dimen> + +</resources>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml new file mode 100644 index 000000000000..c4195b5090a3 --- /dev/null +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Title of the CompanionDeviceManager application. [CHAR LIMIT=50] --> + <string name="app_label">Companion Device Manager</string> + + <!-- Title of the device selection dialog. --> + <string name="chooser_title">Pair with <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> via Bluetooth?</string> + +</resources> diff --git a/packages/CompanionDeviceManager/res/values/themes.xml b/packages/CompanionDeviceManager/res/values/themes.xml new file mode 100644 index 000000000000..465f8fc6d5ac --- /dev/null +++ b/packages/CompanionDeviceManager/res/values/themes.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + + <!--TODO--> + <!--<style name="Theme.ChooserActivity" parent="@*android:style/Theme.Dialog.NoFrame">--> + <!--<!–<item name="android:windowBackground">@android:color/light_grey</item>–>--> + <!--<item name="android:backgroundColor">@android:color/light_grey</item>--> + <!--</style>--> + +</resources> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java new file mode 100644 index 000000000000..c95f940807a9 --- /dev/null +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java @@ -0,0 +1,133 @@ +/* + * 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.companiondevicemanager; + +import android.app.Activity; +import android.bluetooth.BluetoothDevice; +import android.companion.CompanionDeviceManager; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.database.DataSetObserver; +import android.graphics.Color; +import android.os.Bundle; +import android.text.Html; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +public class DeviceChooserActivity extends Activity { + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "DeviceChooserActivity"; + + private ListView mDeviceListView; + private View mPairButton; + private View mCancelButton; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (DEBUG) Log.i(LOG_TAG, "Started with intent " + getIntent()); + + setContentView(R.layout.device_chooser); + setTitle(Html.fromHtml(getString(R.string.chooser_title, getCallingAppName()), 0)); + getWindow().getDecorView().getRootView().setBackgroundColor(Color.LTGRAY); //TODO theme + + if (getService().mDevicesFound.isEmpty()) { + Log.e(LOG_TAG, "About to show UI, but no devices to show"); + } + + final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter; + mDeviceListView = (ListView) findViewById(R.id.device_list); + mDeviceListView.setAdapter(adapter); + mDeviceListView.addFooterView(getProgressBar(), null, false); + + mPairButton = findViewById(R.id.button_pair); + mPairButton.setOnClickListener((view) -> + onPairTapped(getService().mSelectedDevice)); + adapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + updatePairButtonEnabled(); + } + }); + updatePairButtonEnabled(); + mCancelButton = findViewById(R.id.button_cancel); + mCancelButton.setOnClickListener((view) -> { + setResult(RESULT_CANCELED); + finish(); + }); + } + + private CharSequence getCallingAppName() { + try { + final PackageManager packageManager = getPackageManager(); + return packageManager.getApplicationLabel( + packageManager.getApplicationInfo(getService().mCallingPackage, 0)); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setTitle(CharSequence title) { + final TextView titleView = (TextView) findViewById(R.id.title); + final int padding = getPadding(getResources()); + titleView.setPadding(padding, padding, padding, padding); + titleView.setText(title); + } + + //TODO put in resources xmls + private ProgressBar getProgressBar() { + final ProgressBar progressBar = new ProgressBar(this); + progressBar.setForegroundGravity(Gravity.CENTER_HORIZONTAL); + final int padding = getPadding(getResources()); + progressBar.setPadding(padding, padding, padding, padding); + return progressBar; + } + + static int getPadding(Resources r) { + return r.getDimensionPixelSize(R.dimen.padding); + //TODO +// final float dp = r.getDisplayMetrics().density; +// return (int)(12 * dp); + } + + private void updatePairButtonEnabled() { + mPairButton.setEnabled(getService().mSelectedDevice != null); + } + + private DeviceDiscoveryService getService() { + return DeviceDiscoveryService.sInstance; + } + + protected void onPairTapped(BluetoothDevice selectedDevice) { + setResult(RESULT_OK, + new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice)); + finish(); + } + + private void toast(String msg) { + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); + } +}
\ 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 new file mode 100644 index 000000000000..a3eec0dd5cb5 --- /dev/null +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2013 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.companiondevicemanager; + +import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayName; +import static android.companion.BluetoothLEDeviceFilter.nullsafe; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.companion.AssociationRequest; +import android.companion.BluetoothDeviceFilterUtils; +import android.companion.BluetoothLEDeviceFilter; +import android.companion.ICompanionDeviceManagerService; +import android.companion.IOnAssociateCallback; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class DeviceDiscoveryService extends Service { + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "DeviceDiscoveryService"; + + static DeviceDiscoveryService sInstance; + + private BluetoothAdapter mBluetoothAdapter; + private BluetoothLEDeviceFilter mFilter; + private ScanFilter mScanFilter; + private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build(); + List<BluetoothDevice> mDevicesFound; + BluetoothDevice mSelectedDevice; + DevicesAdapter mDevicesAdapter; + IOnAssociateCallback mCallback; + String mCallingPackage; + + private final ICompanionDeviceManagerService mBinder = + new ICompanionDeviceManagerService.Stub() { + @Override + public void startDiscovery(AssociationRequest request, + IOnAssociateCallback callback, + String callingPackage) throws RemoteException { + if (DEBUG) { + Log.i(LOG_TAG, + "startDiscovery() called with: filter = [" + request + "], callback = [" + + callback + "]"); + } + mCallback = callback; + mCallingPackage = callingPackage; + DeviceDiscoveryService.this.startDiscovery(request); + } + }; + + private final ScanCallback mBLEScanCallback = new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + final BluetoothDevice device = result.getDevice(); + if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) { + onDeviceLost(device); + } else { + onDeviceFound(device); + } + } + }; + + private BluetoothLeScanner mBLEScanner; + + 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 + + if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) { + onDeviceFound(device); + } else { + onDeviceLost(device); + } + } + }; + + @Override + public IBinder onBind(Intent intent) { + if (DEBUG) Log.i(LOG_TAG, "onBind(" + intent + ")"); + return mBinder.asBinder(); + } + + @Override + public void onCreate() { + super.onCreate(); + + if (DEBUG) Log.i(LOG_TAG, "onCreate()"); + + mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter(); + mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner(); + + mDevicesFound = new ArrayList<>(); + mDevicesAdapter = new DevicesAdapter(); + + sInstance = this; + } + + private void startDiscovery(AssociationRequest<?> request) { + //TODO support other protocols as well + mFilter = nullsafe((BluetoothLEDeviceFilter) request.getDeviceFilter()); + mScanFilter = mFilter.getScanFilter(); + + reset(); + + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BluetoothDevice.ACTION_FOUND); + intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED); + + registerReceiver(mBluetoothDeviceFoundBroadcastReceiver, intentFilter); + mBluetoothAdapter.startDiscovery(); + + mBLEScanner.startScan( + Collections.singletonList(mScanFilter), mDefaultScanSettings, mBLEScanCallback); + } + + private void reset() { + mDevicesFound.clear(); + mSelectedDevice = null; + mDevicesAdapter.notifyDataSetChanged(); + } + + @Override + public boolean onUnbind(Intent intent) { + stopScan(); + return super.onUnbind(intent); + } + + private void stopScan() { + if (DEBUG) Log.i(LOG_TAG, "stopScan() called"); + mBluetoothAdapter.cancelDiscovery(); + mBLEScanner.stopScan(mBLEScanCallback); + unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver); + stopSelf(); + } + + private void onDeviceFound(BluetoothDevice device) { + if (mDevicesFound.contains(device)) { + return; + } + + if (DEBUG) { + Log.i(LOG_TAG, "Considering device " + getDeviceDisplayName(device)); + } + + if (!mFilter.matches(device)) { + return; + } + + if (DEBUG) { + Log.i(LOG_TAG, "Found device " + getDeviceDisplayName(device)); + } + if (mDevicesFound.isEmpty()) { + onReadyToShowUI(); + } + mDevicesFound.add(device); + mDevicesAdapter.notifyDataSetChanged(); + } + + //TODO also, on timeout -> call onFailure + private void onReadyToShowUI() { + try { + mCallback.onSuccess(PendingIntent.getActivity( + this, 0, + new Intent(this, DeviceChooserActivity.class), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_IMMUTABLE)); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + private void onDeviceLost(BluetoothDevice device) { + mDevicesFound.remove(device); + mDevicesAdapter.notifyDataSetChanged(); + if (DEBUG) { + Log.i(LOG_TAG, "Lost device " + getDeviceDisplayName(device)); + } + } + + class DevicesAdapter extends ArrayAdapter<BluetoothDevice> { + private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth); + + private Drawable icon(int drawableRes) { + Drawable icon = getResources().getDrawable(drawableRes, null); + icon.setTint(Color.DKGRAY); + return icon; + } + + public DevicesAdapter() { + super(DeviceDiscoveryService.this, 0, mDevicesFound); + } + + @Override + public View getView( + int position, + @Nullable View convertView, + @NonNull ViewGroup parent) { + TextView view = convertView instanceof TextView + ? (TextView) convertView + : newView(); + bind(view, getItem(position)); + return view; + } + + private void bind(TextView textView, BluetoothDevice device) { + textView.setText(getDeviceDisplayName(device)); + textView.setBackgroundColor( + device.equals(mSelectedDevice) + ? Color.GRAY + : Color.TRANSPARENT); + textView.setOnClickListener((view) -> { + mSelectedDevice = device; + notifyDataSetChanged(); + }); + } + + //TODO move to a layout file + private TextView newView() { + final TextView textView = new TextView(DeviceDiscoveryService.this); + textView.setTextColor(Color.BLACK); + final int padding = DeviceChooserActivity.getPadding(getResources()); + textView.setPadding(padding, padding, padding, padding); + textView.setCompoundDrawablesWithIntrinsicBounds( + BLUETOOTH_ICON, null, null, null); + textView.setCompoundDrawablePadding(padding); + return textView; + } + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index a861522c0576..c0a2e714734b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -70,6 +70,8 @@ import android.os.UserManager; import android.os.UserManagerInternal; import android.provider.Settings; import android.hardware.fingerprint.IFingerprintService; +import android.provider.SettingsStringUtil.ComponentNameSet; +import android.provider.SettingsStringUtil.SettingStringHelper; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; import android.util.Slog; @@ -98,9 +100,9 @@ import com.android.internal.R; import com.android.internal.content.PackageMonitor; import com.android.internal.os.SomeArgs; import com.android.server.LocalServices; - import com.android.server.policy.AccessibilityShortcutController; import com.android.server.statusbar.StatusBarManagerInternal; + import org.xmlpull.v1.XmlPullParserException; import java.io.FileDescriptor; @@ -2013,10 +2015,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * Enables accessibility service specified by {@param componentName} for the {@param userId}. */ private void enableAccessibilityServiceLocked(ComponentName componentName, int userId) { - SettingsStringHelper settingsHelper = new SettingsStringHelper( - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId); - settingsHelper.addService(componentName); - settingsHelper.writeToSettings(); + final SettingStringHelper setting = + new SettingStringHelper( + mContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userId); + setting.write(ComponentNameSet.add(setting.read(), componentName)); UserState userState = getUserStateLocked(userId); if (userState.mEnabledServices.add(componentName)) { @@ -2028,10 +2032,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * Disables accessibility service specified by {@param componentName} for the {@param userId}. */ private void disableAccessibilityServiceLocked(ComponentName componentName, int userId) { - SettingsStringHelper settingsHelper = new SettingsStringHelper( - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId); - settingsHelper.deleteService(componentName); - settingsHelper.writeToSettings(); + final SettingStringHelper setting = + new SettingStringHelper( + mContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userId); + setting.write(ComponentNameSet.remove(setting.read(), componentName)); UserState userState = getUserStateLocked(userId); if (userState.mEnabledServices.remove(componentName)) { @@ -2060,45 +2066,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return mFingerprintGestureDispatcher.onFingerprintGesture(gestureKeyCode); } - private class SettingsStringHelper { - private static final String SETTINGS_DELIMITER = ":"; - private ContentResolver mContentResolver; - private final String mSettingsName; - private Set<String> mServices; - private final int mUserId; - - public SettingsStringHelper(String name, int userId) { - mUserId = userId; - mSettingsName = name; - mContentResolver = mContext.getContentResolver(); - String servicesString = Settings.Secure.getStringForUser( - mContentResolver, mSettingsName, userId); - mServices = new HashSet(); - if (!TextUtils.isEmpty(servicesString)) { - final TextUtils.SimpleStringSplitter colonSplitter = - new TextUtils.SimpleStringSplitter(SETTINGS_DELIMITER.charAt(0)); - colonSplitter.setString(servicesString); - while (colonSplitter.hasNext()) { - final String serviceName = colonSplitter.next(); - mServices.add(serviceName); - } - } - } - - public void addService(ComponentName component) { - mServices.add(component.flattenToString()); - } - - public void deleteService(ComponentName component) { - mServices.remove(component.flattenToString()); - } - - public void writeToSettings() { - Settings.Secure.putStringForUser(mContentResolver, mSettingsName, - TextUtils.join(SETTINGS_DELIMITER, mServices), mUserId); - } - } - @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3eebd8968d99..83e209dd5bba 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -154,6 +154,8 @@ public final class SystemServer { "com.android.server.voiceinteraction.VoiceInteractionManagerService"; private static final String PRINT_MANAGER_SERVICE_CLASS = "com.android.server.print.PrintManagerService"; + private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS = + "com.android.server.print.CompanionDeviceManagerService"; private static final String USB_SERVICE_CLASS = "com.android.server.usb.UsbService$Lifecycle"; private static final String MIDI_SERVICE_CLASS = @@ -1350,6 +1352,10 @@ public final class SystemServer { traceEnd(); } + traceBeginAndSlog("StartCompanionDeviceManager"); + mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS); + traceEnd(); + traceBeginAndSlog("StartRestrictionManager"); mSystemServiceManager.startService(RestrictionsManagerService.class); traceEnd(); diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java new file mode 100644 index 000000000000..9824c1d12613 --- /dev/null +++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java @@ -0,0 +1,147 @@ +/* + * 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.print; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.app.PendingIntent; +import android.companion.AssociationRequest; +import android.companion.ICompanionDeviceManager; +import android.companion.ICompanionDeviceManagerService; +import android.companion.IOnAssociateCallback; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.server.SystemService; + +//TODO move to own package! +/** @hide */ +public class CompanionDeviceManagerService extends SystemService { + + private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative( + "com.android.companiondevicemanager", ".DeviceDiscoveryService"); + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "CompanionDeviceManagerService"; + + private final CompanionDeviceManagerImpl mImpl; + + public CompanionDeviceManagerService(Context context) { + super(context); + mImpl = new CompanionDeviceManagerImpl(); + } + + @Override + public void onStart() { + publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl); + } + + class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { + + @Override + public void associate( + AssociationRequest request, + IOnAssociateCallback callback, + String callingPackage) throws RemoteException { + if (DEBUG) { + Log.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback + + ", callingPackage = " + callingPackage + ")"); + } + checkNotNull(request); + checkNotNull(callback); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + //TODO bindServiceAsUser + getContext().bindService( + new Intent().setComponent(SERVICE_TO_BIND_TO), + getServiceConnection(request, callback, callingPackage), + Context.BIND_AUTO_CREATE); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + } + + private ServiceConnection getServiceConnection( + final AssociationRequest<?> request, + final IOnAssociateCallback callback, + final String callingPackage) { + return new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) { + Log.i(LOG_TAG, + "onServiceConnected(name = " + name + ", service = " + + service + ")"); + } + try { + ICompanionDeviceManagerService.Stub + .asInterface(service) + .startDiscovery( + request, + getCallback(callingPackage, callback), + callingPackage); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Log.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")"); + } + }; + } + + private IOnAssociateCallback.Stub getCallback( + String callingPackage, + IOnAssociateCallback propagateTo) { + return new IOnAssociateCallback.Stub() { + + @Override + public void onSuccess(PendingIntent launcher) + throws RemoteException { + if (DEBUG) Log.i(LOG_TAG, "onSuccess(launcher = " + launcher + ")"); + recordSpecialPriviledgesForPackage(callingPackage); + propagateTo.onSuccess(launcher); + } + + @Override + public void onFailure(CharSequence reason) throws RemoteException { + if (DEBUG) Log.i(LOG_TAG, "onFailure()"); + propagateTo.onFailure(reason); + } + }; + } + + void recordSpecialPriviledgesForPackage(String priviledgedPackage) { + //TODO Show dialog before recording notification access +// final SettingStringHelper setting = +// new SettingStringHelper( +// getContext().getContentResolver(), +// Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, +// Binder.getCallingUid()); +// setting.write(ColonDelimitedSet.OfStrings.add(setting.read(), priviledgedPackage)); + } +} |