Merge "Create a job scheduler for CDM association cleanup"
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -6603,6 +6603,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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user