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

Commit ef570066 authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Reconnect to DAService after binding is dead" into oc-dev

parents 31ef1394 09c529a9
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -9221,6 +9221,23 @@ public final class Settings {
         */
        public static final String SHORTCUT_MANAGER_CONSTANTS = "shortcut_manager_constants";

        /**
         * DevicePolicyManager specific settings.
         * This is encoded as a key=value list, separated by commas. Ex:
         *
         * <pre>
         * das_died_service_reconnect_backoff_sec       (long)
         * das_died_service_reconnect_backoff_increase  (float)
         * das_died_service_reconnect_max_backoff_sec   (long)
         * </pre>
         *
         * <p>
         * Type: string
         * @hide
         * see also com.android.server.devicepolicy.DevicePolicyConstants
         */
        public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants";

        /**
         * Get the key that retrieves a bluetooth headset's priority.
         * @hide
+231 −25
Original line number Diff line number Diff line
@@ -22,32 +22,77 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;

/**
 * Connects to a given service component on a given user.
 *
 * - Call {@link #connect()} to create a connection.
 * - Call {@link #disconnect()} to disconnect.  Make sure to disconnect when the user stops.
 * - Call {@link #bind()} to create a connection.
 * - Call {@link #unbind()} to disconnect.  Make sure to disconnect when the user stops.
 *
 * Add onConnected/onDisconnected callbacks as needed.
 *
 * When the target process gets killed (by OOM-killer, etc), then the activity manager will
 * re-connect the connection automatically, in which case onServiceDisconnected() gets called
 * and then onServiceConnected().
 *
 * However sometimes the activity manager just "kills" the connection -- like when the target
 * package gets updated or the target process crashes multiple times in a row, in which case
 * onBindingDied() gets called.  This class handles this case by re-connecting in the time
 * {@link #mRebindBackoffMs}.  If this happens again, this class increases the back-off time
 * by {@link #mRebindBackoffIncrease} and retry.  The back-off time is capped at
 * {@link #mRebindMaxBackoffMs}.
 *
 * The back-off time will never be reset until {@link #unbind()} and {@link #bind()} are called
 * explicitly.
 *
 * NOTE: This class does *not* handle package-updates -- i.e. even if the binding dies due to
 * the target package being updated, this class won't reconnect.  This is because this class doesn't
 * know what to do when the service component has gone missing, for example.  If the user of this
 * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind}
 * explicitly.
 */
public abstract class PersistentConnection<T> {
    private final Object mLock = new Object();

    private final static boolean DEBUG = false;

    private final String mTag;
    private final Context mContext;
    private final Handler mHandler;
    private final int mUserId;
    private final ComponentName mComponentName;

    private long mNextBackoffMs;

    private final long mRebindBackoffMs;
    private final double mRebindBackoffIncrease;
    private final long mRebindMaxBackoffMs;

    private long mReconnectTime;

    // TODO too many booleans... Should clean up.

    @GuardedBy("mLock")
    private boolean mStarted;
    private boolean mBound;

    /**
     * Whether {@link #bind()} has been called and {@link #unbind()} hasn't been yet; meaning this
     * is the expected bind state from the caller's point of view.
     */
    @GuardedBy("mLock")
    private boolean mShouldBeBound;

    @GuardedBy("mLock")
    private boolean mRebindScheduled;

    @GuardedBy("mLock")
    private boolean mIsConnected;
@@ -59,6 +104,14 @@ public abstract class PersistentConnection<T> {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            synchronized (mLock) {
                if (!mBound) {
                    // Callback came in after PersistentConnection.unbind() was called.
                    // We just ignore this.
                    // (We've already called unbindService() already in unbind)
                    Slog.w(mTag, "Connected: " + mComponentName.flattenToShortString()
                            + " u" + mUserId + " but not bound, ignore.");
                    return;
                }
                Slog.i(mTag, "Connected: " + mComponentName.flattenToShortString()
                        + " u" + mUserId);

@@ -76,21 +129,68 @@ public abstract class PersistentConnection<T> {
                cleanUpConnectionLocked();
            }
        }

        @Override
        public void onBindingDied(ComponentName name) {
            // Activity manager gave up; we'll schedule a re-connect by ourselves.
            synchronized (mLock) {
                if (!mBound) {
                    // Callback came in late?
                    Slog.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
                            + " u" + mUserId + " but not bound, ignore.");
                    return;
                }

                Slog.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
                        + " u" + mUserId);
                scheduleRebindLocked();
            }
        }
    };

    private final Runnable mBindForBackoffRunnable = () -> bindForBackoff();

    public PersistentConnection(@NonNull String tag, @NonNull Context context,
            @NonNull Handler handler, int userId, @NonNull ComponentName componentName) {
            @NonNull Handler handler, int userId, @NonNull ComponentName componentName,
            long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds) {
        mTag = tag;
        mContext = context;
        mHandler = handler;
        mUserId = userId;
        mComponentName = componentName;

        mRebindBackoffMs = rebindBackoffSeconds * 1000;
        mRebindBackoffIncrease = rebindBackoffIncrease;
        mRebindMaxBackoffMs = rebindMaxBackoffSeconds * 1000;

        mNextBackoffMs = mRebindBackoffMs;
    }

    public final ComponentName getComponentName() {
        return mComponentName;
    }

    /**
     * @return whether {@link #bind()} has been called and {@link #unbind()} hasn't.
     *
     * Note when the AM gives up on connection, this class detects it and un-bind automatically,
     * and schedule rebind, and {@link #isBound} returns false when it's waiting for a retry.
     */
    public final boolean isBound() {
        synchronized (mLock) {
            return mBound;
        }
    }

    /**
     * @return whether re-bind is scheduled after the AM gives up on a connection.
     */
    public final boolean isRebindScheduled() {
        synchronized (mLock) {
            return mRebindScheduled;
        }
    }

    /**
     * @return whether connected.
     */
