blob: b9afdff0b8f592e50b6bbbbd9d2a34edde67505c [file] [log] [blame]
/*
* Copyright (C) 2023 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.providers.media.photopicker.sync;
import android.content.Context;
import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.work.Configuration;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.Operation;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.android.providers.media.ConfigStore;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* This class manages all the triggers for Picker syncs.
* <p></p>
* There are different use cases for triggering a sync:
* <p>
* 1. Proactive sync - these syncs are proactively performed to minimize the changes that need to be
* synced when the user opens the Photo Picker. The sync should only be performed if the device
* state allows it.
* <p>
* 2. Reactive sync - these syncs are triggered by the user opening the Photo Picker. These should
* be run immediately since the user is likely to be waiting for the sync response on the UI.
*/
public class PickerSyncManager {
private static final String TAG = "SyncWorkManager";
public static final int SYNC_LOCAL_ONLY = 1;
public static final int SYNC_CLOUD_ONLY = 2;
public static final int SYNC_LOCAL_AND_CLOUD = 3;
@IntDef(value = { SYNC_LOCAL_ONLY, SYNC_CLOUD_ONLY, SYNC_LOCAL_AND_CLOUD })
@Retention(RetentionPolicy.SOURCE)
public @interface SyncSource {}
static final String SYNC_WORKER_INPUT_SYNC_SOURCE = "INPUT_SYNC_TYPE";
static final String SYNC_WORKER_INPUT_ALBUM_ID = "INPUT_ALBUM_ID";
private static final int SYNC_MEDIA_PERIODIC_WORK_INTERVAL = 4; // Time unit is hours.
private static final String SYNC_MEDIA_PERIODIC_SYNC_PREFIX = "SYNC_MEDIA_PERIODIC_";
private static final String SYNC_MEDIA_PROACTIVE_WORK_PREFIX = "SYNC_MEDIA_PROACTIVE_";
private static final String SYNC_ALL_WORK_SUFFIX = "ALL";
private final Context mContext;
public PickerSyncManager(@NonNull Context context,
@NonNull ConfigStore configStore,
boolean schedulePeriodicSyncs) {
mContext = context;
if (schedulePeriodicSyncs && configStore.isCloudMediaInPhotoPickerEnabled()) {
schedulePeriodicSyncs();
}
}
private void schedulePeriodicSyncs() {
Log.i(TAG, "Scheduling periodic proactive syncs");
final Data inputData =
new Data(Map.of(SYNC_WORKER_INPUT_SYNC_SOURCE, SYNC_LOCAL_AND_CLOUD));
final PeriodicWorkRequest periodicSyncRequest = getPeriodicProactiveSyncRequest(inputData);
try {
Operation enqueueOperation = getWorkManager()
.enqueueUniquePeriodicWork(
SYNC_MEDIA_PERIODIC_SYNC_PREFIX + SYNC_ALL_WORK_SUFFIX,
ExistingPeriodicWorkPolicy.KEEP,
periodicSyncRequest
);
// Check that the request has been successfully enqueued.
enqueueOperation.getResult().get();
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "Could not enqueue periodic proactive picker sync request", e);
}
}
/**
* Use this method for proactive syncs. The sync might take a while to start. Some device state
* conditions may apply before the sync can start like battery level etc.
*/
public void syncAllMediaProactively() {
Data inputData = new Data(Map.of(SYNC_WORKER_INPUT_SYNC_SOURCE, SYNC_LOCAL_AND_CLOUD));
OneTimeWorkRequest syncRequest = getOneTimeProactiveSyncRequest(inputData);
String workName = SYNC_MEDIA_PROACTIVE_WORK_PREFIX + SYNC_ALL_WORK_SUFFIX;
try {
Operation enqueueOperation = getWorkManager()
.beginUniqueWork(workName, ExistingWorkPolicy.KEEP, syncRequest)
.enqueue();
// Check that the request has been successfully enqueued.
enqueueOperation.getResult().get();
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "Could not enqueue proactive picker sync request", e);
}
}
/**
* Use this method for reactive syncs which are user triggered.
*
* @param isLocal is true when the authority when the sync type is local.
* For cloud syncs, this is false.
*/
public void syncMediaForProviderImmediately(boolean isLocal) {
// TODO
throw new UnsupportedOperationException(
"syncMediaForProviderImmediately is not supported.");
}
/**
* Use this method for reactive syncs which are user triggered.
*
* @param albumId is the id of the album that needs to be synced.
* @param isLocal is true when the authority when the sync type is local.
* For cloud syncs, this is false.
*/
public void syncAlbumMediaForProviderImmediately(
@NonNull String albumId,
boolean isLocal) {
// TODO
throw new UnsupportedOperationException(
"syncAlbumMediaForProviderImmediately is not supported.");
}
/**
* Initialize the {@link WorkManager} if it is not initialized already.
*
* @return a {@link WorkManager} object that can be used to run work requests.
*/
@NotNull
private WorkManager getWorkManager() {
if (!WorkManager.isInitialized()) {
Log.i(TAG, "Work manager not initialised. Attempting to initialise.");
WorkManager.initialize(mContext, getWorkManagerConfiguration());
}
return WorkManager.getInstance(mContext);
}
@NotNull
private static Configuration getWorkManagerConfiguration() {
return new Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build();
}
@NotNull
private PeriodicWorkRequest getPeriodicProactiveSyncRequest(@NotNull Data inputData) {
return new PeriodicWorkRequest.Builder(
ProactiveSyncWorker.class, SYNC_MEDIA_PERIODIC_WORK_INTERVAL, TimeUnit.HOURS)
.setInputData(inputData)
.build();
}
@NotNull
private OneTimeWorkRequest getOneTimeProactiveSyncRequest(@NotNull Data inputData) {
return new OneTimeWorkRequest.Builder(ProactiveSyncWorker.class)
.setInputData(inputData)
.setConstraints(getProactiveSyncConstraints())
.build();
}
@NotNull
private static Constraints getProactiveSyncConstraints() {
// TODO these constraints are not finalised.
return new Constraints.Builder()
.setRequiresBatteryNotLow(true)
.build();
}
}