Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ed90488 authored by Greg Kaiser's avatar Greg Kaiser Committed by Android (Google) Code Review
Browse files

Merge "Revert "[CDM][Refactoring 4/N] Move device presence logic out of..."" into main

parents 170c34a6 8e73ebe7
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -1086,7 +1086,7 @@ public final class CompanionDeviceManager {
        }
        Objects.requireNonNull(deviceAddress, "address cannot be null");
        try {
            mService.legacyStartObservingDevicePresence(deviceAddress,
            mService.registerDevicePresenceListenerService(deviceAddress,
                    mContext.getOpPackageName(), mContext.getUserId());
        } catch (RemoteException e) {
            ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1128,7 +1128,7 @@ public final class CompanionDeviceManager {
        }
        Objects.requireNonNull(deviceAddress, "address cannot be null");
        try {
            mService.legacyStopObservingDevicePresence(deviceAddress,
            mService.unregisterDevicePresenceListenerService(deviceAddress,
                    mContext.getPackageName(), mContext.getUserId());
        } catch (RemoteException e) {
            ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1328,7 +1328,7 @@ public final class CompanionDeviceManager {
    @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
    public void notifyDeviceAppeared(int associationId) {
        try {
            mService.notifySelfManagedDeviceAppeared(associationId);
            mService.notifyDeviceAppeared(associationId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -1350,7 +1350,7 @@ public final class CompanionDeviceManager {
    @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
    public void notifyDeviceDisappeared(int associationId) {
        try {
            mService.notifySelfManagedDeviceDisappeared(associationId);
            mService.notifyDeviceDisappeared(associationId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
+12 −12
Original line number Diff line number Diff line
@@ -59,16 +59,12 @@ interface ICompanionDeviceManager {
        int userId);

    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
    void legacyStartObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);

    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
    void legacyStopObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);

    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
    void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
    void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
        int userId);

    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
    void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
    void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
        int userId);

    boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);

@@ -97,11 +93,9 @@ interface ICompanionDeviceManager {
    @EnforcePermission("USE_COMPANION_TRANSPORTS")
    void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);

    @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
    void notifySelfManagedDeviceAppeared(int associationId);
    void notifyDeviceAppeared(int associationId);

    @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
    void notifySelfManagedDeviceDisappeared(int associationId);
    void notifyDeviceDisappeared(int associationId);

    PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId,
        int associationId);
@@ -141,4 +135,10 @@ interface ICompanionDeviceManager {
    byte[] getBackupPayload(int userId);

    void applyRestoredPayload(in byte[] payload, int userId);

    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
    void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);

    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
    void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
}
+567 −0
Original line number Diff line number Diff line
@@ -14,7 +14,9 @@
 * limitations under the License.
 */

package com.android.server.companion.presence;
package com.android.server.companion;

import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -25,7 +27,9 @@ import android.companion.CompanionDeviceService;
import android.companion.DevicePresenceEvent;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.power.Mode;
import android.os.Handler;
import android.os.ParcelUuid;
import android.os.PowerManagerInternal;
import android.util.Log;
import android.util.Slog;
@@ -33,8 +37,10 @@ import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.PerUser;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
import com.android.server.companion.utils.PackageUtils;

import java.io.PrintWriter;
@@ -54,61 +60,56 @@ import java.util.Map;
 * application process.
 *
 * <p>
 * The following is the list of the APIs provided by {@link CompanionAppBinder} (to be
 * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
 * utilized by {@link CompanionDeviceManagerService}):
 * <ul>
 * <li> {@link #bindCompanionApplication(int, String, boolean, CompanionServiceConnector.Listener)}
 * <li> {@link #bindCompanionApplication(int, String, boolean)}
 * <li> {@link #unbindCompanionApplication(int, String)}
 * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)}
 * <li> {@link #isCompanionApplicationBound(int, String)}
 * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
 * </ul>
 *
 * @see CompanionDeviceService
 * @see android.companion.ICompanionDeviceService
 * @see CompanionServiceConnector
 * @see CompanionDeviceServiceConnector
 */
@SuppressLint("LongLogTag")
public class CompanionAppBinder {
    private static final String TAG = "CDM_CompanionAppBinder";
public class CompanionApplicationController {
    static final boolean DEBUG = false;
    private static final String TAG = "CDM_CompanionApplicationController";

    private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec

    @NonNull
    private final Context mContext;
    @NonNull
    private final AssociationStore mAssociationStore;
    @NonNull
    private final ObservableUuidStore mObservableUuidStore;
    @NonNull
    private final CompanionServicesRegister mCompanionServicesRegister;
    private final @NonNull Context mContext;
    private final @NonNull AssociationStore mAssociationStore;
    private final @NonNull ObservableUuidStore mObservableUuidStore;
    private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
    private final @NonNull CompanionServicesRegister mCompanionServicesRegister;

    private final PowerManagerInternal mPowerManagerInternal;

    @NonNull
    @GuardedBy("mBoundCompanionApplications")
    private final AndroidPackageMap<List<CompanionServiceConnector>>
    private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
            mBoundCompanionApplications;
    @NonNull
    @GuardedBy("mScheduledForRebindingCompanionApplications")
    private final AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
    private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;

    public CompanionAppBinder(@NonNull Context context,
            @NonNull AssociationStore associationStore,
            @NonNull ObservableUuidStore observableUuidStore,
            @NonNull PowerManagerInternal powerManagerInternal) {
    CompanionApplicationController(Context context, AssociationStore associationStore,
            ObservableUuidStore observableUuidStore,
            CompanionDevicePresenceMonitor companionDevicePresenceMonitor,
            PowerManagerInternal powerManagerInternal) {
        mContext = context;
        mAssociationStore = associationStore;
        mObservableUuidStore =  observableUuidStore;
        mDevicePresenceMonitor = companionDevicePresenceMonitor;
        mPowerManagerInternal = powerManagerInternal;
        mCompanionServicesRegister = new CompanionServicesRegister();
        mBoundCompanionApplications = new AndroidPackageMap<>();
        mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
    }

    /**
     * On package changed.
     */
    public void onPackagesChanged(@UserIdInt int userId) {
    void onPackagesChanged(@UserIdInt int userId) {
        mCompanionServicesRegister.invalidate(userId);
    }

@@ -116,14 +117,16 @@ public class CompanionAppBinder {
     * CDM binds to the companion app.
     */
    public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
            boolean isSelfManaged, CompanionServiceConnector.Listener listener) {
        Slog.i(TAG, "Binding user=[" + userId + "], package=[" + packageName + "], isSelfManaged=["
                + isSelfManaged + "]...");
            boolean isSelfManaged) {
        if (DEBUG) {
            Log.i(TAG, "bind() u" + userId + "/" + packageName
                    + " isSelfManaged=" + isSelfManaged);
        }

        final List<ComponentName> companionServices =
                mCompanionServicesRegister.forPackage(userId, packageName);
        if (companionServices.isEmpty()) {
            Slog.e(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
            Slog.w(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
                    + "eligible CompanionDeviceService not found.\n"
                    + "A CompanionDeviceService should declare an intent-filter for "
                    + "\"android.companion.CompanionDeviceService\" action and require "
@@ -131,16 +134,16 @@ public class CompanionAppBinder {
            return;
        }

        final List<CompanionServiceConnector> serviceConnectors = new ArrayList<>();
        final List<CompanionDeviceServiceConnector> serviceConnectors = new ArrayList<>();
        synchronized (mBoundCompanionApplications) {
            if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
                Slog.w(TAG, "The package is ALREADY bound.");
                if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound.");
                return;
            }

            for (int i = 0; i < companionServices.size(); i++) {
                boolean isPrimary = i == 0;
                serviceConnectors.add(CompanionServiceConnector.newInstance(mContext, userId,
                serviceConnectors.add(CompanionDeviceServiceConnector.newInstance(mContext, userId,
                        companionServices.get(i), isSelfManaged, isPrimary));
            }

@@ -148,12 +151,12 @@ public class CompanionAppBinder {
        }

        // Set listeners for both Primary and Secondary connectors.
        for (CompanionServiceConnector serviceConnector : serviceConnectors) {
            serviceConnector.setListener(listener);
        for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
            serviceConnector.setListener(this::onBinderDied);
        }

        // Now "bind" all the connectors: the primary one and the rest of them.
        for (CompanionServiceConnector serviceConnector : serviceConnectors) {
        for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
            serviceConnector.connect();
        }
    }
@@ -162,9 +165,9 @@ public class CompanionAppBinder {
     * CDM unbinds the companion app.
     */
    public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
        Slog.i(TAG, "Unbinding user=[" + userId + "], package=[" + packageName + "]...");
        if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);

        final List<CompanionServiceConnector> serviceConnectors;
        final List<CompanionDeviceServiceConnector> serviceConnectors;

        synchronized (mBoundCompanionApplications) {
            serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName);
@@ -175,11 +178,15 @@ public class CompanionAppBinder {
        }

        if (serviceConnectors == null) {
            Slog.e(TAG, "The package is not bound.");
            if (DEBUG) {
                Log.e(TAG, "unbindCompanionApplication(): "
                        + "u" + userId + "/" + packageName + " is NOT bound");
                Log.d(TAG, "Stacktrace", new Throwable());
            }
            return;
        }

        for (CompanionServiceConnector serviceConnector : serviceConnectors) {
        for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
            serviceConnector.postUnbind();
        }
    }
@@ -193,25 +200,15 @@ public class CompanionAppBinder {
        }
    }

    /**
     * Remove bound apps for package.
     */
    public void removePackage(int userId, String packageName) {
        synchronized (mBoundCompanionApplications) {
            mBoundCompanionApplications.removePackage(userId, packageName);
        }
    }

    /**
     * Schedule rebinding for the package.
     */
    public void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
            CompanionServiceConnector serviceConnector) {
    private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
            CompanionDeviceServiceConnector serviceConnector) {
        Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);

        if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
            Slog.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
            if (DEBUG) {
                Log.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
                        + serviceConnector.getComponentName());
            }
            return;
        }

@@ -224,8 +221,7 @@ public class CompanionAppBinder {

        // Rebinding in 10 seconds.
        Handler.getMain().postDelayed(() ->
                        onRebindingCompanionApplicationTimeout(userId, packageName,
                                serviceConnector),
                onRebindingCompanionApplicationTimeout(userId, packageName, serviceConnector),
                REBIND_TIMEOUT);
    }

@@ -239,12 +235,12 @@ public class CompanionAppBinder {

    private void onRebindingCompanionApplicationTimeout(
            @UserIdInt int userId, @NonNull String packageName,
            @NonNull CompanionServiceConnector serviceConnector) {
            @NonNull CompanionDeviceServiceConnector serviceConnector) {
        // Re-mark the application is bound.
        if (serviceConnector.isPrimary()) {
            synchronized (mBoundCompanionApplications) {
                if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
                    List<CompanionServiceConnector> serviceConnectors =
                    List<CompanionDeviceServiceConnector> serviceConnectors =
                            Collections.singletonList(serviceConnector);
                    mBoundCompanionApplications.setValueForPackage(userId, packageName,
                            serviceConnectors);
@@ -260,9 +256,114 @@ public class CompanionAppBinder {
    }

    /**
     * Dump bound apps.
     * Notify the app that the device appeared.
     *
     * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
     */
    @Deprecated
    public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
        final int userId = association.getUserId();
        final String packageName = association.getPackageName();

        Slog.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId
                    + "/" + packageName);

        final CompanionDeviceServiceConnector primaryServiceConnector =
                getPrimaryServiceConnector(userId, packageName);
        if (primaryServiceConnector == null) {
            Slog.e(TAG, "notify_CompanionApplicationDevice_Appeared(): "
                        + "u" + userId + "/" + packageName + " is NOT bound.");
            Slog.e(TAG, "Stacktrace", new Throwable());
            return;
        }

        Log.i(TAG, "Calling onDeviceAppeared to userId=[" + userId + "] package=["
                + packageName + "] associationId=[" + association.getId() + "]");

        primaryServiceConnector.postOnDeviceAppeared(association);
    }

    /**
     * Notify the app that the device disappeared.
     *
     * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
     */
    @Deprecated
    public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
        final int userId = association.getUserId();
        final String packageName = association.getPackageName();

        Slog.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId
                + "/" + packageName);

        final CompanionDeviceServiceConnector primaryServiceConnector =
                getPrimaryServiceConnector(userId, packageName);
        if (primaryServiceConnector == null) {
            Slog.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): "
                        + "u" + userId + "/" + packageName + " is NOT bound.");
            Slog.e(TAG, "Stacktrace", new Throwable());
            return;
        }

        Log.i(TAG, "Calling onDeviceDisappeared to userId=[" + userId + "] package=["
                + packageName + "] associationId=[" + association.getId() + "]");

        primaryServiceConnector.postOnDeviceDisappeared(association);
    }

    /**
     * Notify the app that the device appeared.
     */
    public void notifyCompanionDevicePresenceEvent(AssociationInfo association, int event) {
        final int userId = association.getUserId();
        final String packageName = association.getPackageName();
        final CompanionDeviceServiceConnector primaryServiceConnector =
                getPrimaryServiceConnector(userId, packageName);
        final DevicePresenceEvent devicePresenceEvent =
                new DevicePresenceEvent(association.getId(), event, null);

        if (primaryServiceConnector == null) {
            Slog.e(TAG, "notifyCompanionApplicationDevicePresenceEvent(): "
                        + "u" + userId + "/" + packageName
                        + " event=[ " + event  + " ] is NOT bound.");
            Slog.e(TAG, "Stacktrace", new Throwable());
            return;
        }

        Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
                + packageName + "] associationId=[" + association.getId()
                + "] event=[" + event + "]");

        primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
    }

    /**
     * Notify the app that the device disappeared.
     */
    public void dump(@NonNull PrintWriter out) {
    public void notifyUuidDevicePresenceEvent(ObservableUuid uuid, int event) {
        final int userId = uuid.getUserId();
        final ParcelUuid parcelUuid = uuid.getUuid();
        final String packageName = uuid.getPackageName();
        final CompanionDeviceServiceConnector primaryServiceConnector =
                getPrimaryServiceConnector(userId, packageName);
        final DevicePresenceEvent devicePresenceEvent =
                new DevicePresenceEvent(DevicePresenceEvent.NO_ASSOCIATION, event, parcelUuid);

        if (primaryServiceConnector == null) {
            Slog.e(TAG, "notifyApplicationDevicePresenceChanged(): "
                    + "u" + userId + "/" + packageName
                    + " event=[ " + event  + " ] is NOT bound.");
            Slog.e(TAG, "Stacktrace", new Throwable());
            return;
        }

        Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
                + packageName + "]" + "event= [" + event + "]");

        primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
    }

    void dump(@NonNull PrintWriter out) {
        out.append("Companion Device Application Controller: \n");

        synchronized (mBoundCompanionApplications) {
@@ -276,7 +377,6 @@ public class CompanionAppBinder {
        }

        out.append("  Companion Applications Scheduled For Rebinding: ");
        synchronized (mScheduledForRebindingCompanionApplications) {
        if (mScheduledForRebindingCompanionApplications.size() == 0) {
            out.append("<empty>\n");
        } else {
@@ -284,18 +384,93 @@ public class CompanionAppBinder {
            mScheduledForRebindingCompanionApplications.dump(out);
        }
    }

    /**
     * Rebinding for Self-Managed secondary services OR Non-Self-Managed services.
     */
    private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
            @NonNull CompanionDeviceServiceConnector serviceConnector) {

        boolean isPrimary = serviceConnector.isPrimary();
        Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);

        // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
        if (isPrimary) {
            final List<AssociationInfo> associations =
                    mAssociationStore.getActiveAssociationsByPackage(userId, packageName);

            for (AssociationInfo association : associations) {
                final String deviceProfile = association.getDeviceProfile();
                if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
                    Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
                    mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
                    break;
                }
            }

    @Nullable
    CompanionServiceConnector getPrimaryServiceConnector(
            synchronized (mBoundCompanionApplications) {
                mBoundCompanionApplications.removePackage(userId, packageName);
            }
        }

        // Second: schedule rebinding if needed.
        final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);

        if (shouldScheduleRebind) {
            scheduleRebinding(userId, packageName, serviceConnector);
        }
    }

    private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector(
            @UserIdInt int userId, @NonNull String packageName) {
        final List<CompanionServiceConnector> connectors;
        final List<CompanionDeviceServiceConnector> connectors;
        synchronized (mBoundCompanionApplications) {
            connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName);
        }
        return connectors != null ? connectors.get(0) : null;
    }

    private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
        // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
        // app is uninstalled.
        boolean stillAssociated = false;
        // Make sure to clean up the state for all the associations
        // that associate with this package.
        boolean shouldScheduleRebind = false;
        boolean shouldScheduleRebindForUuid = false;
        final List<ObservableUuid> uuids =
                mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);

        for (AssociationInfo ai :
                mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
            final int associationId = ai.getId();
            stillAssociated = true;
            if (ai.isSelfManaged()) {
                // Do not rebind if primary one is died for selfManaged application.
                if (isPrimary
                        && mDevicePresenceMonitor.isDevicePresent(associationId)) {
                    mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
                    shouldScheduleRebind = false;
                }
                // Do not rebind if both primary and secondary services are died for
                // selfManaged application.
                shouldScheduleRebind = isCompanionApplicationBound(userId, packageName);
            } else if (ai.isNotifyOnDeviceNearby()) {
                // Always rebind for non-selfManaged devices.
                shouldScheduleRebind = true;
            }
        }

        for (ObservableUuid uuid : uuids) {
            if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
                shouldScheduleRebindForUuid = true;
                break;
            }
        }

        return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
    }

    private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
        @Override
        public synchronized @NonNull Map<String, List<ComponentName>> forUser(
Loading