@@ -112,15 +212,33 @@ public abstract class PersistentConnection<T> {
    /**
     * Connects to the service.
     */
    public final void connect() {
    public final void bind() {
        synchronized (mLock) {
            if (mStarted) {
            mShouldBeBound = true;

            bindInnerLocked(/* resetBackoff= */ true);
        }
    }

    public final void bindInnerLocked(boolean resetBackoff) {
        unscheduleRebindLocked();

        if (mBound) {
            return;
        }
            mStarted = true;
        mBound = true;

        if (resetBackoff) {
            // Note this is the only place we reset the backoff time.
            mNextBackoffMs = mRebindBackoffMs;
        }

        final Intent service = new Intent().setComponent(mComponentName);

        if (DEBUG) {
            Slog.d(mTag, "Attempting to connect to " + mComponentName);
        }

        final boolean success = mContext.bindServiceAsUser(service, mServiceConnection,
                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
                mHandler, UserHandle.of(mUserId));
@@ -130,6 +248,16 @@ public abstract class PersistentConnection<T> {
                    + " failed.");
        }
    }

    final void bindForBackoff() {
        synchronized (mLock) {
            if (!mShouldBeBound) {
                // Race condition -- by the time we got here, unbind() has already been called.
                return;
            }

            bindInnerLocked(/* resetBackoff= */ false);
        }
    }

    private void cleanUpConnectionLocked() {
@@ -140,17 +268,47 @@ public abstract class PersistentConnection<T> {
    /**
     * Disconnect from the service.
     */
    public final void disconnect() {
    public final void unbind() {
        synchronized (mLock) {
            if (!mStarted) {
            mShouldBeBound = false;

            unbindLocked();
        }
    }

    private final void unbindLocked() {
        unscheduleRebindLocked();

        if (!mBound) {
            return;
        }
        Slog.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
            mStarted = false;
        mBound = false;
        mContext.unbindService(mServiceConnection);

        cleanUpConnectionLocked();
    }

    void unscheduleRebindLocked() {
        injectRemoveCallbacks(mBindForBackoffRunnable);
        mRebindScheduled = false;
    }

    void scheduleRebindLocked() {
        unbindLocked();

        if (!mRebindScheduled) {
            Slog.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)");

            mReconnectTime = injectUptimeMillis() + mNextBackoffMs;

            injectPostAtTime(mBindForBackoffRunnable, mReconnectTime);

            mNextBackoffMs = Math.min(mRebindMaxBackoffMs,
                    (long) (mNextBackoffMs * mRebindBackoffIncrease));

            mRebindScheduled = true;
        }
    }

    /** Must be implemented by a subclass to convert an {@link IBinder} to a stub. */
@@ -160,9 +318,57 @@ public abstract class PersistentConnection<T> {
        synchronized (mLock) {
            pw.print(prefix);
            pw.print(mComponentName.flattenToShortString());
            pw.print(mStarted ? "  [started]" : "  [not started]");
            pw.print(mBound ? "  [bound]" : "  [not bound]");
            pw.print(mIsConnected ? "  [connected]" : "  [not connected]");
            if (mRebindScheduled) {
                pw.print("  reconnect in ");
                TimeUtils.formatDuration((mReconnectTime - injectUptimeMillis()), pw);
            }
            pw.println();

            pw.print(prefix);
            pw.print("  Next backoff(sec): ");
            pw.print(mNextBackoffMs / 1000);
        }
    }

    @VisibleForTesting
    void injectRemoveCallbacks(Runnable r) {
        mHandler.removeCallbacks(r);
    }

    @VisibleForTesting
    void injectPostAtTime(Runnable r, long uptimeMillis) {
        mHandler.postAtTime(r, uptimeMillis);
    }

    @VisibleForTesting
    long injectUptimeMillis() {
        return SystemClock.uptimeMillis();
    }

    @VisibleForTesting
    long getNextBackoffMsForTest() {
        return mNextBackoffMs;
    }

    @VisibleForTesting
    long getReconnectTimeForTest() {
        return mReconnectTime;
    }

    @VisibleForTesting
    ServiceConnection getServiceConnectionForTest() {
        return mServiceConnection;
    }

    @VisibleForTesting
    Runnable getBindForBackoffRunnableForTest() {
        return mBindForBackoffRunnable;
    }

    @VisibleForTesting
    boolean shouldBeBoundForTest() {
        return mShouldBeBound;
    }
}
+16 −7
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ public class DeviceAdminServiceController {

    private final DevicePolicyManagerService mService;
    private final DevicePolicyManagerService.Injector mInjector;
    private final DevicePolicyConstants mConstants;

    private final Handler mHandler; // needed?

@@ -66,7 +67,10 @@ public class DeviceAdminServiceController {
    private class DevicePolicyServiceConnection
            extends PersistentConnection<IDeviceAdminService> {
        public DevicePolicyServiceConnection(int userId, @NonNull ComponentName componentName) {
            super(TAG, mContext, mHandler, userId, componentName);
            super(TAG, mContext, mHandler, userId, componentName,
                    mConstants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC,
                    mConstants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE,
                    mConstants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
        }

        @Override
@@ -81,11 +85,13 @@ public class DeviceAdminServiceController {
    @GuardedBy("mLock")
    private final SparseArray<DevicePolicyServiceConnection> mConnections = new SparseArray<>();

    public DeviceAdminServiceController(DevicePolicyManagerService service) {
    public DeviceAdminServiceController(DevicePolicyManagerService service,
            DevicePolicyConstants constants) {
        mService = service;
        mInjector = service.mInjector;
        mContext = mInjector.mContext;
        mHandler = new Handler(BackgroundThread.get().getLooper());
        mConstants = constants;
    }

    /**
@@ -150,9 +156,11 @@ public class DeviceAdminServiceController {
                final PersistentConnection<IDeviceAdminService> existing =
                        mConnections.get(userId);
                if (existing != null) {
                    if (existing.getComponentName().equals(service.getComponentName())) {
                        return;
                    }
                    // Note even when we're already connected to the same service, the binding
                    // would have died at this point due to a package update.  So we disconnect
                    // anyway and re-connect.
                    debug("Disconnecting from existing service connection.",
                            packageName, userId);
                    disconnectServiceOnUserLocked(userId, actionForLog);
                }

@@ -164,7 +172,7 @@ public class DeviceAdminServiceController {
                        new DevicePolicyServiceConnection(
                                userId, service.getComponentName());
                mConnections.put(userId, conn);
                conn.connect();
                conn.bind();
            }
        } finally {
            mInjector.binderRestoreCallingIdentity(token);
@@ -190,7 +198,7 @@ public class DeviceAdminServiceController {
        if (conn != null) {
            debug("Stopping service for u%d if already running for %s.",
                    userId, actionForLog);
            conn.disconnect();
            conn.unbind();
            mConnections.remove(userId);
        }
    }
@@ -209,6 +217,7 @@ public class DeviceAdminServiceController {
                final DevicePolicyServiceConnection con = mConnections.valueAt(i);
                con.dump(prefix + "    ", pw);
            }
            pw.println();
        }
    }
}
+115 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.devicepolicy;

import android.util.KeyValueListParser;
import android.util.Slog;

import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * Constants that are configurable via the global settings for {@link DevicePolicyManagerService}.
 *
 * Example of setting the values for testing.
 * adb shell settings put global device_policy_constants das_died_service_reconnect_backoff_sec=10,das_died_service_reconnect_backoff_increase=1.5,das_died_service_reconnect_max_backoff_sec=30
 */
public class DevicePolicyConstants {
    private static final String TAG = DevicePolicyManagerService.LOG_TAG;

    private static final String DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC_KEY
            = "das_died_service_reconnect_backoff_sec";

    private static final String DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE_KEY
            = "das_died_service_reconnect_backoff_increase";

    private static final String DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY
            = "das_died_service_reconnect_max_backoff_sec";

    /**
     * The back-off before re-connecting, when a service binding died, due to the owner
     * crashing repeatedly.
     */
    public final long DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC;

    /**
     * The exponential back-off increase factor when a binding dies multiple times.
     */
    public final double DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE;

    /**
     * The max back-off
     */
    public final long DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC;

    private DevicePolicyConstants(String settings) {

        final KeyValueListParser parser = new KeyValueListParser(',');
        try {
            parser.setString(settings);
        } catch (IllegalArgumentException e) {
            // Failed to parse the settings string, log this and move on
            // with defaults.
            Slog.e(TAG, "Bad device policy settings: " + settings);
        }

        long dasDiedServiceReconnectBackoffSec = parser.getLong(
                DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC_KEY, TimeUnit.HOURS.toSeconds(1));

        double dasDiedServiceReconnectBackoffIncrease = parser.getFloat(
                DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE_KEY, 2f);

        long dasDiedServiceReconnectMaxBackoffSec = parser.getLong(
                DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.DAYS.toSeconds(1));

        // Set minimum: 5 seconds.
        dasDiedServiceReconnectBackoffSec = Math.max(5, dasDiedServiceReconnectBackoffSec);

        // Set minimum: 1.0.
        dasDiedServiceReconnectBackoffIncrease =
                Math.max(1, dasDiedServiceReconnectBackoffIncrease);

        // Make sure max >= default back off.
        dasDiedServiceReconnectMaxBackoffSec = Math.max(dasDiedServiceReconnectBackoffSec,
                dasDiedServiceReconnectMaxBackoffSec);

        DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC = dasDiedServiceReconnectBackoffSec;
        DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE = dasDiedServiceReconnectBackoffIncrease;
        DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC = dasDiedServiceReconnectMaxBackoffSec;

    }

    public static DevicePolicyConstants loadFromString(String settings) {
        return new DevicePolicyConstants(settings);
    }

    public void dump(String prefix, PrintWriter pw) {
        pw.print(prefix);
        pw.println("Constants:");

        pw.print(prefix);
        pw.print("  DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC: ");
        pw.println( DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC);

        pw.print(prefix);
        pw.print("  DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE: ");
        pw.println( DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE);

        pw.print(prefix);
        pw.print("  DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC: ");
        pw.println( DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
    }
}
+18 −3
Original line number Diff line number Diff line
@@ -141,6 +141,7 @@ import android.os.storage.StorageManager;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.security.IKeyChainAliasCallback;
import android.security.IKeyChainService;
import android.security.KeyChain;
@@ -364,6 +365,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
    final UserManagerInternal mUserManagerInternal;
    final TelephonyManager mTelephonyManager;
    private final LockPatternUtils mLockPatternUtils;
    private final DevicePolicyConstants mConstants;
    private final DeviceAdminServiceController mDeviceAdminServiceController;

    /**
@@ -1447,7 +1449,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {

    private void handlePackagesChanged(@Nullable String packageName, int userHandle) {
        boolean removedAdmin = false;
        if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
        if (VERBOSE_LOG) {
            Slog.d(LOG_TAG, "Handling package changes package " + packageName
                    + " for user " + userHandle);
        }
        DevicePolicyData policy = getUserData(userHandle);
        synchronized (this) {
            for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
@@ -1760,6 +1765,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            return Settings.Global.getInt(mContext.getContentResolver(), name, def);
        }

        String settingsGlobalGetString(String name) {
            return Settings.Global.getString(mContext.getContentResolver(), name);
        }

        void settingsGlobalPutInt(String name, int value) {
            Settings.Global.putInt(mContext.getContentResolver(), name, value);
        }
@@ -1801,6 +1810,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        mInjector = injector;
        mContext = Preconditions.checkNotNull(injector.mContext);
        mHandler = new Handler(Preconditions.checkNotNull(injector.getMyLooper()));
        mConstants = DevicePolicyConstants.loadFromString(
                mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS));

        mOwners = Preconditions.checkNotNull(injector.newOwners());

        mUserManager = Preconditions.checkNotNull(injector.getUserManager());
@@ -1823,7 +1835,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        // Needed when mHasFeature == false, because it controls the certificate warning text.
        mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);

        mDeviceAdminServiceController = new DeviceAdminServiceController(this);
        mDeviceAdminServiceController = new DeviceAdminServiceController(this, mConstants);

        if (!mHasFeature) {
            // Skip the rest of the initialization
@@ -7354,6 +7366,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {

        synchronized (this) {
            pw.println("Current Device Policy Manager state:");

            mOwners.dump("  ", pw);
            mDeviceAdminServiceController.dump("  ", pw);
            int userCount = mUserData.size();
@@ -7380,6 +7393,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                pw.print("    mPasswordOwner="); pw.println(policy.mPasswordOwner);
            }
            pw.println();
            mConstants.dump("  ", pw);
            pw.println();
            pw.println("  Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
        }
    }
Loading