Merge "Reconnect to DAService after binding is dead" into oc-dev
am: ef5700667f
Change-Id: I03049914406ed38f92c3a6a43a82f55504c6e518
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,23 +212,51 @@ 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;
|
||||
}
|
||||
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));
|
||||
|
||||
if (!success) {
|
||||
Slog.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
|
||||
+ " failed.");
|
||||
}
|
||||
}
|
||||
|
||||
final void bindForBackoff() {
|
||||
synchronized (mLock) {
|
||||
if (!mShouldBeBound) {
|
||||
// Race condition -- by the time we got here, unbind() has already been called.
|
||||
return;
|
||||
}
|
||||
mStarted = true;
|
||||
|
||||
final Intent service = new Intent().setComponent(mComponentName);
|
||||
|
||||
final boolean success = mContext.bindServiceAsUser(service, mServiceConnection,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
|
||||
mHandler, UserHandle.of(mUserId));
|
||||
|
||||
if (!success) {
|
||||
Slog.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
|
||||
+ " failed.");
|
||||
}
|
||||
bindInnerLocked(/* resetBackoff= */ false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,16 +268,46 @@ public abstract class PersistentConnection<T> {
|
||||
/**
|
||||
* Disconnect from the service.
|
||||
*/
|
||||
public final void disconnect() {
|
||||
public final void unbind() {
|
||||
synchronized (mLock) {
|
||||
if (!mStarted) {
|
||||
return;
|
||||
}
|
||||
Slog.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
|
||||
mStarted = false;
|
||||
mContext.unbindService(mServiceConnection);
|
||||
mShouldBeBound = false;
|
||||
|
||||
cleanUpConnectionLocked();
|
||||
unbindLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private final void unbindLocked() {
|
||||
unscheduleRebindLocked();
|
||||
|
||||
if (!mBound) {
|
||||
return;
|
||||
}
|
||||
Slog.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -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,7 +7393,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
pw.print(" mPasswordOwner="); pw.println(policy.mPasswordOwner);
|
||||
}
|
||||
pw.println();
|
||||
pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
|
||||
mConstants.dump(" ", pw);
|
||||
pw.println();
|
||||
pw.println(" Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* 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.am;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.admin.IDeviceAdminService;
|
||||
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.UserHandle;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.mockito.ArgumentMatchers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
public class PersistentConnectionTest extends AndroidTestCase {
|
||||
private static class MyConnection extends PersistentConnection<IDeviceAdminService> {
|
||||
public long uptimeMillis = 12345;
|
||||
|
||||
public ArrayList<Pair<Runnable, Long>> scheduledRunnables = new ArrayList<>();
|
||||
|
||||
public MyConnection(String tag, Context context, Handler handler, int userId,
|
||||
ComponentName componentName, long rebindBackoffSeconds,
|
||||
double rebindBackoffIncrease, long rebindMaxBackoffSeconds) {
|
||||
super(tag, context, handler, userId, componentName,
|
||||
rebindBackoffSeconds, rebindBackoffIncrease, rebindMaxBackoffSeconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IDeviceAdminService asInterface(IBinder binder) {
|
||||
return (IDeviceAdminService) binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
long injectUptimeMillis() {
|
||||
return uptimeMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
void injectPostAtTime(Runnable r, long uptimeMillis) {
|
||||
scheduledRunnables.add(Pair.create(r, uptimeMillis));
|
||||
}
|
||||
|
||||
@Override
|
||||
void injectRemoveCallbacks(Runnable r) {
|
||||
for (int i = scheduledRunnables.size() - 1; i >= 0; i--) {
|
||||
if (scheduledRunnables.get(i).first.equals(r)) {
|
||||
scheduledRunnables.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void elapse(long milliSeconds) {
|
||||
uptimeMillis += milliSeconds;
|
||||
|
||||
// Fire the scheduled runnables.
|
||||
|
||||
// Note we collect first and then run all, because sometimes a scheduled runnable
|
||||
// calls removeCallbacks.
|
||||
final ArrayList<Runnable> list = new ArrayList<>();
|
||||
|
||||
for (int i = scheduledRunnables.size() - 1; i >= 0; i--) {
|
||||
if (scheduledRunnables.get(i).second <= uptimeMillis) {
|
||||
list.add(scheduledRunnables.get(i).first);
|
||||
scheduledRunnables.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(list);
|
||||
for (Runnable r : list) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testAll() {
|
||||
final Context context = mock(Context.class);
|
||||
final int userId = 11;
|
||||
final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def");
|
||||
final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
final MyConnection conn = new MyConnection("tag", context, handler, userId, cn,
|
||||
/* rebindBackoffSeconds= */ 5,
|
||||
/* rebindBackoffIncrease= */ 1.5,
|
||||
/* rebindMaxBackoffSeconds= */ 11);
|
||||
|
||||
assertFalse(conn.isBound());
|
||||
assertFalse(conn.isConnected());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
assertEquals(5000, conn.getNextBackoffMsForTest());
|
||||
assertNull(conn.getServiceBinder());
|
||||
|
||||
when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(),
|
||||
any(Handler.class), any(UserHandle.class)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Call bind.
|
||||
conn.bind();
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
assertNull(conn.getServiceBinder());
|
||||
|
||||
assertEquals(5000, conn.getNextBackoffMsForTest());
|
||||
|
||||
verify(context).bindServiceAsUser(
|
||||
ArgumentMatchers.argThat(intent -> cn.equals(intent.getComponent())),
|
||||
eq(conn.getServiceConnectionForTest()),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(handler), eq(UserHandle.of(userId)));
|
||||
|
||||
// AM responds...
|
||||
conn.getServiceConnectionForTest().onServiceConnected(cn,
|
||||
new IDeviceAdminService.Stub() {});
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertTrue(conn.isConnected());
|
||||
assertNotNull(conn.getServiceBinder());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(5000, conn.getNextBackoffMsForTest());
|
||||
|
||||
|
||||
// Now connected.
|
||||
|
||||
// Call unbind...
|
||||
conn.unbind();
|
||||
assertFalse(conn.isBound());
|
||||
assertFalse(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertNull(conn.getServiceBinder());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
// Caller bind again...
|
||||
conn.bind();
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
assertNull(conn.getServiceBinder());
|
||||
|
||||
assertEquals(5000, conn.getNextBackoffMsForTest());
|
||||
|
||||
|
||||
// Now connected again.
|
||||
|
||||
// The service got killed...
|
||||
conn.getServiceConnectionForTest().onServiceDisconnected(cn);
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertNull(conn.getServiceBinder());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(5000, conn.getNextBackoffMsForTest());
|
||||
|
||||
// Connected again...
|
||||
conn.getServiceConnectionForTest().onServiceConnected(cn,
|
||||
new IDeviceAdminService.Stub() {});
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertTrue(conn.isConnected());
|
||||
assertNotNull(conn.getServiceBinder());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(5000, conn.getNextBackoffMsForTest());
|
||||
|
||||
|
||||
// Then the binding is "died"...
|
||||
conn.getServiceConnectionForTest().onBindingDied(cn);
|
||||
|
||||
assertFalse(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertNull(conn.getServiceBinder());
|
||||
assertTrue(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(7500, conn.getNextBackoffMsForTest());
|
||||
|
||||
assertEquals(
|
||||
Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
|
||||
conn.uptimeMillis + 5000)),
|
||||
conn.scheduledRunnables);
|
||||
|
||||
// 5000 ms later...
|
||||
conn.elapse(5000);
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertNull(conn.getServiceBinder());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(7500, conn.getNextBackoffMsForTest());
|
||||
|
||||
// Connected.
|
||||
conn.getServiceConnectionForTest().onServiceConnected(cn,
|
||||
new IDeviceAdminService.Stub() {});
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertTrue(conn.isConnected());
|
||||
assertNotNull(conn.getServiceBinder());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(7500, conn.getNextBackoffMsForTest());
|
||||
|
||||
// Then the binding is "died"...
|
||||
conn.getServiceConnectionForTest().onBindingDied(cn);
|
||||
|
||||
assertFalse(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertNull(conn.getServiceBinder());
|
||||
assertTrue(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(11000, conn.getNextBackoffMsForTest());
|
||||
|
||||
assertEquals(
|
||||
Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
|
||||
conn.uptimeMillis + 7500)),
|
||||
conn.scheduledRunnables);
|
||||
|
||||
// Later...
|
||||
conn.elapse(7500);
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertNull(conn.getServiceBinder());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(11000, conn.getNextBackoffMsForTest());
|
||||
|
||||
|
||||
// Then the binding is "died"...
|
||||
conn.getServiceConnectionForTest().onBindingDied(cn);
|
||||
|
||||
assertFalse(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertNull(conn.getServiceBinder());
|
||||
assertTrue(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(11000, conn.getNextBackoffMsForTest());
|
||||
|
||||
assertEquals(
|
||||
Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
|
||||
conn.uptimeMillis + 11000)),
|
||||
conn.scheduledRunnables);
|
||||
|
||||
// Call unbind...
|
||||
conn.unbind();
|
||||
assertFalse(conn.isBound());
|
||||
assertFalse(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertNull(conn.getServiceBinder());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
// Call bind again... And now the backoff is reset to 5000.
|
||||
conn.bind();
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isConnected());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
assertNull(conn.getServiceBinder());
|
||||
|
||||
assertEquals(5000, conn.getNextBackoffMsForTest());
|
||||
}
|
||||
|
||||
public void testReconnectFiresAfterUnbind() {
|
||||
final Context context = mock(Context.class);
|
||||
final int userId = 11;
|
||||
final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def");
|
||||
final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
final MyConnection conn = new MyConnection("tag", context, handler, userId, cn,
|
||||
/* rebindBackoffSeconds= */ 5,
|
||||
/* rebindBackoffIncrease= */ 1.5,
|
||||
/* rebindMaxBackoffSeconds= */ 11);
|
||||
|
||||
when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(),
|
||||
any(Handler.class), any(UserHandle.class)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Bind.
|
||||
conn.bind();
|
||||
|
||||
assertTrue(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertFalse(conn.isRebindScheduled());
|
||||
|
||||
conn.elapse(1000);
|
||||
|
||||
// Service crashes.
|
||||
conn.getServiceConnectionForTest().onBindingDied(cn);
|
||||
|
||||
assertFalse(conn.isBound());
|
||||
assertTrue(conn.shouldBeBoundForTest());
|
||||
assertTrue(conn.isRebindScheduled());
|
||||
|
||||
assertEquals(7500, conn.getNextBackoffMsForTest());
|
||||
|
||||
// Call unbind.
|
||||
conn.unbind();
|
||||
assertFalse(conn.isBound());
|
||||
assertFalse(conn.shouldBeBoundForTest());
|
||||
|
||||
// Now, at this point, it's possible that the scheduled runnable had already been fired
|
||||
// before during the unbind() call, and waiting on mLock.
|
||||
// To simulate it, we just call the runnable here.
|
||||
conn.getBindForBackoffRunnableForTest().run();
|
||||
|
||||
// Should still not be bound.
|
||||
assertFalse(conn.isBound());
|
||||
assertFalse(conn.shouldBeBoundForTest());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.test.AndroidTestCase;
|
||||
|
||||
/**
|
||||
* Test for {@link DevicePolicyConstants}.
|
||||
*
|
||||
m FrameworksServicesTests &&
|
||||
adb install \
|
||||
-r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
|
||||
adb shell am instrument -e class com.android.server.devicepolicy.DevicePolicyConstantsTest \
|
||||
-w com.android.frameworks.servicestests
|
||||
|
||||
|
||||
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
|
||||
*/
|
||||
public class DevicePolicyConstantsTest extends AndroidTestCase {
|
||||
private static final String TAG = "DevicePolicyConstantsTest";
|
||||
|
||||
public void testDefaultValues() throws Exception {
|
||||
final DevicePolicyConstants constants = DevicePolicyConstants.loadFromString("");
|
||||
|
||||
assertEquals(1 * 60 * 60, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC);
|
||||
assertEquals(24 * 60 * 60, constants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
|
||||
assertEquals(2.0, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE);
|
||||
}
|
||||
|
||||
public void testCustomValues() throws Exception {
|
||||
final DevicePolicyConstants constants = DevicePolicyConstants.loadFromString(
|
||||
"das_died_service_reconnect_backoff_sec=10,"
|
||||
+ "das_died_service_reconnect_backoff_increase=1.25,"
|
||||
+ "das_died_service_reconnect_max_backoff_sec=15"
|
||||
);
|
||||
|
||||
assertEquals(10, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC);
|
||||
assertEquals(15, constants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
|
||||
assertEquals(1.25, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE);
|
||||
}
|
||||
|
||||
public void testMinMax() throws Exception {
|
||||
final DevicePolicyConstants constants = DevicePolicyConstants.loadFromString(
|
||||
"das_died_service_reconnect_backoff_sec=3,"
|
||||
+ "das_died_service_reconnect_backoff_increase=.25,"
|
||||
+ "das_died_service_reconnect_max_backoff_sec=1"
|
||||
);
|
||||
|
||||
assertEquals(5, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC);
|
||||
assertEquals(5, constants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
|
||||
assertEquals(1.0, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE);
|
||||
}
|
||||
}
|
||||
@@ -368,6 +368,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
|
||||
return context.settings.settingsGlobalGetInt(name, def);
|
||||
}
|
||||
|
||||
@Override
|
||||
String settingsGlobalGetString(String name) {
|
||||
return context.settings.settingsGlobalGetString(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
void securityLogSetLoggingEnabledProperty(boolean enabled) {
|
||||
context.settings.securityLogSetLoggingEnabledProperty(enabled);
|
||||
|
||||
@@ -233,6 +233,10 @@ public class DpmMockContext extends MockContext {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public String settingsGlobalGetString(String name) {
|
||||
return "";
|
||||
}
|
||||
|
||||
public void securityLogSetLoggingEnabledProperty(boolean enabled) {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user