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

Commit 44f3ee4c authored by Joanne Chung's avatar Joanne Chung
Browse files

Fix potential deadlock in getAutofillOptions().

The getAutofillOptions() holds the main service lock, we move disable
autofill information from AutofillManagerService to AutofillOptions
in ag/8684509, it may possible cause circular lock between
AutofillManagerService.

Move the disable information to another class DisabledInfoCache, keyed
by user id. We can use a new lock inside this class to prevent use
main service lock

Bug: 150655097
Bug: 150193890
Test: atest CtsAutoFillServiceTestCases

Change-Id: I81973663d849dbbf6e3493ad436e1c77a98f8d82
parent d5763c4c
Loading
Loading
Loading
Loading
+238 −11
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
@@ -63,6 +64,7 @@ import android.util.LocalLog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -151,6 +153,7 @@ public final class AutofillManagerService
    private final LocalLog mWtfHistory = new LocalLog(50);

    private final AutofillCompatState mAutofillCompatState = new AutofillCompatState();
    private final DisabledInfoCache mDisabledInfoCache = new DisabledInfoCache();

    private final LocalService mLocalService = new LocalService();
    private final ActivityManagerInternal mAm;
@@ -302,14 +305,15 @@ public final class AutofillManagerService
    @Override // from AbstractMasterSystemService
    protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
            boolean disabled) {
        return new AutofillManagerServiceImpl(this, mLock, mUiLatencyHistory,
                mWtfHistory, resolvedUserId, mUi, mAutofillCompatState, disabled);
        return new AutofillManagerServiceImpl(this, mLock, mUiLatencyHistory, mWtfHistory,
                resolvedUserId, mUi, mAutofillCompatState, disabled, mDisabledInfoCache);
    }

    @Override // AbstractMasterSystemService
    protected void onServiceRemoved(@NonNull AutofillManagerServiceImpl service,
            @UserIdInt int userId) {
        service.destroyLocked();
        mDisabledInfoCache.remove(userId);
        mAutofillCompatState.removeCompatibilityModeRequests(userId);
    }

@@ -835,15 +839,10 @@ public final class AutofillManagerService

        private void injectDisableAppInfo(@NonNull AutofillOptions options, int userId,
                String packageName) {
            synchronized (mLock) {
                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                if (service != null) {
                    options.appDisabledExpiration = service.getAppDisabledExpirationLocked(
                            packageName);
                    options.disabledActivities = service.getAppDisabledActivitiesLocked(
                            packageName);
                }
            }
            options.appDisabledExpiration =
                    mDisabledInfoCache.getAppDisabledExpiration(userId, packageName);
            options.disabledActivities =
                    mDisabledInfoCache.getAppDisabledActivities(userId, packageName);
        }
    }

