Merge "Add Injector for DiscoveryProviderManager"
diff --git a/nearby/apex/Android.bp b/nearby/apex/Android.bp
index 540be44..b277ac4 100644
--- a/nearby/apex/Android.bp
+++ b/nearby/apex/Android.bp
@@ -40,6 +40,7 @@
java_libs: [
"service-nearby",
],
+ apps: ["HalfSheetUX"],
}
filegroup {
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index ba97112..1f1dac5 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -65,6 +65,7 @@
installable: false,
visibility: [
"//packages/modules/Nearby/service",
+ "//packages/modules/Nearby/halfsheet",
"//packages/modules/Nearby/tests",
],
}
diff --git a/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl b/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl
new file mode 100644
index 0000000..43d31d1
--- /dev/null
+++ b/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl
@@ -0,0 +1,24 @@
+// Copyright (C) 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;
+
+/**
+ * Provides callback interface for halfsheet to send FastPair call back.
+ *
+ * {@hide}
+ */
+interface IFastPairHalfSheetCallback {
+ void onHalfSheetConnectionConfirm();
+ }
\ No newline at end of file
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
new file mode 100644
index 0000000..a1a90fb
--- /dev/null
+++ b/nearby/halfsheet/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+ name: "HalfSheetDefaults",
+ srcs: ["src/**/*.java"],
+ sdk_version: "module_current",
+ min_sdk_version: "current",
+ target_sdk_version: "current",
+ manifest: "AndroidManifest.xml",
+ plugins: ["java_api_finder"],
+ jarjar_rules: ":nearby-jarjar-rules",
+ libs: [
+ "framework-nearby-pre-jarjar",
+ "nearby-service-string",
+ ],
+ static_libs: [
+ "androidx.annotation_annotation"
+ ],
+ lint: { strict_updatability_linting: true }
+}
+
+android_app {
+ name: "HalfSheetUX",
+ defaults: ["HalfSheetDefaults"],
+ resource_dirs: ["res"],
+ certificate: ":com.android.nearby.halfsheetcertificate",
+ updatable: true,
+ apex_available: ["com.android.nearby",],
+}
+
+android_app_certificate {
+ name: "com.android.nearby.halfsheetcertificate",
+ certificate: "apk-certs/com.android.nearby.halfsheet"
+}
+
diff --git a/nearby/halfsheet/AndroidManifest.xml b/nearby/halfsheet/AndroidManifest.xml
new file mode 100644
index 0000000..3e8b39b
--- /dev/null
+++ b/nearby/halfsheet/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.nearby.halfsheet">
+ <application android:label="@string/app_name">
+ <activity
+ android:name="com.android.nearby.halfsheet.HalfSheetActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.nearby.SHOW_HALFSHEET"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8 b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8
new file mode 100644
index 0000000..187d51e
--- /dev/null
+++ b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8
Binary files differ
diff --git a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem
new file mode 100644
index 0000000..440c524
--- /dev/null
+++ b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----
diff --git a/nearby/halfsheet/apk-certs/key.pem b/nearby/halfsheet/apk-certs/key.pem
new file mode 100644
index 0000000..e9f4288
--- /dev/null
+++ b/nearby/halfsheet/apk-certs/key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+ayrX7pbsnW6jsNYBUlck+RYVYkVkxA==
+-----END PRIVATE KEY-----
diff --git a/nearby/halfsheet/res/layout/activity_half_sheet.xml b/nearby/halfsheet/res/layout/activity_half_sheet.xml
new file mode 100644
index 0000000..4915d4b
--- /dev/null
+++ b/nearby/halfsheet/res/layout/activity_half_sheet.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="abc half sheet" />
+ </LinearLayout>
+
+</FrameLayout>
+
diff --git a/nearby/halfsheet/res/values/strings.xml b/nearby/halfsheet/res/values/strings.xml
new file mode 100644
index 0000000..22f78d7
--- /dev/null
+++ b/nearby/halfsheet/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 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.
+ -->
+
+<resources>
+ <string name="app_name">Nearby HalfSheet Dialog</string>
+</resources>
\ No newline at end of file
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
new file mode 100644
index 0000000..4a74c9b
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 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 com.android.nearby.halfsheet;
+
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
+
+import android.app.Activity;
+import android.nearby.IFastPairHalfSheetCallback;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Half sheet activity to show pairing ux.
+ */
+public class HalfSheetActivity extends Activity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_half_sheet);
+ Bundle bundle = getIntent().getBundleExtra(EXTRA_BUNDLE);
+ // Any app that has the component name can start the activity so the binder can't be
+ // trusted.
+ try {
+ IFastPairHalfSheetCallback.Stub.asInterface(bundle.getBinder(EXTRA_BINDER))
+ .onHalfSheetConnectionConfirm();
+ } catch (RemoteException e) {
+ Log.d("FastPairHalfSheet", "invoke callback fall");
+ }
+ }
+}
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index 4a8ab0b..63e2748 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -23,6 +23,20 @@
],
}
+filegroup {
+ name: "nearby-service-string-res",
+ srcs: [
+ "java/**/Constant.java",
+ ],
+}
+
+java_library {
+ name: "nearby-service-string",
+ srcs: [":nearby-service-string-res"],
+ sdk_version: "module_current",
+}
+
+
// Main lib for nearby services.
java_library {
name: "service-nearby",
@@ -44,6 +58,10 @@
"guava",
"libprotobuf-java-lite",
"fast-pair-lite-protos",
+ "modules-utils-build",
+ "modules-utils-handlerexecutor",
+ "modules-utils-preconditions",
+ "modules-utils-backgroundthread",
],
sdk_version: "system_server_current",
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 764978f..464d469 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -22,7 +22,7 @@
import com.android.server.SystemService;
import com.android.server.nearby.common.locator.LocatorContextWrapper;
import com.android.server.nearby.fastpair.FastPairManager;
-
+import com.android.server.nearby.provider.FastPairDataProvider;
/**
* Service implementing nearby functionality. The actual implementation is delegated to
@@ -30,9 +30,11 @@
*/
// TODO(189954300): Implement nearby service.
public class NearbyService extends SystemService {
+
private static final String TAG = "NearbyService";
private static final boolean DBG = true;
private final NearbyServiceImpl mImpl;
+ private final Context mContext;
private final FastPairManager mFastPairManager;
private LocatorContextWrapper mLocatorContextWrapper;
@@ -40,6 +42,7 @@
public NearbyService(Context contextBase) {
super(contextBase);
mImpl = new NearbyServiceImpl(contextBase);
+ mContext = contextBase;
mLocatorContextWrapper = new LocatorContextWrapper(contextBase, null);
mFastPairManager = new FastPairManager(mLocatorContextWrapper);
}
@@ -54,7 +57,13 @@
@Override
public void onBootPhase(int phase) {
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ onSystemThirdPartyAppsCanStart();
+ }
}
-
-}
\ No newline at end of file
+ private void onSystemThirdPartyAppsCanStart() {
+ // Ensures that a fast pair data provider exists which will work in direct boot.
+ FastPairDataProvider.init(mContext);
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
index 2658bec..b57e6ad 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
@@ -2096,11 +2096,7 @@
}
private static void disableBle(android.bluetooth.BluetoothAdapter adapter) {
- try {
- Reflect.on(adapter).withMethod("disableBLE").invoke();
- } catch (ReflectionException e) {
- Log.i(TAG, "Can't call disableBLE", e);
- }
+ adapter.disableBLE();
}
/**
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java
new file mode 100644
index 0000000..80248e8
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.common.servicemonitor;
+
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceChangedListener;
+import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceProvider;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * This is mostly borrowed from frameworks CurrentUserServiceSupplier.
+ * Provides services based on the current active user and version as defined in the service
+ * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
+ * ensure only system (ie, privileged) services are matched. It also handles services that are not
+ * direct boot aware, and will automatically pick the best service as the user's direct boot state
+ * changes.
+ */
+public final class CurrentUserServiceProvider extends BroadcastReceiver implements
+ ServiceProvider<CurrentUserServiceProvider.BoundServiceInfo> {
+
+ private static final String TAG = "CurrentUserServiceProvider";
+
+ private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+
+ // This is equal to the hidden Intent.ACTION_USER_SWITCHED.
+ private static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED";
+ // This is equal to the hidden Intent.EXTRA_USER_HANDLE.
+ private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
+ // This is equal to the hidden UserHandle.USER_NULL.
+ private static final int USER_NULL = -10000;
+
+ private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
+ if (o1 == o2) {
+ return 0;
+ } else if (o1 == null) {
+ return -1;
+ } else if (o2 == null) {
+ return 1;
+ }
+
+ // ServiceInfos with higher version numbers always win.
+ return Integer.compare(o1.getVersion(), o2.getVersion());
+ };
+
+ /** Bound service information with version information. */
+ public static class BoundServiceInfo extends ServiceMonitor.BoundServiceInfo {
+
+ private static int parseUid(ResolveInfo resolveInfo) {
+ return resolveInfo.serviceInfo.applicationInfo.uid;
+ }
+
+ private static int parseVersion(ResolveInfo resolveInfo) {
+ int version = Integer.MIN_VALUE;
+ if (resolveInfo.serviceInfo.metaData != null) {
+ version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
+ }
+ return version;
+ }
+
+ private final int mVersion;
+
+ protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+ this(
+ action,
+ parseUid(resolveInfo),
+ new ComponentName(
+ resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name),
+ parseVersion(resolveInfo));
+ }
+
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName,
+ int version) {
+ super(action, uid, componentName);
+ mVersion = version;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "@" + mVersion;
+ }
+ }
+
+ /**
+ * Creates an instance with the specific service details.
+ *
+ * @param context the context the provider is to use
+ * @param action the action the service must declare in its intent-filter
+ */
+ public static CurrentUserServiceProvider create(Context context, String action) {
+ return new CurrentUserServiceProvider(context, action);
+ }
+
+ private final Context mContext;
+ private final Intent mIntent;
+ private volatile ServiceChangedListener mListener;
+
+ private CurrentUserServiceProvider(Context context, String action) {
+ mContext = context;
+ mIntent = new Intent(action);
+ }
+
+ @Override
+ public boolean hasMatchingService() {
+ int intentQueryFlags =
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY;
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
+ mIntent, intentQueryFlags, UserHandle.SYSTEM);
+ return !resolveInfos.isEmpty();
+ }
+
+ @Override
+ public void register(ServiceChangedListener listener) {
+ Preconditions.checkState(mListener == null);
+
+ mListener = listener;
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiverForAllUsers(this, intentFilter, null,
+ ForegroundThread.getHandler());
+ }
+
+ @Override
+ public void unregister() {
+ Preconditions.checkArgument(mListener != null);
+
+ mListener = null;
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public BoundServiceInfo getServiceInfo() {
+ BoundServiceInfo bestServiceInfo = null;
+
+ // only allow services in the correct direct boot state to match
+ int intentQueryFlags = MATCH_DIRECT_BOOT_AUTO | GET_META_DATA | MATCH_SYSTEM_ONLY;
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
+ mIntent, intentQueryFlags, UserHandle.of(ActivityManager.getCurrentUser()));
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ BoundServiceInfo serviceInfo =
+ new BoundServiceInfo(mIntent.getAction(), resolveInfo);
+
+ if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
+ bestServiceInfo = serviceInfo;
+ }
+ }
+
+ return bestServiceInfo;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL);
+ if (userId == USER_NULL) {
+ return;
+ }
+ ServiceChangedListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ switch (action) {
+ case ACTION_USER_SWITCHED:
+ listener.onServiceChanged();
+ break;
+ case Intent.ACTION_USER_UNLOCKED:
+ // user unlocked implies direct boot mode may have changed
+ if (userId == ActivityManager.getCurrentUser()) {
+ listener.onServiceChanged();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java
new file mode 100644
index 0000000..2c363f8
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.common.servicemonitor;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.modules.utils.HandlerExecutor;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Thread for asynchronous event processing. This thread is configured as
+ * {@link android.os.Process#THREAD_PRIORITY_FOREGROUND}, which means more CPU
+ * resources will be dedicated to it, and it will be treated like "a user
+ * interface that the user is interacting with."
+ * <p>
+ * This thread is best suited for tasks that the user is actively waiting for,
+ * or for tasks that the user expects to be executed immediately.
+ *
+ */
+public final class ForegroundThread extends HandlerThread {
+ private static ForegroundThread sInstance;
+ private static Handler sHandler;
+ private static HandlerExecutor sHandlerExecutor;
+
+ private ForegroundThread() {
+ super("nearbyfg", android.os.Process.THREAD_PRIORITY_FOREGROUND);
+ }
+
+ private static void ensureThreadLocked() {
+ if (sInstance == null) {
+ sInstance = new ForegroundThread();
+ sInstance.start();
+ sHandler = new Handler(sInstance.getLooper());
+ sHandlerExecutor = new HandlerExecutor(sHandler);
+ }
+ }
+
+ /** Get ForegroundThread singleton instance. */
+ public static ForegroundThread get() {
+ synchronized (ForegroundThread.class) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ /** Get ForegroundThread singleton handler. */
+ public static Handler getHandler() {
+ synchronized (ForegroundThread.class) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+
+ /** Get ForegroundThread singleton executor. */
+ public static Executor getExecutor() {
+ synchronized (ForegroundThread.class) {
+ ensureThreadLocked();
+ return sHandlerExecutor;
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java
new file mode 100644
index 0000000..7d1db57
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.common.servicemonitor;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.modules.utils.BackgroundThread;
+
+import java.util.Objects;
+
+/**
+ * This is mostly from frameworks PackageMonitor.
+ * Helper class for watching somePackagesChanged.
+ */
+public abstract class PackageWatcher extends BroadcastReceiver {
+ static final String TAG = "PackageWatcher";
+ static final IntentFilter sPackageFilt = new IntentFilter();
+ static final IntentFilter sNonDataFilt = new IntentFilter();
+ static final IntentFilter sExternalFilt = new IntentFilter();
+
+ static {
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ sPackageFilt.addDataScheme("package");
+ sNonDataFilt.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+ sNonDataFilt.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ }
+
+ Context mRegisteredContext;
+ Handler mRegisteredHandler;
+ boolean mSomePackagesChanged;
+
+ public PackageWatcher() {
+ }
+
+ void register(Context context, Looper thread, boolean externalStorage) {
+ register(context, externalStorage,
+ (thread == null) ? BackgroundThread.getHandler() : new Handler(thread));
+ }
+
+ void register(Context context, boolean externalStorage, Handler handler) {
+ if (mRegisteredContext != null) {
+ throw new IllegalStateException("Already registered");
+ }
+ mRegisteredContext = context;
+ mRegisteredHandler = Objects.requireNonNull(handler);
+ context.registerReceiverForAllUsers(this, sPackageFilt, null, mRegisteredHandler);
+ context.registerReceiverForAllUsers(this, sNonDataFilt, null, mRegisteredHandler);
+ if (externalStorage) {
+ context.registerReceiverForAllUsers(this, sExternalFilt, null, mRegisteredHandler);
+ }
+ }
+
+ void unregister() {
+ if (mRegisteredContext == null) {
+ throw new IllegalStateException("Not registered");
+ }
+ mRegisteredContext.unregisterReceiver(this);
+ mRegisteredContext = null;
+ }
+
+ // Called when some package has been changed.
+ abstract void onSomePackagesChanged();
+
+ String getPackageName(Intent intent) {
+ Uri uri = intent.getData();
+ String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
+ return pkg;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mSomePackagesChanged = false;
+
+ String action = intent.getAction();
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ // We consider something to have changed regardless of whether
+ // this is just an update, because the update is now finished
+ // and the contents of the package may have changed.
+ mSomePackagesChanged = true;
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ String pkg = getPackageName(intent);
+ if (pkg != null) {
+ if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ mSomePackagesChanged = true;
+ }
+ }
+ } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ String pkg = getPackageName(intent);
+ if (pkg != null) {
+ mSomePackagesChanged = true;
+ }
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ mSomePackagesChanged = true;
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ mSomePackagesChanged = true;
+ } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) {
+ mSomePackagesChanged = true;
+ } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) {
+ mSomePackagesChanged = true;
+ }
+
+ if (mSomePackagesChanged) {
+ onSomePackagesChanged();
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java
new file mode 100644
index 0000000..a86af85
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.common.servicemonitor;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * This is exported from frameworks ServiceWatcher.
+ * A ServiceMonitor is responsible for continuously maintaining an active binding to a service
+ * selected by it's {@link ServiceProvider}. The {@link ServiceProvider} may change the service it
+ * selects over time, and the currently bound service may crash, restart, have a user change, have
+ * changes made to its package, and so on and so forth. The ServiceMonitor is responsible for
+ * maintaining the binding across all these changes.
+ *
+ * <p>Clients may invoke {@link BinderOperation}s on the ServiceMonitor, and it will make a best
+ * effort to run these on the currently bound service, but individual operations may fail (if there
+ * is no service currently bound for instance). In order to help clients maintain the correct state,
+ * clients may supply a {@link ServiceListener}, which is informed when the ServiceMonitor connects
+ * and disconnects from a service. This allows clients to bring a bound service back into a known
+ * state on connection, and then run binder operations from there. In order to help clients
+ * accomplish this, ServiceMonitor guarantees that {@link BinderOperation}s and the
+ * {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
+ * can be established between them.
+ *
+ * There is never any guarantee of whether a ServiceMonitor is currently connected to a service, and
+ * whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
+ * on this, and instead use {@link ServiceListener} notifications as necessary to recover from
+ * failures.
+ */
+public interface ServiceMonitor {
+
+ /**
+ * Operation to run on a binder interface. All operations will be run on the thread used by the
+ * ServiceMonitor this is run with.
+ */
+ interface BinderOperation {
+ /** Invoked to run the operation. Run on the ServiceMonitor thread. */
+ void run(IBinder binder) throws RemoteException;
+
+ /**
+ * Invoked if {@link #run(IBinder)} could not be invoked because there was no current
+ * binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
+ * {@link RuntimeException}). This callback is only intended for resource deallocation and
+ * cleanup in response to a single binder operation, it should not be used to propagate
+ * errors further. Run on the ServiceMonitor thread.
+ */
+ default void onError() {}
+ }
+
+ /**
+ * Listener for bind and unbind events. All operations will be run on the thread used by the
+ * ServiceMonitor this is run with.
+ *
+ * @param <TBoundServiceInfo> type of bound service
+ */
+ interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
+ /** Invoked when a service is bound. Run on the ServiceMonitor thread. */
+ void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
+
+ /** Invoked when a service is unbound. Run on the ServiceMonitor thread. */
+ void onUnbind();
+ }
+
+ /**
+ * A listener for when a {@link ServiceProvider} decides that the current service has changed.
+ */
+ interface ServiceChangedListener {
+ /**
+ * Should be invoked when the current service may have changed.
+ */
+ void onServiceChanged();
+ }
+
+ /**
+ * This provider encapsulates the logic of deciding what service a {@link ServiceMonitor} should
+ * be bound to at any given moment.
+ *
+ * @param <TBoundServiceInfo> type of bound service
+ */
+ interface ServiceProvider<TBoundServiceInfo extends BoundServiceInfo> {
+ /**
+ * Should return true if there exists at least one service capable of meeting the criteria
+ * of this provider. This does not imply that {@link #getServiceInfo()} will always return a
+ * non-null result, as any service may be disqualified for various reasons at any point in
+ * time. May be invoked at any time from any thread and thus should generally not have any
+ * dependency on the other methods in this interface.
+ */
+ boolean hasMatchingService();
+
+ /**
+ * Invoked when the provider should start monitoring for any changes that could result in a
+ * different service selection, and should invoke
+ * {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
+ * may be invoked after this method is called.
+ */
+ void register(ServiceChangedListener listener);
+
+ /**
+ * Invoked when the provider should stop monitoring for any changes that could result in a
+ * different service selection, should no longer invoke
+ * {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
+ * invoked after this method is called.
+ */
+ void unregister();
+
+ /**
+ * Must be implemented to return the current service selected by this provider. May return
+ * null if no service currently meets the criteria. Only invoked while registered.
+ */
+ @Nullable TBoundServiceInfo getServiceInfo();
+ }
+
+ /**
+ * Information on the service selected as the best option for binding.
+ */
+ class BoundServiceInfo {
+
+ protected final @Nullable String mAction;
+ protected final int mUid;
+ protected final ComponentName mComponentName;
+
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
+ mAction = action;
+ mUid = uid;
+ mComponentName = Objects.requireNonNull(componentName);
+ }
+
+ /** Returns the action associated with this bound service. */
+ public @Nullable String getAction() {
+ return mAction;
+ }
+
+ /** Returns the component of this bound service. */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof BoundServiceInfo)) {
+ return false;
+ }
+
+ BoundServiceInfo that = (BoundServiceInfo) o;
+ return mUid == that.mUid
+ && Objects.equals(mAction, that.mAction)
+ && mComponentName.equals(that.mComponentName);
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(mAction, mUid, mComponentName);
+ }
+
+ @Override
+ public String toString() {
+ if (mComponentName == null) {
+ return "none";
+ } else {
+ return mUid + "/" + mComponentName.flattenToShortString();
+ }
+ }
+ }
+
+ /**
+ * Creates a new ServiceMonitor instance.
+ */
+ static <TBoundServiceInfo extends BoundServiceInfo> ServiceMonitor create(
+ Context context,
+ String tag,
+ ServiceProvider<TBoundServiceInfo> serviceProvider,
+ @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ return create(context, ForegroundThread.getHandler(), ForegroundThread.getExecutor(), tag,
+ serviceProvider, serviceListener);
+ }
+
+ /**
+ * Creates a new ServiceMonitor instance that runs on the given handler.
+ */
+ static <TBoundServiceInfo extends BoundServiceInfo> ServiceMonitor create(
+ Context context,
+ Handler handler,
+ Executor executor,
+ String tag,
+ ServiceProvider<TBoundServiceInfo> serviceProvider,
+ @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ return new ServiceMonitorImpl<>(context, handler, executor, tag, serviceProvider,
+ serviceListener);
+ }
+
+ /**
+ * Returns true if there is at least one service that the ServiceMonitor could hypothetically
+ * bind to, as selected by the {@link ServiceProvider}.
+ */
+ boolean checkServiceResolves();
+
+ /**
+ * Registers the ServiceMonitor, so that it will begin maintaining an active binding to the
+ * service selected by {@link ServiceProvider}, until {@link #unregister()} is called.
+ */
+ void register();
+
+ /**
+ * Unregisters the ServiceMonitor, so that it will release any active bindings. If the
+ * ServiceMonitor is currently bound, this will result in one final
+ * {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
+ * (but which is guaranteed to occur before any further
+ * {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
+ * call to {@link #register()}).
+ */
+ void unregister();
+
+ /**
+ * Runs the given binder operation on the currently bound service (if available). The operation
+ * will always fail if the ServiceMonitor is not currently registered.
+ */
+ void runOnBinder(BinderOperation operation);
+
+ /**
+ * Dumps ServiceMonitor information.
+ */
+ void dump(PrintWriter pw);
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java
new file mode 100644
index 0000000..d0d6c3b
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.common.servicemonitor;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.nearby.common.servicemonitor.ServiceMonitor.BoundServiceInfo;
+import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceChangedListener;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of ServiceMonitor. Keeping the implementation separate from the interface allows
+ * us to store the generic relationship between the service provider and the service listener, while
+ * hiding the generics from clients, simplifying the API.
+ */
+class ServiceMonitorImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceMonitor,
+ ServiceChangedListener {
+
+ private static final String TAG = "ServiceMonitor";
+ private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
+ private static final long RETRY_DELAY_MS = 15 * 1000;
+
+ // This is the same as Context.BIND_NOT_VISIBLE.
+ private static final int BIND_NOT_VISIBLE = 0x40000000;
+
+ final Context mContext;
+ final Handler mHandler;
+ final Executor mExecutor;
+ final String mTag;
+ final ServiceProvider<TBoundServiceInfo> mServiceProvider;
+ final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener;
+
+ private final PackageWatcher mPackageWatcher = new PackageWatcher() {
+ @Override
+ public void onSomePackagesChanged() {
+ onServiceChanged(false);
+ }
+ };
+
+ @GuardedBy("this")
+ private boolean mRegistered = false;
+ @GuardedBy("this")
+ private MyServiceConnection mServiceConnection = new MyServiceConnection(null);
+
+ ServiceMonitorImpl(Context context, Handler handler, Executor executor, String tag,
+ ServiceProvider<TBoundServiceInfo> serviceProvider,
+ ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ mContext = context;
+ mExecutor = executor;
+ mHandler = handler;
+ mTag = tag;
+ mServiceProvider = serviceProvider;
+ mServiceListener = serviceListener;
+ }
+
+ @Override
+ public boolean checkServiceResolves() {
+ return mServiceProvider.hasMatchingService();
+ }
+
+ @Override
+ public synchronized void register() {
+ Preconditions.checkState(!mRegistered);
+
+ mRegistered = true;
+ mPackageWatcher.register(mContext, /*externalStorage=*/ true, mHandler);
+ mServiceProvider.register(this);
+
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void unregister() {
+ Preconditions.checkState(mRegistered);
+
+ mServiceProvider.unregister();
+ mPackageWatcher.unregister();
+ mRegistered = false;
+
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void onServiceChanged() {
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void runOnBinder(BinderOperation operation) {
+ MyServiceConnection serviceConnection = mServiceConnection;
+ mHandler.post(() -> serviceConnection.runOnBinder(operation));
+ }
+
+ synchronized void onServiceChanged(boolean forceRebind) {
+ TBoundServiceInfo newBoundServiceInfo;
+ if (mRegistered) {
+ newBoundServiceInfo = mServiceProvider.getServiceInfo();
+ } else {
+ newBoundServiceInfo = null;
+ }
+
+ if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
+ newBoundServiceInfo)) {
+ Log.i(TAG, "[" + mTag + "] chose new implementation " + newBoundServiceInfo);
+ MyServiceConnection oldServiceConnection = mServiceConnection;
+ MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
+ mServiceConnection = newServiceConnection;
+ mHandler.post(() -> {
+ oldServiceConnection.unbind();
+ newServiceConnection.bind();
+ });
+ }
+ }
+
+ @Override
+ public String toString() {
+ MyServiceConnection serviceConnection;
+ synchronized (this) {
+ serviceConnection = mServiceConnection;
+ }
+
+ return serviceConnection.getBoundServiceInfo().toString();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ MyServiceConnection serviceConnection;
+ synchronized (this) {
+ serviceConnection = mServiceConnection;
+ }
+
+ pw.println("target service=" + serviceConnection.getBoundServiceInfo());
+ pw.println("connected=" + serviceConnection.isConnected());
+ }
+
+ // runs on the handler thread, and expects most of its methods to be called from that thread
+ private class MyServiceConnection implements ServiceConnection {
+
+ private final @Nullable TBoundServiceInfo mBoundServiceInfo;
+
+ // volatile so that isConnected can be called from any thread easily
+ private volatile @Nullable IBinder mBinder;
+ private @Nullable Runnable mRebinder;
+
+ MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) {
+ mBoundServiceInfo = boundServiceInfo;
+ }
+
+ // may be called from any thread
+ @Nullable TBoundServiceInfo getBoundServiceInfo() {
+ return mBoundServiceInfo;
+ }
+
+ // may be called from any thread
+ boolean isConnected() {
+ return mBinder != null;
+ }
+
+ void bind() {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBoundServiceInfo == null) {
+ return;
+ }
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
+ }
+
+ Intent bindIntent = new Intent(mBoundServiceInfo.getAction())
+ .setComponent(mBoundServiceInfo.getComponentName());
+ if (!mContext.bindService(bindIntent,
+ BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+ mExecutor, this)) {
+ Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+ mRebinder = this::bind;
+ mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+ } else {
+ mRebinder = null;
+ }
+ }
+
+ void unbind() {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBoundServiceInfo == null) {
+ return;
+ }
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo);
+ }
+
+ if (mRebinder != null) {
+ mHandler.removeCallbacks(mRebinder);
+ mRebinder = null;
+ } else {
+ mContext.unbindService(this);
+ }
+
+ onServiceDisconnected(mBoundServiceInfo.getComponentName());
+ }
+
+ void runOnBinder(BinderOperation operation) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBinder == null) {
+ operation.onError();
+ return;
+ }
+
+ try {
+ operation.run(mBinder);
+ } catch (RuntimeException | RemoteException e) {
+ // binders may propagate some specific non-RemoteExceptions from the other side
+ // through the binder as well - we cannot allow those to crash the system server
+ Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+ operation.onError();
+ }
+ }
+
+ @Override
+ public final void onServiceConnected(ComponentName component, IBinder binder) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+ Preconditions.checkState(mBinder == null);
+
+ Log.i(TAG, "[" + mTag + "] connected to " + component.toShortString());
+
+ mBinder = binder;
+
+ if (mServiceListener != null) {
+ try {
+ mServiceListener.onBind(binder, mBoundServiceInfo);
+ } catch (RuntimeException | RemoteException e) {
+ // binders may propagate some specific non-RemoteExceptions from the other side
+ // through the binder as well - we cannot allow those to crash the system server
+ Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+ }
+ }
+ }
+
+ @Override
+ public final void onServiceDisconnected(ComponentName component) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBinder == null) {
+ return;
+ }
+
+ Log.i(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
+
+ mBinder = null;
+ if (mServiceListener != null) {
+ mServiceListener.onUnbind();
+ }
+ }
+
+ @Override
+ public final void onBindingDied(ComponentName component) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ Log.w(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
+
+ // introduce a small delay to prevent spamming binding over and over, since the likely
+ // cause of a binding dying is some package event that may take time to recover from
+ mHandler.postDelayed(() -> onServiceChanged(true), 500);
+ }
+
+ @Override
+ public final void onNullBinding(ComponentName component) {
+ Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
new file mode 100644
index 0000000..bf73360
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.fastpair;
+
+/**
+ * String constant for half sheet.
+ */
+public class Constant {
+ public static final String EXTRA_BINDER = "com.android.server.nearby.fastpair.BINDER";
+ public static final String EXTRA_BUNDLE = "com.android.server.nearby.fastpair.BUNDLE_EXTRA";
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
index ff4bce2..56650ce 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -16,17 +16,29 @@
package com.android.server.nearby.fastpair;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
+
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.KeyguardManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.nearby.NearbyDevice;
+import android.nearby.ScanCallback;
+import android.os.Bundle;
+import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.server.nearby.common.bluetooth.BluetoothException;
import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
import com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection;
@@ -42,9 +54,12 @@
import com.android.server.nearby.fastpair.cache.DiscoveryItem;
import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.HalfSheetCallback;
import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
+import com.android.server.nearby.util.Environment;
import java.security.GeneralSecurityException;
+import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@@ -52,6 +67,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
import service.proto.Rpcs;
@@ -63,13 +79,18 @@
public class FastPairManager {
private static final String ACTION_PREFIX = UserActionHandler.PREFIX;
private static final int WAIT_FOR_UNLOCK_MILLIS = 5000;
+ private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET";
/** A notification ID which should be dismissed*/
public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID";
+ public static final String ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET";
+
private static Executor sFastPairExecutor;
- LocatorContextWrapper mLocatorContextWrapper;
- IntentFilter mIntentFilter;
- Locator mLocator;
+ private String mHalfSheetApkPkgName;
+
+ final LocatorContextWrapper mLocatorContextWrapper;
+ final IntentFilter mIntentFilter;
+ final Locator mLocator;
private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -91,12 +112,29 @@
Rpcs.GetObservedDeviceResponse.newBuilder().build();
}
+ final ScanCallback mScanCallback = new ScanCallback() {
+ @Override
+ public void onDiscovered(@NonNull NearbyDevice device) {
+ Log.d("FastPair", "Ondiscovery " + device.getName());
+ }
+
+ @Override
+ public void onUpdated(@NonNull NearbyDevice device) {
+
+ }
+
+ @Override
+ public void onLost(@NonNull NearbyDevice device) {
+
+ }
+ };
/**
* Function called when nearby service start.
*/
public void initiate() {
mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
mLocatorContextWrapper.getContext()
.registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
@@ -110,7 +148,6 @@
mLocatorContextWrapper.getContext().unregisterReceiver(mScreenBroadcastReceiver);
}
-
/**
* Starts fast pair process.
*/
@@ -251,6 +288,9 @@
}
}
+ /**
+ * This function should only be called on main thread since there is no lock
+ */
private static Executor getExecutor() {
if (sFastPairExecutor != null) {
return sFastPairExecutor;
@@ -267,4 +307,64 @@
BluetoothManager manager = context.getSystemService(BluetoothManager.class);
return manager == null ? null : manager.getAdapter();
}
+
+ /** Gets the package name of HalfSheet.apk
+ * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have
+ * race condition check. Since there is no lock for mHalfSheetApkPkgName.
+ */
+ String getHalfSheetApkPkgName() {
+ if (mHalfSheetApkPkgName != null) {
+ return mHalfSheetApkPkgName;
+ }
+ List<ResolveInfo> resolveInfos = mLocatorContextWrapper.getContext()
+ .getPackageManager().queryIntentActivities(
+ new Intent(ACTION_RESOURCES_APK),
+ PackageManager.MATCH_SYSTEM_ONLY);
+
+ // remove apps that don't live in the nearby apex
+ resolveInfos.removeIf(info ->
+ !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo));
+
+ if (resolveInfos.isEmpty()) {
+ // Resource APK not loaded yet, print a stack trace to see where this is called from
+ Log.e("FastPairManager", "Attempted to fetch resources before halfsheet "
+ + " APK is installed or package manager can't resolve correctly!",
+ new IllegalStateException());
+ return null;
+ }
+
+ if (resolveInfos.size() > 1) {
+ // multiple apps found, log a warning, but continue
+ Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: "
+ + resolveInfos.stream()
+ .map(info -> info.activityInfo.applicationInfo.packageName)
+ .collect(Collectors.joining(", ")));
+ }
+
+ // Assume the first ResolveInfo is the one we're looking for
+ ResolveInfo info = resolveInfos.get(0);
+ mHalfSheetApkPkgName = info.activityInfo.applicationInfo.packageName;
+ Log.i("FastPairManager", "Found halfsheet APK at: " + mHalfSheetApkPkgName);
+ return mHalfSheetApkPkgName;
+ }
+
+ /**
+ * Invokes half sheet in the other apk. This function can only be called in Nearby because other
+ * app can't get the correct component name.
+ */
+ void showHalfSheet() {
+ if (mLocatorContextWrapper != null) {
+ String packageName = getHalfSheetApkPkgName();
+ HalfSheetCallback callback = new HalfSheetCallback();
+ Bundle bundle = new Bundle();
+ bundle.putBinder(EXTRA_BINDER, callback);
+ mLocatorContextWrapper.getContext()
+ .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
+ .putExtra(EXTRA_BUNDLE, bundle)
+ .setComponent(new ComponentName(packageName,
+ packageName + ".HalfSheetActivity")),
+ UserHandle.CURRENT);
+
+ }
+ }
}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java
new file mode 100644
index 0000000..fd9e460
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.fastpair.halfsheet;
+
+import android.nearby.IFastPairHalfSheetCallback;
+import android.util.Log;
+
+/**
+ * Callback to send ux action back to nearby service.
+ */
+public class HalfSheetCallback extends IFastPairHalfSheetCallback.Stub {
+ @Override
+ public void onHalfSheetConnectionConfirm() {
+ Log.d("FastPairHalfSheet", "Call back receiver");
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
new file mode 100644
index 0000000..cf9629b
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.provider;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.WorkerThread;
+
+import service.proto.Rpcs;
+
+/** FastPairDataProvider is a singleton that implements APIs to get FastPair data. */
+public class FastPairDataProvider {
+
+ private static final String TAG = "FastPairDataProvider";
+ // TODO(204780849): move this to system api.
+ private static final String ACTION_FAST_PAIR_DATA_PROVIDER =
+ "com.android.nearby.service.FastPairDataProvider";
+ private static FastPairDataProvider sInstance;
+
+ private ProxyFastPairDataProvider mProxyProvider;
+
+ /** Initializes FastPairDataProvider singleton. */
+ public static synchronized FastPairDataProvider init(Context context) {
+ if (sInstance == null) {
+ sInstance = new FastPairDataProvider(context);
+ }
+ if (sInstance.mProxyProvider == null) {
+ Log.wtf(TAG, "no proxy fast pair data provider found");
+ } else {
+ sInstance.mProxyProvider.register();
+ }
+ return sInstance;
+ }
+
+ @Nullable
+ public static synchronized FastPairDataProvider getInstance() {
+ return sInstance;
+ }
+
+ private FastPairDataProvider(Context context) {
+ mProxyProvider = ProxyFastPairDataProvider.create(context, ACTION_FAST_PAIR_DATA_PROVIDER);
+ }
+
+ /** loadFastPairDeviceMetadata. */
+ @WorkerThread
+ @Nullable
+ public Rpcs.GetObservedDeviceResponse loadFastPairDeviceMetadata(byte[] modelId) {
+ if (mProxyProvider != null) {
+ return mProxyProvider.loadFastPairDeviceMetadata(modelId);
+ }
+ throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java
new file mode 100644
index 0000000..073518b
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.provider;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.nearby.FastPairDeviceMetadataParcel;
+import android.nearby.FastPairDeviceMetadataRequestParcel;
+import android.nearby.IFastPairDataCallback;
+import android.nearby.IFastPairDataProvider;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.WorkerThread;
+
+import com.android.server.nearby.common.servicemonitor.CurrentUserServiceProvider;
+import com.android.server.nearby.common.servicemonitor.CurrentUserServiceProvider.BoundServiceInfo;
+import com.android.server.nearby.common.servicemonitor.ServiceMonitor;
+import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceListener;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import service.proto.Rpcs;
+
+/**
+ * Proxy for IFastPairDataProvider implementations.
+ */
+public class ProxyFastPairDataProvider implements ServiceListener<BoundServiceInfo> {
+
+ /**
+ * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+ * null.
+ */
+ @Nullable
+ public static ProxyFastPairDataProvider create(Context context, String action) {
+ ProxyFastPairDataProvider proxy = new ProxyFastPairDataProvider(context, action);
+ if (proxy.checkServiceResolves()) {
+ return proxy;
+ } else {
+ return null;
+ }
+ }
+
+ private final ServiceMonitor mServiceMonitor;
+
+ private ProxyFastPairDataProvider(Context context, String action) {
+ // safe to use direct executor since our locks are not acquired in a code path invoked by
+ // our owning provider
+
+ mServiceMonitor = ServiceMonitor.create(context, "FAST_PAIR_DATA_PROVIDER",
+ CurrentUserServiceProvider.create(context, action), this);
+ }
+
+ private boolean checkServiceResolves() {
+ return mServiceMonitor.checkServiceResolves();
+ }
+
+ /** User service watch to connect to actually services implemented by OEMs. */
+ public void register() {
+ mServiceMonitor.register();
+ }
+
+ // Fast Pair Data Provider doesn't maintain a long running state.
+ // Therefore, it doesn't need setup at bind time.
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
+ }
+
+ // Fast Pair Data Provider doesn't maintain a long running state.
+ // Therefore, it doesn't need tear down at unbind time.
+ @Override
+ public void onUnbind() {
+ }
+
+ /** Invoke loadFastPairDeviceMetadata. */
+ @WorkerThread
+ @Nullable
+ Rpcs.GetObservedDeviceResponse loadFastPairDeviceMetadata(byte[] modelId) {
+ final CountDownLatch waitForCompletionLatch = new CountDownLatch(1);
+ final AtomicReference<Rpcs.GetObservedDeviceResponse> response = new AtomicReference<>();
+ mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder);
+ FastPairDeviceMetadataRequestParcel requestParcel =
+ new FastPairDeviceMetadataRequestParcel();
+ requestParcel.modelId = modelId;
+ IFastPairDataCallback callback = new IFastPairDataCallback.Stub() {
+ public void onFastPairDeviceMetadataReceived(
+ FastPairDeviceMetadataParcel metadata) {
+ response.set(Utils.convert(metadata));
+ waitForCompletionLatch.countDown();
+ }
+ };
+ provider.loadFastPairDeviceMetadata(requestParcel, callback);
+ }
+
+ @Override
+ public void onError() {
+ waitForCompletionLatch.countDown();
+ }
+ });
+ try {
+ waitForCompletionLatch.await(10000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // skip.
+ }
+ return response.get();
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/Utils.java b/nearby/service/java/com/android/server/nearby/provider/Utils.java
new file mode 100644
index 0000000..115dfee
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.provider;
+
+import android.nearby.FastPairDeviceMetadataParcel;
+
+import com.google.protobuf.ByteString;
+
+import service.proto.Rpcs;
+
+class Utils {
+
+ static Rpcs.GetObservedDeviceResponse convert(FastPairDeviceMetadataParcel metadata) {
+ return Rpcs.GetObservedDeviceResponse.newBuilder()
+ .setDevice(
+ Rpcs.Device.newBuilder()
+ .setImageUrl(metadata.imageUrl)
+ .setIntentUri(metadata.intentUri)
+ .setAntiSpoofingKeyPair(
+ Rpcs.AntiSpoofingKeyPair.newBuilder()
+ .setPublicKey(ByteString.copyFrom(metadata.antiSpoofPublicKey))
+ .build())
+ .setBleTxPower(metadata.bleTxPower)
+ .setTriggerDistance(metadata.triggerDistance)
+ .setDeviceType(Rpcs.DeviceType.forNumber(metadata.deviceType))
+ .build())
+ .setImage(ByteString.copyFrom(metadata.image))
+ .build();
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/Environment.java b/nearby/service/java/com/android/server/nearby/util/Environment.java
new file mode 100644
index 0000000..dc131e7
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/Environment.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 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 com.android.server.nearby.util;
+
+import android.content.ApexEnvironment;
+import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
+
+import java.io.File;
+
+/**
+ * Provides function to make sure the function caller is from the same apex.
+ */
+public class Environment {
+ /**
+ * NEARBY apex name.
+ */
+ private static final String NEARBY_APEX_NAME = "com.android.nearby";
+
+ /**
+ * The path where the Nearby apex is mounted.
+ * Current value = "/apex/com.android.nearby"
+ */
+ private static final String NEARBY_APEX_PATH =
+ new File("/apex", NEARBY_APEX_NAME).getAbsolutePath();
+
+ /**
+ * Nearby shared folder.
+ */
+ public static File getNearbyDirectory() {
+ return ApexEnvironment.getApexEnvironment(NEARBY_APEX_NAME).getDeviceProtectedDataDir();
+ }
+
+ /**
+ * Nearby user specific folder.
+ */
+ public static File getNearbyDirectory(int userId) {
+ return ApexEnvironment.getApexEnvironment(NEARBY_APEX_NAME)
+ .getCredentialProtectedDataDirForUser(UserHandle.of(userId));
+ }
+
+ /**
+ * Returns true if the app is in the nearby apex, false otherwise.
+ * Checks if the app's path starts with "/apex/com.android.nearby".
+ */
+ public static boolean isAppInNearbyApex(ApplicationInfo appInfo) {
+ return appInfo.sourceDir.startsWith(NEARBY_APEX_PATH);
+ }
+}