package com.lody.virtual.server.job;
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.text.TextUtils;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.client.ipc.VJobScheduler;
import com.lody.virtual.client.stub.StubManifest;
import com.lody.virtual.helper.utils.Singleton;
import com.lody.virtual.os.VBinder;
import com.lody.virtual.os.VEnvironment;
import com.lody.virtual.server.IJobScheduler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author Lody
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class VJobSchedulerService extends IJobScheduler.Stub {
private static final String TAG = VJobScheduler.class.getSimpleName();
private static final int JOB_FILE_VERSION = 1;
private final Map<JobId, JobConfig> mJobStore = new HashMap<>();
private int mGlobalJobId;
private final JobScheduler mScheduler = (JobScheduler)
VirtualCore.get().getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
private final ComponentName mJobProxyComponent;
private VJobSchedulerService() {
mJobProxyComponent = new ComponentName(VirtualCore.get().getHostPkg(), StubManifest.STUB_JOB);
readJobs();
}
private static final Singleton<VJobSchedulerService> gDefault = new Singleton<VJobSchedulerService>() {
@Override
protected VJobSchedulerService create() {
return new VJobSchedulerService();
}
};
public static VJobSchedulerService get() {
return gDefault.get();
}
public static final class JobId implements Parcelable {
public int vuid;
public String packageName;
/**
* The id given by User.
*/
public int clientJobId;
JobId(int vuid, String packageName, int id) {
this.vuid = vuid;
this.packageName = packageName;
this.clientJobId = id;
}
JobId(Parcel in) {
this.vuid = in.readInt();
this.packageName = in.readString();
this.clientJobId = in.readInt();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JobId jobId = (JobId) o;
return vuid == jobId.vuid
&& clientJobId == jobId.clientJobId
&& TextUtils.equals(packageName, jobId.packageName);
}
@Override
public int hashCode() {
int result = vuid;
result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
result = 31 * result + clientJobId;
return result;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.vuid);
dest.writeString(this.packageName);
dest.writeInt(this.clientJobId);
}
public static final Parcelable.Creator<JobId> CREATOR = new Parcelable.Creator<JobId>() {
@Override
public JobId createFromParcel(Parcel source) {
return new JobId(source);
}
@Override
public JobId[] newArray(int size) {
return new JobId[size];
}
};
}
public static final class JobConfig implements Parcelable {
/**
* The id given by VA.
*/
public int virtualJobId;
public String serviceName;
public PersistableBundle extras;
JobConfig(int virtualJobId, String serviceName, PersistableBundle extra) {
this.virtualJobId = virtualJobId;
this.serviceName = serviceName;
this.extras = extra;
}
JobConfig(Parcel in) {
this.virtualJobId = in.readInt();
this.serviceName = in.readString();
this.extras = in.readParcelable(PersistableBundle.class.getClassLoader());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.virtualJobId);
dest.writeString(this.serviceName);
dest.writeParcelable(this.extras, flags);
}
public static final Parcelable.Creator<JobConfig> CREATOR = new Parcelable.Creator<JobConfig>() {
@Override
public JobConfig createFromParcel(Parcel source) {
return new JobConfig(source);
}
@Override
public JobConfig[] newArray(int size) {
return new JobConfig[size];
}
};
}
@Override
public int schedule(JobInfo job) throws RemoteException {
int vuid = VBinder.getCallingUid();
int id = job.getId();
ComponentName service = job.getService();
JobId jobId = new JobId(vuid, service.getPackageName(), id);
JobConfig config = mJobStore.get(jobId);
if (config == null) {
config = new JobConfig(mGlobalJobId++, service.getClassName(), job.getExtras());
mJobStore.put(jobId, config);
} else {
config.serviceName = service.getClassName();
config.extras = job.getExtras();
}
saveJobs();
mirror.android.app.job.JobInfo.jobId.set(job, config.virtualJobId);
mirror.android.app.job.JobInfo.service.set(job, mJobProxyComponent);
return mScheduler.schedule(job);
}
private void saveJobs() {
File jobFile = VEnvironment.getJobConfigFile();
Parcel p = Parcel.obtain();
try {
p.writeInt(JOB_FILE_VERSION);
p.writeInt(mJobStore.size());
for (Map.Entry<JobId, JobConfig> entry : mJobStore.entrySet()) {
entry.getKey().writeToParcel(p, 0);
entry.getValue().writeToParcel(p, 0);
}
FileOutputStream fos = new FileOutputStream(jobFile);
fos.write(p.marshall());
fos.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
p.recycle();
}
}
private void readJobs() {
File jobFile = VEnvironment.getJobConfigFile();
if (!jobFile.exists()) {
return;
}
Parcel p = Parcel.obtain();
try {
FileInputStream fis = new FileInputStream(jobFile);
byte[] bytes = new byte[(int) jobFile.length()];
int len = fis.read(bytes);
fis.close();
if (len != bytes.length) {
throw new IOException("Unable to read job config.");
}
p.unmarshall(bytes, 0, bytes.length);
p.setDataPosition(0);
int version = p.readInt();
if (version != JOB_FILE_VERSION) {
throw new IOException("Bad version of job file: " + version);
}
if (!mJobStore.isEmpty()) {
mJobStore.clear();
}
int count = p.readInt();
for (int i = 0; i < count; i++) {
JobId jobId = new JobId(p);
JobConfig config = new JobConfig(p);
mJobStore.put(jobId, config);
mGlobalJobId = Math.max(mGlobalJobId, config.virtualJobId);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
p.recycle();
}
}
@Override
public void cancel(int jobId) throws RemoteException {
int vuid = VBinder.getCallingUid();
synchronized (mJobStore) {
boolean changed = false;
Iterator<Map.Entry<JobId, JobConfig>> iterator = mJobStore.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<JobId, JobConfig> entry = iterator.next();
JobId job = entry.getKey();
JobConfig config = entry.getValue();
if (job.vuid == vuid && job.clientJobId == jobId) {
changed = true;
mScheduler.cancel(config.virtualJobId);
iterator.remove();
break;
}
}
if (changed) {
saveJobs();
}
}
}
@Override
public void cancelAll() throws RemoteException {
int vuid = VBinder.getCallingUid();
synchronized (mJobStore) {
boolean changed = false;
Iterator<Map.Entry<JobId, JobConfig>> iterator = mJobStore.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<JobId, JobConfig> entry = iterator.next();
JobId job = entry.getKey();
if (job.vuid == vuid) {
JobConfig config = entry.getValue();
mScheduler.cancel(config.virtualJobId);
changed = true;
iterator.remove();
break;
}
}
if (changed) {
saveJobs();
}
}
}
@Override
public List<JobInfo> getAllPendingJobs() throws RemoteException {
int vuid = VBinder.getCallingUid();
List<JobInfo> jobs = mScheduler.getAllPendingJobs();
synchronized (mJobStore) {
Iterator<JobInfo> iterator = jobs.listIterator();
while (iterator.hasNext()) {
JobInfo job = iterator.next();
if (!StubManifest.STUB_JOB.equals(job.getService().getClassName())) {
// Schedule by Host, invisible in VA.
iterator.remove();
continue;
}
Map.Entry<JobId, JobConfig> jobEntry = findJobByVirtualJobId(job.getId());
if (jobEntry == null) {
iterator.remove();
continue;
}
JobId jobId = jobEntry.getKey();
JobConfig config = jobEntry.getValue();
if (jobId.vuid != vuid) {
iterator.remove();
continue;
}
mirror.android.app.job.JobInfo.jobId.set(job, jobId.clientJobId);
mirror.android.app.job.JobInfo.service.set(job, new ComponentName(jobId.packageName, config.serviceName));
}
}
return jobs;
}
public Map.Entry<JobId, JobConfig> findJobByVirtualJobId(int virtualJobId) {
synchronized (mJobStore) {
for (Map.Entry<JobId, JobConfig> entry : mJobStore.entrySet()) {
if (entry.getValue().virtualJobId == virtualJobId) {
return entry;
}
}
return null;
}
}
}