@@ -866,6 +865,234 @@ public final class AutofillManagerService
        }
    }

    /**
     * Stores autofill disable information, i.e. {@link AutofillDisabledInfo},  keyed by user id.
     * The information is cleaned up when the service is removed.
     */
    static final class DisabledInfoCache {

        private final Object mLock = new Object();

        @GuardedBy("mLock")
        private final SparseArray<AutofillDisabledInfo> mCache = new SparseArray<>();

        void remove(@UserIdInt int userId) {
            synchronized (mLock) {
                mCache.remove(userId);
            }
        }

        void addDisabledAppLocked(@UserIdInt int userId, @NonNull String packageName,
                long expiration) {
            Preconditions.checkNotNull(packageName);
            synchronized (mLock) {
                AutofillDisabledInfo info =
                        getOrCreateAutofillDisabledInfoByUserIdLocked(userId);
                info.putDisableAppsLocked(packageName, expiration);
            }
        }

        void addDisabledActivityLocked(@UserIdInt int userId, @NonNull ComponentName componentName,
                long expiration) {
            Preconditions.checkNotNull(componentName);
            synchronized (mLock) {
                AutofillDisabledInfo info =
                        getOrCreateAutofillDisabledInfoByUserIdLocked(userId);
                info.putDisableActivityLocked(componentName, expiration);
            }
        }

        boolean isAutofillDisabledLocked(@UserIdInt int userId,
                @NonNull ComponentName componentName) {
            Preconditions.checkNotNull(componentName);
            final boolean disabled;
            synchronized (mLock) {
                final AutofillDisabledInfo info = mCache.get(userId);
                disabled = info != null ? info.isAutofillDisabledLocked(componentName) : false;
            }
            return disabled;
        }

        long getAppDisabledExpiration(@UserIdInt int userId, @NonNull String packageName) {
            Preconditions.checkNotNull(packageName);
            final Long expiration;
            synchronized (mLock) {
                final AutofillDisabledInfo info = mCache.get(userId);
                expiration = info != null ? info.getAppDisabledExpirationLocked(packageName) : 0;
            }
            return expiration;
        }

        @Nullable
        ArrayMap<String, Long> getAppDisabledActivities(@UserIdInt int userId,
                @NonNull String packageName) {
            Preconditions.checkNotNull(packageName);
            final ArrayMap<String, Long> disabledList;
            synchronized (mLock) {
                final AutofillDisabledInfo info = mCache.get(userId);
                disabledList =
                        info != null ? info.getAppDisabledActivitiesLocked(packageName) : null;
            }
            return disabledList;
        }

        void dump(@UserIdInt int userId, String prefix, PrintWriter pw) {
            synchronized (mLock) {
                final AutofillDisabledInfo info = mCache.get(userId);
                if (info != null) {
                    info.dumpLocked(prefix, pw);
                }
            }
        }

        @NonNull
        private AutofillDisabledInfo getOrCreateAutofillDisabledInfoByUserIdLocked(
                @UserIdInt int userId) {
            AutofillDisabledInfo info = mCache.get(userId);
            if (info == null) {
                info = new AutofillDisabledInfo();
                mCache.put(userId, info);
            }
            return info;
        }
    }

    /**
     * The autofill disable information.
     * <p>
     * This contains disable information set by the AutofillService, e.g. disabled application
     * expiration, disable activity expiration.
     */
    private static final class AutofillDisabledInfo {
        /**
         * Apps disabled by the service; key is package name, value is when they will be enabled
         * again.
         */
        private ArrayMap<String, Long> mDisabledApps;
        /**
         * Activities disabled by the service; key is component name, value is when they will be
         * enabled again.
         */
        private ArrayMap<ComponentName, Long> mDisabledActivities;

        void putDisableAppsLocked(@NonNull String packageName, long expiration) {
            if (mDisabledApps == null) {
                mDisabledApps = new ArrayMap<>(1);
            }
            mDisabledApps.put(packageName, expiration);
        }

        void putDisableActivityLocked(@NonNull ComponentName componentName, long expiration) {
            if (mDisabledActivities == null) {
                mDisabledActivities = new ArrayMap<>(1);
            }
            mDisabledActivities.put(componentName, expiration);
        }

        long getAppDisabledExpirationLocked(@NonNull String packageName) {
            if (mDisabledApps == null) {
                return 0;
            }
            final Long expiration = mDisabledApps.get(packageName);
            return expiration != null ? expiration : 0;
        }

        ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) {
            if (mDisabledActivities != null) {
                final int size = mDisabledActivities.size();
                ArrayMap<String, Long> disabledList = null;
                for (int i = 0; i < size; i++) {
                    final ComponentName component = mDisabledActivities.keyAt(i);
                    if (packageName.equals(component.getPackageName())) {
                        if (disabledList == null) {
                            disabledList = new ArrayMap<>();
                        }
                        final long expiration = mDisabledActivities.valueAt(i);
                        disabledList.put(component.flattenToShortString(), expiration);
                    }
                }
                return disabledList;
            }
            return null;
        }

        boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
            // Check activities first.
            long elapsedTime = 0;
            if (mDisabledActivities != null) {
                elapsedTime = SystemClock.elapsedRealtime();
                final Long expiration = mDisabledActivities.get(componentName);
                if (expiration != null) {
                    if (expiration >= elapsedTime) return true;
                    // Restriction expired - clean it up.
                    if (sVerbose) {
                        Slog.v(TAG, "Removing " + componentName.toShortString()
                                + " from disabled list");
                    }
                    mDisabledActivities.remove(componentName);
                }
            }

            // Then check apps.
            final String packageName = componentName.getPackageName();
            if (mDisabledApps == null) return false;

            final Long expiration = mDisabledApps.get(packageName);
            if (expiration == null) return false;

            if (elapsedTime == 0) {
                elapsedTime = SystemClock.elapsedRealtime();
            }

            if (expiration >= elapsedTime) return true;

            // Restriction expired - clean it up.
            if (sVerbose)  Slog.v(TAG, "Removing " + packageName + " from disabled list");
            mDisabledApps.remove(packageName);
            return false;
        }

        void dumpLocked(String prefix, PrintWriter pw) {
            pw.print(prefix); pw.print("Disabled apps: ");
            if (mDisabledApps == null) {
                pw.println("N/A");
            } else {
                final int size = mDisabledApps.size();
                pw.println(size);
                final StringBuilder builder = new StringBuilder();
                final long now = SystemClock.elapsedRealtime();
                for (int i = 0; i < size; i++) {
                    final String packageName = mDisabledApps.keyAt(i);
                    final long expiration = mDisabledApps.valueAt(i);
                    builder.append(prefix).append(prefix)
                            .append(i).append(". ").append(packageName).append(": ");
                    TimeUtils.formatDuration((expiration - now), builder);
                    builder.append('\n');
                }
                pw.println(builder);
            }

            pw.print(prefix); pw.print("Disabled activities: ");
            if (mDisabledActivities == null) {
                pw.println("N/A");
            } else {
                final int size = mDisabledActivities.size();
                pw.println(size);
                final StringBuilder builder = new StringBuilder();
                final long now = SystemClock.elapsedRealtime();
                for (int i = 0; i < size; i++) {
                    final ComponentName component = mDisabledActivities.keyAt(i);
                    final long expiration = mDisabledActivities.valueAt(i);
                    builder.append(prefix).append(prefix)
                            .append(i).append(". ").append(component).append(": ");
                    TimeUtils.formatDuration((expiration - now), builder);
                    builder.append('\n');
                }
                pw.println(builder);
            }
        }
    }

    /**
     * Compatibility mode metadata associated with all services.
     *
+10 −127
Original line number Diff line number Diff line
@@ -67,7 +67,6 @@ import android.util.LocalLog;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -80,6 +79,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
import com.android.server.autofill.AutofillManagerService.DisabledInfoCache;
import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
@@ -90,7 +90,6 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
 * app's {@link IAutoFillService} implementation.
@@ -124,19 +123,6 @@ final class AutofillManagerServiceImpl
    @Nullable
    private RemoteInlineSuggestionRenderService mRemoteInlineSuggestionRenderService;

    /**
     * Apps disabled by the service; key is package name, value is when they will be enabled again.
     */
    @GuardedBy("mLock")
    private ArrayMap<String, Long> mDisabledApps;

    /**
     * Activities disabled by the service; key is component name, value is when they will be enabled
     * again.
     */
    @GuardedBy("mLock")
    private ArrayMap<ComponentName, Long> mDisabledActivities;

    /**
     * Data used for field classification.
     */
