Create a job scheduler for CDM association cleanup

A job scheduler that running daily and revoke associations if
the selfManaged companion device(EXO) does not connect for 3 months.

Fix: 205738945
Test: manually test
Change-Id: I6316c8563deb31ef85fd3193cfc6c3f93b8ad5ef
This commit is contained in:
Evan Chen
2021-12-15 22:18:20 +00:00
parent cb1226d6de
commit 4107e3da28
6 changed files with 201 additions and 8 deletions

View File

@@ -35,6 +35,10 @@ import java.util.Objects;
* an app should have access to a companion device.
*/
public final class AssociationInfo implements Parcelable {
/**
* A String indicates the selfManaged device is not connected.
*/
private static final String LAST_TIME_CONNECTED_NONE = "None";
/**
* A unique ID of this Association record.
* Disclosed to the clients (ie. companion applications) for referring to this record (eg. in
@@ -52,6 +56,11 @@ public final class AssociationInfo implements Parcelable {
private final boolean mSelfManaged;
private boolean mNotifyOnDeviceNearby;
private final long mTimeApprovedMs;
/**
* A long value indicates the last time connected reported by selfManaged devices
* Default value is Long.MAX_VALUE.
*/
private long mLastTimeConnectedMs;
/**
* Creates a new Association.
@@ -62,7 +71,7 @@ public final class AssociationInfo implements Parcelable {
public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
long timeApprovedMs) {
long timeApprovedMs, long lastTimeConnectedMs) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -83,6 +92,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
}
/**
@@ -151,13 +161,21 @@ public final class AssociationInfo implements Parcelable {
}
/**
* Should only be used by the CdmService.
* Should only be used by the CompanionDeviceManagerService.
* @hide
*/
public void setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) {
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
}
/**
* Should only be used by the CompanionDeviceManagerService.
* @hide
*/
public void setLastTimeConnected(long lastTimeConnectedMs) {
mLastTimeConnectedMs = lastTimeConnectedMs;
}
/** @hide */
public boolean isNotifyOnDeviceNearby() {
return mNotifyOnDeviceNearby;
@@ -173,6 +191,14 @@ public final class AssociationInfo implements Parcelable {
return mUserId == userId && Objects.equals(mPackageName, packageName);
}
/**
* @return the last time self reported disconnected for selfManaged only.
* @hide
*/
public Long getLastTimeConnectedMs() {
return mLastTimeConnectedMs;
}
/**
* Utility method for checking if the association represents a device with the given MAC
* address.
@@ -223,6 +249,9 @@ public final class AssociationInfo implements Parcelable {
+ ", mSelfManaged=" + mSelfManaged
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ ", mLastTimeConnectedMs=" + (
mLastTimeConnectedMs == Long.MAX_VALUE
? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs))
+ '}';
}
@@ -236,6 +265,7 @@ public final class AssociationInfo implements Parcelable {
&& mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
&& Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
&& Objects.equals(mDisplayName, that.mDisplayName)
@@ -245,7 +275,8 @@ public final class AssociationInfo implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs);
mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs,
mLastTimeConnectedMs);
}
@Override
@@ -267,6 +298,7 @@ public final class AssociationInfo implements Parcelable {
dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
}
private AssociationInfo(@NonNull Parcel in) {
@@ -282,6 +314,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
}
@NonNull

View File

@@ -6590,6 +6590,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
<service android:name="com.android.server.companion.AssociationCleanUpService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.companion;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.companion.AssociationRequest;
import android.content.ComponentName;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Slog;
import com.android.server.LocalServices;
/**
* A Job Service responsible for clean up the Association.
* The job will be executed only if the device is charging and in idle mode due to the application
* will be killed if association/role are revoked.
*/
public class AssociationCleanUpService extends JobService {
private static final String TAG = LOG_TAG + ".AssociationCleanUpService";
private static final int JOB_ID = AssociationCleanUpService.class.hashCode();
private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day
private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService(
CompanionDeviceManagerServiceInternal.class);
@Override
public boolean onStartJob(final JobParameters params) {
Slog.i(LOG_TAG, "Execute the Association CleanUp job");
// Special policy for APP_STREAMING role that need to revoke associations if the device
// does not connect for 3 months.
AsyncTask.execute(() -> {
mCdmServiceInternal.associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
jobFinished(params, false);
});
return true;
}
@Override
public boolean onStopJob(final JobParameters params) {
Slog.d(TAG, "Association cleanup job stopped; id=" + params.getJobId()
+ ", reason="
+ JobParameters.getInternalReasonCodeDescription(
params.getInternalStopReasonCode()));
return false;
}
static void schedule(Context context) {
Slog.i(LOG_TAG, "Scheduling the Association Cleanup job");
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
final JobInfo job = new JobInfo.Builder(JOB_ID,
new ComponentName(context, AssociationCleanUpService.class))
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.setPeriodic(ONE_DAY_INTERVAL)
.build();
jobScheduler.schedule(job);
}
}

View File

@@ -149,6 +149,9 @@ public class CompanionDeviceManagerService extends SystemService
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
private static final long ASSOCIATION_CLEAN_UP_TIME_WINDOW =
90L * 24 * 60 * 60 * 1000; // 3 months
private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static {
sDateFormat.setTimeZone(TimeZone.getDefault());
@@ -179,6 +182,7 @@ public class CompanionDeviceManagerService extends SystemService
new ArrayMap<>();
private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
new RemoteCallbackList<>();
private final CompanionDeviceManagerServiceInternal mLocalService = new LocalService(this);
final Handler mMainHandler = Handler.getMain();
private CompanionDevicePresenceController mCompanionDevicePresenceController;
@@ -208,6 +212,8 @@ public class CompanionDeviceManagerService extends SystemService
mPermissionControllerManager = requireNonNull(
context.getSystemService(PermissionControllerManager.class));
mUserManager = context.getSystemService(UserManager.class);
LocalServices.addService(CompanionDeviceManagerServiceInternal.class, mLocalService);
}
@Override
@@ -250,6 +256,9 @@ public class CompanionDeviceManagerService extends SystemService
} else {
Slog.w(LOG_TAG, "No BluetoothAdapter available");
}
} else if (phase == PHASE_BOOT_COMPLETED) {
// Run the Association CleanUp job service daily.
AssociationCleanUpService.schedule(getContext());
}
}
@@ -294,6 +303,24 @@ public class CompanionDeviceManagerService extends SystemService
return association;
}
// Revoke associations if the selfManaged companion device does not connect for 3
// months for specific profile.
private void associationCleanUp(String profile) {
for (AssociationInfo ai : mAssociationStore.getAssociations()) {
if (ai.isSelfManaged()
&& profile.equals(ai.getDeviceProfile())
&& System.currentTimeMillis() - ai.getLastTimeConnectedMs()
>= ASSOCIATION_CLEAN_UP_TIME_WINDOW) {
Slog.d(LOG_TAG, "Removing the association for associationId: "
+ ai.getId()
+ " due to the device does not connect for 3 months."
+ " Current time: "
+ new Date(System.currentTimeMillis()));
disassociateInternal(ai.getId());
}
}
}
void maybeGrantAutoRevokeExemptions() {
Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()");
PackageManager pm = getContext().getPackageManager();
@@ -573,6 +600,9 @@ public class CompanionDeviceManagerService extends SystemService
return;
}
association.setLastTimeConnected(System.currentTimeMillis());
mAssociationStore.updateAssociation(association);
mCompanionDevicePresenceController.onDeviceNotifyAppeared(
association, getContext(), mMainHandler);
}
@@ -735,7 +765,8 @@ public class CompanionDeviceManagerService extends SystemService
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
macAddress, displayName, deviceProfile, selfManaged, false, timestamp);
macAddress, displayName, deviceProfile, selfManaged, false, timestamp,
Long.MAX_VALUE);
Slog.i(LOG_TAG, "New CDM association created=" + association);
mAssociationStore.addAssociation(association);
@@ -1297,4 +1328,17 @@ public class CompanionDeviceManagerService extends SystemService
return Collections.unmodifiableMap(copy);
}
private final class LocalService extends CompanionDeviceManagerServiceInternal {
private final CompanionDeviceManagerService mService;
LocalService(CompanionDeviceManagerService service) {
mService = service;
}
@Override
public void associationCleanUp(String profile) {
mService.associationCleanUp(profile);
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.companion;
/**
* Companion Device Manager Local System Service Interface.
*
* @hide Only for use within the system server.
*/
public abstract class CompanionDeviceManagerServiceInternal {
/**
* @see CompanionDeviceManagerService#associationCleanUp
*/
public abstract void associationCleanUp(String profile);
}

View File

@@ -168,6 +168,7 @@ final class PersistentDataStore {
private static final String XML_ATTR_SELF_MANAGED = "self_managed";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
private static final String LEGACY_XML_ATTR_DEVICE = "device";
@@ -378,7 +379,7 @@ final class PersistentDataStore {
out.add(new AssociationInfo(associationId, userId, appPackage,
MacAddress.fromString(deviceAddress), null, profile,
/* managedByCompanionApp */false, notify, timeApproved));
/* managedByCompanionApp */false, notify, timeApproved, Long.MAX_VALUE));
}
private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -408,9 +409,12 @@ final class PersistentDataStore {
final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
final long lastTimeConnected = readLongAttribute(
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved);
appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved,
lastTimeConnected);
if (associationInfo != null) {
out.add(associationInfo);
}
@@ -464,6 +468,8 @@ final class PersistentDataStore {
writeBooleanAttribute(
serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
serializer.endTag(null, XML_TAG_ASSOCIATION);
}
@@ -512,11 +518,11 @@ final class PersistentDataStore {
private static AssociationInfo createAssociationInfoNoThrow(int associationId,
@UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress,
@Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged,
boolean notify, long timeApproved) {
boolean notify, long timeApproved, long lastTimeConnected) {
AssociationInfo associationInfo = null;
try {
associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
displayName, profile, selfManaged, notify, timeApproved);
displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected);
} catch (Exception e) {
if (DEBUG) Slog.w(LOG_TAG, "Could not create AssociationInfo", e);
}