| /* |
| * Copyright 2021 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.nearby; |
| |
| import android.Manifest; |
| import android.annotation.CallbackExecutor; |
| import android.annotation.FlaggedApi; |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.RequiresPermission; |
| import android.annotation.SuppressLint; |
| import android.annotation.SystemApi; |
| import android.annotation.SystemService; |
| import android.bluetooth.BluetoothManager; |
| import android.content.Context; |
| import android.location.LocationManager; |
| import android.nearby.aidl.IOffloadCallback; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.provider.Settings; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.util.Preconditions; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.ref.WeakReference; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.WeakHashMap; |
| import java.util.concurrent.Executor; |
| import java.util.function.Consumer; |
| |
| /** |
| * This class provides a way to perform Nearby related operations such as scanning, broadcasting |
| * and connecting to nearby devices. |
| * |
| * <p> To get a {@link NearbyManager} instance, call the |
| * <code>Context.getSystemService(NearbyManager.class)</code>. |
| * |
| * @hide |
| */ |
| @SystemApi |
| @SystemService(Context.NEARBY_SERVICE) |
| public class NearbyManager { |
| |
| /** |
| * Represents the scanning state. |
| * |
| * @hide |
| */ |
| @IntDef({ |
| ScanStatus.UNKNOWN, |
| ScanStatus.SUCCESS, |
| ScanStatus.ERROR, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface ScanStatus { |
| // The undetermined status, some modules may be initializing. Retry is suggested. |
| int UNKNOWN = 0; |
| // The successful state. |
| int SUCCESS = 1; |
| // Failed state. |
| int ERROR = 2; |
| } |
| |
| /** |
| * Return value of {@link #getPoweredOffFindingMode()} when this powered off finding is not |
| * supported the device. |
| */ |
| @FlaggedApi("com.android.nearby.flags.powered_off_finding") |
| public static final int POWERED_OFF_FINDING_MODE_UNSUPPORTED = 0; |
| |
| /** |
| * Return value of {@link #getPoweredOffFindingMode()} and argument of {@link |
| * #setPoweredOffFindingMode(int)} when powered off finding is supported but disabled. The |
| * device will not start to advertise when powered off. |
| */ |
| @FlaggedApi("com.android.nearby.flags.powered_off_finding") |
| public static final int POWERED_OFF_FINDING_MODE_DISABLED = 1; |
| |
| /** |
| * Return value of {@link #getPoweredOffFindingMode()} and argument of {@link |
| * #setPoweredOffFindingMode(int)} when powered off finding is enabled. The device will start to |
| * advertise when powered off. |
| */ |
| @FlaggedApi("com.android.nearby.flags.powered_off_finding") |
| public static final int POWERED_OFF_FINDING_MODE_ENABLED = 2; |
| |
| /** |
| * Powered off finding modes. |
| * |
| * @hide |
| */ |
| @IntDef( |
| prefix = {"POWERED_OFF_FINDING_MODE"}, |
| value = { |
| POWERED_OFF_FINDING_MODE_UNSUPPORTED, |
| POWERED_OFF_FINDING_MODE_DISABLED, |
| POWERED_OFF_FINDING_MODE_ENABLED, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface PoweredOffFindingMode {} |
| |
| private static final String TAG = "NearbyManager"; |
| |
| private static final int POWERED_OFF_FINDING_EID_LENGTH = 20; |
| |
| private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY = |
| "ro.bluetooth.finder.supported"; |
| |
| /** |
| * TODO(b/286137024): Remove this when CTS R5 is rolled out. |
| * Whether allows Fast Pair to scan. |
| * |
| * (0 = disabled, 1 = enabled) |
| * |
| * @hide |
| */ |
| public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled"; |
| |
| @GuardedBy("sScanListeners") |
| private static final WeakHashMap<ScanCallback, WeakReference<ScanListenerTransport>> |
| sScanListeners = new WeakHashMap<>(); |
| @GuardedBy("sBroadcastListeners") |
| private static final WeakHashMap<BroadcastCallback, WeakReference<BroadcastListenerTransport>> |
| sBroadcastListeners = new WeakHashMap<>(); |
| |
| private final Context mContext; |
| private final INearbyManager mService; |
| |
| /** |
| * Creates a new NearbyManager. |
| * |
| * @param service the service object |
| */ |
| NearbyManager(@NonNull Context context, @NonNull INearbyManager service) { |
| Objects.requireNonNull(context); |
| Objects.requireNonNull(service); |
| mContext = context; |
| mService = service; |
| } |
| |
| // This can be null when NearbyDeviceParcelable field not set for Presence device |
| // or the scan type is not recognized. |
| @Nullable |
| private static NearbyDevice toClientNearbyDevice( |
| NearbyDeviceParcelable nearbyDeviceParcelable, |
| @ScanRequest.ScanType int scanType) { |
| if (scanType == ScanRequest.SCAN_TYPE_FAST_PAIR) { |
| return new FastPairDevice.Builder() |
| .setName(nearbyDeviceParcelable.getName()) |
| .addMedium(nearbyDeviceParcelable.getMedium()) |
| .setRssi(nearbyDeviceParcelable.getRssi()) |
| .setTxPower(nearbyDeviceParcelable.getTxPower()) |
| .setModelId(nearbyDeviceParcelable.getFastPairModelId()) |
| .setBluetoothAddress(nearbyDeviceParcelable.getBluetoothAddress()) |
| .setData(nearbyDeviceParcelable.getData()).build(); |
| } |
| |
| if (scanType == ScanRequest.SCAN_TYPE_NEARBY_PRESENCE) { |
| PresenceDevice presenceDevice = nearbyDeviceParcelable.getPresenceDevice(); |
| if (presenceDevice == null) { |
| Log.e(TAG, |
| "Cannot find any Presence device in discovered NearbyDeviceParcelable"); |
| } |
| return presenceDevice; |
| } |
| return null; |
| } |
| |
| /** |
| * Start scan for nearby devices with given parameters. Devices matching {@link ScanRequest} |
| * will be delivered through the given callback. |
| * |
| * @param scanRequest various parameters clients send when requesting scanning |
| * @param executor executor where the listener method is called |
| * @param scanCallback the callback to notify clients when there is a scan result |
| * |
| * @return whether scanning was successfully started |
| */ |
| @RequiresPermission(allOf = {android.Manifest.permission.BLUETOOTH_SCAN, |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED}) |
| @ScanStatus |
| public int startScan(@NonNull ScanRequest scanRequest, |
| @CallbackExecutor @NonNull Executor executor, |
| @NonNull ScanCallback scanCallback) { |
| Objects.requireNonNull(scanRequest, "scanRequest must not be null"); |
| Objects.requireNonNull(scanCallback, "scanCallback must not be null"); |
| Objects.requireNonNull(executor, "executor must not be null"); |
| |
| try { |
| synchronized (sScanListeners) { |
| WeakReference<ScanListenerTransport> reference = sScanListeners.get(scanCallback); |
| ScanListenerTransport transport = reference != null ? reference.get() : null; |
| if (transport == null) { |
| transport = new ScanListenerTransport(scanRequest.getScanType(), scanCallback, |
| executor); |
| } else { |
| Preconditions.checkState(transport.isRegistered()); |
| transport.setExecutor(executor); |
| } |
| @ScanStatus int status = mService.registerScanListener(scanRequest, transport, |
| mContext.getPackageName(), mContext.getAttributionTag()); |
| if (status != ScanStatus.SUCCESS) { |
| return status; |
| } |
| sScanListeners.put(scanCallback, new WeakReference<>(transport)); |
| return ScanStatus.SUCCESS; |
| } |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Stops the nearby device scan for the specified callback. The given callback |
| * is guaranteed not to receive any invocations that happen after this method |
| * is invoked. |
| * |
| * Suppressed lint: Registration methods should have overload that accepts delivery Executor. |
| * Already have executor in startScan() method. |
| * |
| * @param scanCallback the callback that was used to start the scan |
| */ |
| @SuppressLint("ExecutorRegistration") |
| @RequiresPermission(allOf = {android.Manifest.permission.BLUETOOTH_SCAN, |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED}) |
| public void stopScan(@NonNull ScanCallback scanCallback) { |
| Preconditions.checkArgument(scanCallback != null, |
| "invalid null scanCallback"); |
| try { |
| synchronized (sScanListeners) { |
| WeakReference<ScanListenerTransport> reference = sScanListeners.remove( |
| scanCallback); |
| ScanListenerTransport transport = reference != null ? reference.get() : null; |
| if (transport != null) { |
| transport.unregister(); |
| mService.unregisterScanListener(transport, mContext.getPackageName(), |
| mContext.getAttributionTag()); |
| } else { |
| Log.e(TAG, "Cannot stop scan with this callback " |
| + "because it is never registered."); |
| } |
| } |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Start broadcasting the request using nearby specification. |
| * |
| * @param broadcastRequest request for the nearby broadcast |
| * @param executor executor for running the callback |
| * @param callback callback for notifying the client |
| */ |
| @RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADVERTISE, |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED}) |
| public void startBroadcast(@NonNull BroadcastRequest broadcastRequest, |
| @CallbackExecutor @NonNull Executor executor, @NonNull BroadcastCallback callback) { |
| try { |
| synchronized (sBroadcastListeners) { |
| WeakReference<BroadcastListenerTransport> reference = sBroadcastListeners.get( |
| callback); |
| BroadcastListenerTransport transport = reference != null ? reference.get() : null; |
| if (transport == null) { |
| transport = new BroadcastListenerTransport(callback, executor); |
| } else { |
| Preconditions.checkState(transport.isRegistered()); |
| transport.setExecutor(executor); |
| } |
| mService.startBroadcast(new BroadcastRequestParcelable(broadcastRequest), transport, |
| mContext.getPackageName(), mContext.getAttributionTag()); |
| sBroadcastListeners.put(callback, new WeakReference<>(transport)); |
| } |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Stop the broadcast associated with the given callback. |
| * |
| * @param callback the callback that was used for starting the broadcast |
| */ |
| @SuppressLint("ExecutorRegistration") |
| @RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADVERTISE, |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED}) |
| public void stopBroadcast(@NonNull BroadcastCallback callback) { |
| try { |
| synchronized (sBroadcastListeners) { |
| WeakReference<BroadcastListenerTransport> reference = sBroadcastListeners.remove( |
| callback); |
| BroadcastListenerTransport transport = reference != null ? reference.get() : null; |
| if (transport != null) { |
| transport.unregister(); |
| mService.stopBroadcast(transport, mContext.getPackageName(), |
| mContext.getAttributionTag()); |
| } else { |
| Log.e(TAG, "Cannot stop broadcast with this callback " |
| + "because it is never registered."); |
| } |
| } |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Query offload capability in a device. The query is asynchronous and result is called back |
| * in {@link Consumer}, which is set to true if offload is supported. |
| * |
| * @param executor the callback will take place on this {@link Executor} |
| * @param callback the callback invoked with {@link OffloadCapability} |
| */ |
| public void queryOffloadCapability(@NonNull @CallbackExecutor Executor executor, |
| @NonNull Consumer<OffloadCapability> callback) { |
| Objects.requireNonNull(executor); |
| Objects.requireNonNull(callback); |
| try { |
| mService.queryOffloadCapability(new OffloadTransport(executor, callback)); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| private static class OffloadTransport extends IOffloadCallback.Stub { |
| |
| private final Executor mExecutor; |
| // Null when cancelled |
| volatile @Nullable Consumer<OffloadCapability> mConsumer; |
| |
| OffloadTransport(Executor executor, Consumer<OffloadCapability> consumer) { |
| Preconditions.checkArgument(executor != null, "illegal null executor"); |
| Preconditions.checkArgument(consumer != null, "illegal null consumer"); |
| mExecutor = executor; |
| mConsumer = consumer; |
| } |
| |
| @Override |
| public void onQueryComplete(OffloadCapability capability) { |
| mExecutor.execute(() -> { |
| if (mConsumer != null) { |
| mConsumer.accept(capability); |
| } |
| }); |
| } |
| } |
| |
| private static class ScanListenerTransport extends IScanListener.Stub { |
| |
| private @ScanRequest.ScanType int mScanType; |
| private volatile @Nullable ScanCallback mScanCallback; |
| private Executor mExecutor; |
| |
| ScanListenerTransport(@ScanRequest.ScanType int scanType, ScanCallback scanCallback, |
| @CallbackExecutor Executor executor) { |
| Preconditions.checkArgument(scanCallback != null, |
| "invalid null callback"); |
| Preconditions.checkState(ScanRequest.isValidScanType(scanType), |
| "invalid scan type : " + scanType |
| + ", scan type must be one of ScanRequest#SCAN_TYPE_"); |
| mScanType = scanType; |
| mScanCallback = scanCallback; |
| mExecutor = executor; |
| } |
| |
| void setExecutor(Executor executor) { |
| Preconditions.checkArgument( |
| executor != null, "invalid null executor"); |
| mExecutor = executor; |
| } |
| |
| boolean isRegistered() { |
| return mScanCallback != null; |
| } |
| |
| void unregister() { |
| mScanCallback = null; |
| } |
| |
| @Override |
| public void onDiscovered(NearbyDeviceParcelable nearbyDeviceParcelable) |
| throws RemoteException { |
| mExecutor.execute(() -> { |
| NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType); |
| if (mScanCallback != null && nearbyDevice != null) { |
| mScanCallback.onDiscovered(nearbyDevice); |
| } |
| }); |
| } |
| |
| @Override |
| public void onUpdated(NearbyDeviceParcelable nearbyDeviceParcelable) |
| throws RemoteException { |
| mExecutor.execute(() -> { |
| NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType); |
| if (mScanCallback != null && nearbyDevice != null) { |
| mScanCallback.onUpdated( |
| toClientNearbyDevice(nearbyDeviceParcelable, mScanType)); |
| } |
| }); |
| } |
| |
| @Override |
| public void onLost(NearbyDeviceParcelable nearbyDeviceParcelable) throws RemoteException { |
| mExecutor.execute(() -> { |
| NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType); |
| if (mScanCallback != null && nearbyDevice != null) { |
| mScanCallback.onLost( |
| toClientNearbyDevice(nearbyDeviceParcelable, mScanType)); |
| } |
| }); |
| } |
| |
| @Override |
| public void onError(int errorCode) { |
| mExecutor.execute(() -> { |
| if (mScanCallback != null) { |
| mScanCallback.onError(errorCode); |
| } |
| }); |
| } |
| } |
| |
| private static class BroadcastListenerTransport extends IBroadcastListener.Stub { |
| private volatile @Nullable BroadcastCallback mBroadcastCallback; |
| private Executor mExecutor; |
| |
| BroadcastListenerTransport(BroadcastCallback broadcastCallback, |
| @CallbackExecutor Executor executor) { |
| mBroadcastCallback = broadcastCallback; |
| mExecutor = executor; |
| } |
| |
| void setExecutor(Executor executor) { |
| Preconditions.checkArgument( |
| executor != null, "invalid null executor"); |
| mExecutor = executor; |
| } |
| |
| boolean isRegistered() { |
| return mBroadcastCallback != null; |
| } |
| |
| void unregister() { |
| mBroadcastCallback = null; |
| } |
| |
| @Override |
| public void onStatusChanged(int status) { |
| mExecutor.execute(() -> { |
| if (mBroadcastCallback != null) { |
| mBroadcastCallback.onStatusChanged(status); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * TODO(b/286137024): Remove this when CTS R5 is rolled out. |
| * Read from {@link Settings} whether Fast Pair scan is enabled. |
| * |
| * @param context the {@link Context} to query the setting |
| * @return whether the Fast Pair is enabled |
| * @hide |
| */ |
| public static boolean getFastPairScanEnabled(@NonNull Context context) { |
| final int enabled = Settings.Secure.getInt( |
| context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, 0); |
| return enabled != 0; |
| } |
| |
| /** |
| * TODO(b/286137024): Remove this when CTS R5 is rolled out. |
| * Write into {@link Settings} whether Fast Pair scan is enabled |
| * |
| * @param context the {@link Context} to set the setting |
| * @param enable whether the Fast Pair scan should be enabled |
| * @hide |
| */ |
| @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) |
| public static void setFastPairScanEnabled(@NonNull Context context, boolean enable) { |
| Settings.Secure.putInt( |
| context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, enable ? 1 : 0); |
| Log.v(TAG, String.format( |
| "successfully %s Fast Pair scan", enable ? "enables" : "disables")); |
| } |
| |
| /** |
| * Sets the precomputed EIDs for advertising when the phone is powered off. The Bluetooth |
| * controller will store these EIDs in its memory, and will start advertising them in Find My |
| * Device network EID frames when powered off, only if the powered off finding mode was |
| * previously enabled by calling {@link #setPoweredOffFindingMode(int)}. |
| * |
| * <p>The EIDs are cryptographic ephemeral identifiers that change periodically, based on the |
| * Android clock at the time of the shutdown. They are used as the public part of asymmetric key |
| * pairs. Members of the Find My Device network can use them to encrypt the location of where |
| * they sight the advertising device. Only someone in possession of the private key (the device |
| * owner or someone that the device owner shared the key with) can decrypt this encrypted |
| * location. |
| * |
| * <p>Android will typically call this method during the shutdown process. Even after the |
| * method was called, it is still possible to call {#link setPoweredOffFindingMode() to disable |
| * the advertisement, for example to temporarily disable it for a single shutdown. |
| * |
| * <p>If called more than once, the EIDs of the most recent call overrides the EIDs from any |
| * previous call. |
| * |
| * @throws IllegalArgumentException if the length of one of the EIDs is not 20 bytes |
| */ |
| @FlaggedApi("com.android.nearby.flags.powered_off_finding") |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) |
| public void setPoweredOffFindingEphemeralIds(@NonNull List<byte[]> eids) { |
| Objects.requireNonNull(eids); |
| if (!isPoweredOffFindingSupported()) { |
| throw new UnsupportedOperationException( |
| "Powered off finding is not supported on this device"); |
| } |
| List<PoweredOffFindingEphemeralId> ephemeralIdList = eids.stream().map( |
| eid -> { |
| Preconditions.checkArgument(eid.length == POWERED_OFF_FINDING_EID_LENGTH); |
| PoweredOffFindingEphemeralId ephemeralId = new PoweredOffFindingEphemeralId(); |
| ephemeralId.bytes = eid; |
| return ephemeralId; |
| }).toList(); |
| try { |
| mService.setPoweredOffFindingEphemeralIds(ephemeralIdList); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| |
| } |
| |
| /** |
| * Turns the powered off finding on or off. Power off finding will operate only if this method |
| * was called at least once since boot, and the value of the argument {@code |
| * poweredOffFindinMode} was {@link #POWERED_OFF_FINDING_MODE_ENABLED} the last time the method |
| * was called. |
| * |
| * <p>When an Android device with the powered off finding feature is turned off (either as part |
| * of a normal shutdown or due to dead battery), its Bluetooth chip starts to advertise Find My |
| * Device network EID frames with the EID payload that were provided by the last call to {@link |
| * #setPoweredOffFindingEphemeralIds(List)}. These EIDs can be sighted by other Android devices |
| * in BLE range that are part of the Find My Device network. The Android sighters use the EID to |
| * encrypt the location of the Android device and upload it to the server, in a way that only |
| * the owner of the advertising device, or people that the owner shared their encryption key |
| * with, can decrypt the location. |
| * |
| * @param poweredOffFindingMode {@link #POWERED_OFF_FINDING_MODE_ENABLED} or {@link |
| * #POWERED_OFF_FINDING_MODE_DISABLED} |
| * |
| * @throws IllegalStateException if called with {@link #POWERED_OFF_FINDING_MODE_ENABLED} when |
| * Bluetooth or location services are disabled |
| */ |
| @FlaggedApi("com.android.nearby.flags.powered_off_finding") |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) |
| public void setPoweredOffFindingMode(@PoweredOffFindingMode int poweredOffFindingMode) { |
| Preconditions.checkArgument( |
| poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED |
| || poweredOffFindingMode == POWERED_OFF_FINDING_MODE_DISABLED, |
| "invalid poweredOffFindingMode"); |
| if (!isPoweredOffFindingSupported()) { |
| throw new UnsupportedOperationException( |
| "Powered off finding is not supported on this device"); |
| } |
| if (poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED) { |
| Preconditions.checkState(areLocationAndBluetoothEnabled(), |
| "Location services and Bluetooth must be on"); |
| } |
| try { |
| mService.setPoweredOffModeEnabled( |
| poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Returns the state of the powered off finding feature. |
| * |
| * <p>{@link #POWERED_OFF_FINDING_MODE_UNSUPPORTED} if the feature is not supported by the |
| * device, {@link #POWERED_OFF_FINDING_MODE_DISABLED} if this was the last value set by {@link |
| * #setPoweredOffFindingMode(int)} or if no value was set since boot, {@link |
| * #POWERED_OFF_FINDING_MODE_ENABLED} if this was the last value set by {@link |
| * #setPoweredOffFindingMode(int)} |
| */ |
| @FlaggedApi("com.android.nearby.flags.powered_off_finding") |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) |
| public @PoweredOffFindingMode int getPoweredOffFindingMode() { |
| if (!isPoweredOffFindingSupported()) { |
| return POWERED_OFF_FINDING_MODE_UNSUPPORTED; |
| } |
| try { |
| return mService.getPoweredOffModeEnabled() |
| ? POWERED_OFF_FINDING_MODE_ENABLED : POWERED_OFF_FINDING_MODE_DISABLED; |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| private boolean isPoweredOffFindingSupported() { |
| return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY)); |
| } |
| |
| private boolean areLocationAndBluetoothEnabled() { |
| return mContext.getSystemService(BluetoothManager.class).getAdapter().isEnabled() |
| && mContext.getSystemService(LocationManager.class).isLocationEnabled(); |
| } |
| } |