@@ -186,10 +172,12 @@ final class AutofillManagerServiceImpl

    private final ContentCaptureManagerInternal mContentCaptureManagerInternal;

    private final DisabledInfoCache mDisabledInfoCache;

    AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
            LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
            AutofillCompatState autofillCompatState,
            boolean disabled) {
            boolean disabled, DisabledInfoCache disableCache) {
        super(master, lock, userId);

        mUiLatencyHistory = uiLatencyHistory;
@@ -200,7 +188,7 @@ final class AutofillManagerServiceImpl
        mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
        mContentCaptureManagerInternal = LocalServices.getService(
                ContentCaptureManagerInternal.class);

        mDisabledInfoCache = disableCache;
        updateLocked(disabled);
    }

@@ -1045,45 +1033,7 @@ final class AutofillManagerServiceImpl
        pw.println(isInlineSuggestionsEnabled());
        pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);

        pw.print(prefix); pw.print("Disabled apps: ");

        if (mDisabledApps == null) {
            pw.println("N/A");
        } else {
            final int size = mDisabledApps.size();
            pw.println(size);
            final StringBuilder builder = new StringBuilder();
            final long now = SystemClock.elapsedRealtime();
            for (int i = 0; i < size; i++) {
                final String packageName = mDisabledApps.keyAt(i);
                final long expiration = mDisabledApps.valueAt(i);
                builder.append(prefix).append(prefix)
                        .append(i).append(". ").append(packageName).append(": ");
                TimeUtils.formatDuration((expiration - now), builder);
                builder.append('\n');
            }
            pw.println(builder);
        }

        pw.print(prefix); pw.print("Disabled activities: ");

        if (mDisabledActivities == null) {
            pw.println("N/A");
        } else {
            final int size = mDisabledActivities.size();
            pw.println(size);
            final StringBuilder builder = new StringBuilder();
            final long now = SystemClock.elapsedRealtime();
            for (int i = 0; i < size; i++) {
                final ComponentName component = mDisabledActivities.keyAt(i);
                final long expiration = mDisabledActivities.valueAt(i);
                builder.append(prefix).append(prefix)
                        .append(i).append(". ").append(component).append(": ");
                TimeUtils.formatDuration((expiration - now), builder);
                builder.append('\n');
            }
            pw.println(builder);
        }
        mDisabledInfoCache.dump(mUserId, prefix, pw);

        final int size = mSessions.size();
        if (size == 0) {
@@ -1480,15 +1430,13 @@ final class AutofillManagerServiceImpl
    void disableAutofillForApp(@NonNull String packageName, long duration, int sessionId,
            boolean compatMode) {
        synchronized (mLock) {
            if (mDisabledApps == null) {
                mDisabledApps = new ArrayMap<>(1);
            }
            long expiration = SystemClock.elapsedRealtime() + duration;
            // Protect it against overflow
            if (expiration < 0) {
                expiration = Long.MAX_VALUE;
            }
            mDisabledApps.put(packageName, expiration);
            mDisabledInfoCache.addDisabledAppLocked(mUserId, packageName, expiration);

            int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
            mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
                    packageName, getServicePackageName(), sessionId, compatMode)
@@ -1502,15 +1450,12 @@ final class AutofillManagerServiceImpl
    void disableAutofillForActivity(@NonNull ComponentName componentName, long duration,
            int sessionId, boolean compatMode) {
        synchronized (mLock) {
            if (mDisabledActivities == null) {
                mDisabledActivities = new ArrayMap<>(1);
            }
            long expiration = SystemClock.elapsedRealtime() + duration;
            // Protect it against overflow
            if (expiration < 0) {
                expiration = Long.MAX_VALUE;
            }
            mDisabledActivities.put(componentName, expiration);
            mDisabledInfoCache.addDisabledActivityLocked(mUserId, componentName, expiration);
            final int intDuration = duration > Integer.MAX_VALUE
                    ? Integer.MAX_VALUE
                    : (int) duration;
@@ -1528,74 +1473,12 @@ final class AutofillManagerServiceImpl
        }
    }

    // Called by AutofillManagerService
    long getAppDisabledExpirationLocked(@NonNull String packageName) {
        if (mDisabledApps == null) {
            return 0;
        }
        final Long expiration = mDisabledApps.get(packageName);
        return expiration != null ? expiration : 0;
    }

    // Called by AutofillManagerService
    @Nullable
    ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) {
        if (mDisabledActivities != null) {
            final int size = mDisabledActivities.size();
            ArrayMap<String, Long> disabledList = null;
            for (int i = 0; i < size; i++) {
                final ComponentName component = mDisabledActivities.keyAt(i);
                if (packageName.equals(component.getPackageName())) {
                    if (disabledList == null) {
                        disabledList = new ArrayMap<>();
                    }
                    final long expiration = mDisabledActivities.valueAt(i);
                    disabledList.put(component.flattenToShortString(), expiration);
                }
            }
            return disabledList;
        }
        return null;
    }

    /**
     * Checks if autofill is disabled by service to the given activity.
     */
    @GuardedBy("mLock")
    private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
        // Check activities first.
        long elapsedTime = 0;
        if (mDisabledActivities != null) {
            elapsedTime = SystemClock.elapsedRealtime();
            final Long expiration = mDisabledActivities.get(componentName);
            if (expiration != null) {
                if (expiration >= elapsedTime) return true;
                // Restriction expired - clean it up.
                if (sVerbose) {
                    Slog.v(TAG, "Removing " + componentName.toShortString()
                        + " from disabled list");
                }
                mDisabledActivities.remove(componentName);
            }
        }

        // Then check apps.
        final String packageName = componentName.getPackageName();
        if (mDisabledApps == null) return false;

        final Long expiration = mDisabledApps.get(packageName);
        if (expiration == null) return false;

        if (elapsedTime == 0) {
            elapsedTime = SystemClock.elapsedRealtime();
        }

        if (expiration >= elapsedTime) return true;

        // Restriction expired - clean it up.
        if (sVerbose)  Slog.v(TAG, "Removing " + packageName + " from disabled list");
        mDisabledApps.remove(packageName);
        return false;
        return mDisabledInfoCache.isAutofillDisabledLocked(mUserId, componentName);
    }

    // Called by AutofillManager, checks UID.