package com.lody.virtual.client.stub; import android.annotation.TargetApi; import android.app.Service; import android.app.job.IJobCallback; import android.app.job.IJobService; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import com.lody.virtual.client.core.InvocationStubManager; import com.lody.virtual.client.hook.proxies.am.ActivityManagerStub; import com.lody.virtual.helper.utils.VLog; import com.lody.virtual.helper.collection.SparseArray; import com.lody.virtual.os.VUserHandle; import java.util.Map; import static com.lody.virtual.server.job.VJobSchedulerService.JobConfig; import static com.lody.virtual.server.job.VJobSchedulerService.JobId; import static com.lody.virtual.server.job.VJobSchedulerService.get; /** * @author Lody * <p> * This service running on the Server process. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class StubJob extends Service { private static final String TAG = StubJob.class.getSimpleName(); private final SparseArray<JobSession> mJobSessions = new SparseArray<>(); private JobScheduler mScheduler; private final IJobService mService = new IJobService.Stub() { @Override public void startJob(JobParameters jobParams) throws RemoteException { int jobId = jobParams.getJobId(); IBinder binder = mirror.android.app.job.JobParameters.callback.get(jobParams); IJobCallback callback = IJobCallback.Stub.asInterface(binder); Map.Entry<JobId, JobConfig> entry = get().findJobByVirtualJobId(jobId); if (entry == null) { emptyCallback(callback, jobId); mScheduler.cancel(jobId); } else { JobId key = entry.getKey(); JobConfig config = entry.getValue(); synchronized (mJobSessions) { JobSession session = mJobSessions.get(jobId); if (session != null) { // Job Session has exist. emptyCallback(callback, jobId); } else { session = new JobSession(jobId, callback, jobParams); mirror.android.app.job.JobParameters.callback.set(jobParams, session.asBinder()); mirror.android.app.job.JobParameters.jobId.set(jobParams, key.clientJobId); Intent service = new Intent(); service.setComponent(new ComponentName(key.packageName, config.serviceName)); service.putExtra("_VA_|_user_id_", VUserHandle.getUserId(key.vuid)); boolean bound = false; try { bound = bindService(service, session, 0); } catch (Throwable e) { VLog.e(TAG, e); } if (bound) { mJobSessions.put(jobId, session); } else { emptyCallback(callback, jobId); mScheduler.cancel(jobId); get().cancel(jobId); } } } } } @Override public void stopJob(JobParameters jobParams) throws RemoteException { int jobId = jobParams.getJobId(); synchronized (mJobSessions) { JobSession session = mJobSessions.get(jobId); if (session != null) { session.stopSession(); } } } }; /** * Make JobScheduler happy. */ private void emptyCallback(IJobCallback callback, int jobId) { try { callback.acknowledgeStartMessage(jobId, false); callback.jobFinished(jobId, false); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onCreate() { super.onCreate(); InvocationStubManager.getInstance().checkEnv(ActivityManagerStub.class); mScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE); } @Override public IBinder onBind(Intent intent) { return mService.asBinder(); } private final class JobSession extends IJobCallback.Stub implements ServiceConnection { private int jobId; private IJobCallback clientCallback; private JobParameters jobParams; private IJobService clientJobService; JobSession(int jobId, IJobCallback clientCallback, JobParameters jobParams) { this.jobId = jobId; this.clientCallback = clientCallback; this.jobParams = jobParams; } @Override public void acknowledgeStartMessage(int jobId, boolean ongoing) throws RemoteException { clientCallback.acknowledgeStartMessage(jobId, ongoing); } @Override public void acknowledgeStopMessage(int jobId, boolean reschedule) throws RemoteException { clientCallback.acknowledgeStopMessage(jobId, reschedule); } @Override public void jobFinished(int jobId, boolean reschedule) throws RemoteException { clientCallback.jobFinished(jobId, reschedule); } @Override public void onServiceConnected(ComponentName name, IBinder service) { clientJobService = IJobService.Stub.asInterface(service); if (clientJobService == null) { emptyCallback(clientCallback, jobId); stopSession(); return; } try { clientJobService.startJob(jobParams); } catch (RemoteException e) { forceFinishJob(); e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } void forceFinishJob() { try { clientCallback.jobFinished(jobId, false); } catch (RemoteException e) { e.printStackTrace(); } finally { stopSession(); } } void stopSession() { if (clientJobService != null) { try { clientJobService.stopJob(jobParams); } catch (RemoteException e) { e.printStackTrace(); } } mJobSessions.remove(jobId); unbindService(this); } } }