PageRenderTime 118ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/services/core/java/com/android/server/job/controllers/ContentObserverController.java

https://gitlab.com/amardeep434/nitro_base
Java | 459 lines | 378 code | 30 blank | 51 comment | 89 complexity | c0fd51660571b011e45af275ad32ebac MD5 | raw file
  1. /*
  2. * Copyright (C) 2016 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License
  15. */
  16. package com.android.server.job.controllers;
  17. import android.annotation.UserIdInt;
  18. import android.app.job.JobInfo;
  19. import android.content.Context;
  20. import android.database.ContentObserver;
  21. import android.net.Uri;
  22. import android.os.Handler;
  23. import android.os.UserHandle;
  24. import android.util.Slog;
  25. import android.util.SparseArray;
  26. import android.util.TimeUtils;
  27. import android.util.ArrayMap;
  28. import android.util.ArraySet;
  29. import com.android.internal.annotations.VisibleForTesting;
  30. import com.android.server.job.JobSchedulerService;
  31. import com.android.server.job.StateChangedListener;
  32. import java.io.PrintWriter;
  33. import java.util.ArrayList;
  34. import java.util.Iterator;
  35. import java.util.List;
  36. import java.util.Objects;
  37. /**
  38. * Controller for monitoring changes to content URIs through a ContentObserver.
  39. */
  40. public class ContentObserverController extends StateController {
  41. private static final String TAG = "JobScheduler.Content";
  42. private static final boolean DEBUG = false;
  43. /**
  44. * Maximum number of changing URIs we will batch together to report.
  45. * XXX Should be smarter about this, restricting it by the maximum number
  46. * of characters we will retain.
  47. */
  48. private static final int MAX_URIS_REPORTED = 50;
  49. /**
  50. * At this point we consider it urgent to schedule the job ASAP.
  51. */
  52. private static final int URIS_URGENT_THRESHOLD = 40;
  53. private static final Object sCreationLock = new Object();
  54. private static volatile ContentObserverController sController;
  55. final private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
  56. /**
  57. * Per-userid {@link JobInfo.TriggerContentUri} keyed ContentObserver cache.
  58. */
  59. SparseArray<ArrayMap<JobInfo.TriggerContentUri, ObserverInstance>> mObservers =
  60. new SparseArray<>();
  61. final Handler mHandler;
  62. public static ContentObserverController get(JobSchedulerService taskManagerService) {
  63. synchronized (sCreationLock) {
  64. if (sController == null) {
  65. sController = new ContentObserverController(taskManagerService,
  66. taskManagerService.getContext(), taskManagerService.getLock());
  67. }
  68. }
  69. return sController;
  70. }
  71. @VisibleForTesting
  72. public static ContentObserverController getForTesting(StateChangedListener stateChangedListener,
  73. Context context) {
  74. return new ContentObserverController(stateChangedListener, context, new Object());
  75. }
  76. private ContentObserverController(StateChangedListener stateChangedListener, Context context,
  77. Object lock) {
  78. super(stateChangedListener, context, lock);
  79. mHandler = new Handler(context.getMainLooper());
  80. }
  81. @Override
  82. public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
  83. if (taskStatus.hasContentTriggerConstraint()) {
  84. if (taskStatus.contentObserverJobInstance == null) {
  85. taskStatus.contentObserverJobInstance = new JobInstance(taskStatus);
  86. }
  87. if (DEBUG) {
  88. Slog.i(TAG, "Tracking content-trigger job " + taskStatus);
  89. }
  90. mTrackedTasks.add(taskStatus);
  91. boolean havePendingUris = false;
  92. // If there is a previous job associated with the new job, propagate over
  93. // any pending content URI trigger reports.
  94. if (taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
  95. havePendingUris = true;
  96. }
  97. // If we have previously reported changed authorities/uris, then we failed
  98. // to complete the job with them so will re-record them to report again.
  99. if (taskStatus.changedAuthorities != null) {
  100. havePendingUris = true;
  101. if (taskStatus.contentObserverJobInstance.mChangedAuthorities == null) {
  102. taskStatus.contentObserverJobInstance.mChangedAuthorities
  103. = new ArraySet<>();
  104. }
  105. for (String auth : taskStatus.changedAuthorities) {
  106. taskStatus.contentObserverJobInstance.mChangedAuthorities.add(auth);
  107. }
  108. if (taskStatus.changedUris != null) {
  109. if (taskStatus.contentObserverJobInstance.mChangedUris == null) {
  110. taskStatus.contentObserverJobInstance.mChangedUris = new ArraySet<>();
  111. }
  112. for (Uri uri : taskStatus.changedUris) {
  113. taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
  114. }
  115. }
  116. taskStatus.changedAuthorities = null;
  117. taskStatus.changedUris = null;
  118. }
  119. taskStatus.changedAuthorities = null;
  120. taskStatus.changedUris = null;
  121. taskStatus.setContentTriggerConstraintSatisfied(havePendingUris);
  122. }
  123. if (lastJob != null && lastJob.contentObserverJobInstance != null) {
  124. // And now we can detach the instance state from the last job.
  125. lastJob.contentObserverJobInstance.detachLocked();
  126. lastJob.contentObserverJobInstance = null;
  127. }
  128. }
  129. @Override
  130. public void prepareForExecutionLocked(JobStatus taskStatus) {
  131. if (taskStatus.hasContentTriggerConstraint()) {
  132. if (taskStatus.contentObserverJobInstance != null) {
  133. taskStatus.changedUris = taskStatus.contentObserverJobInstance.mChangedUris;
  134. taskStatus.changedAuthorities
  135. = taskStatus.contentObserverJobInstance.mChangedAuthorities;
  136. taskStatus.contentObserverJobInstance.mChangedUris = null;
  137. taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
  138. }
  139. }
  140. }
  141. @Override
  142. public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
  143. boolean forUpdate) {
  144. if (taskStatus.hasContentTriggerConstraint()) {
  145. if (taskStatus.contentObserverJobInstance != null) {
  146. taskStatus.contentObserverJobInstance.unscheduleLocked();
  147. if (incomingJob != null) {
  148. if (taskStatus.contentObserverJobInstance != null
  149. && taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
  150. // We are stopping this job, but it is going to be replaced by this given
  151. // incoming job. We want to propagate our state over to it, so we don't
  152. // lose any content changes that had happend since the last one started.
  153. // If there is a previous job associated with the new job, propagate over
  154. // any pending content URI trigger reports.
  155. if (incomingJob.contentObserverJobInstance == null) {
  156. incomingJob.contentObserverJobInstance = new JobInstance(incomingJob);
  157. }
  158. incomingJob.contentObserverJobInstance.mChangedAuthorities
  159. = taskStatus.contentObserverJobInstance.mChangedAuthorities;
  160. incomingJob.contentObserverJobInstance.mChangedUris
  161. = taskStatus.contentObserverJobInstance.mChangedUris;
  162. taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
  163. taskStatus.contentObserverJobInstance.mChangedUris = null;
  164. }
  165. // We won't detach the content observers here, because we want to
  166. // allow them to continue monitoring so we don't miss anything... and
  167. // since we are giving an incomingJob here, we know this will be
  168. // immediately followed by a start tracking of that job.
  169. } else {
  170. // But here there is no incomingJob, so nothing coming up, so time to detach.
  171. taskStatus.contentObserverJobInstance.detachLocked();
  172. taskStatus.contentObserverJobInstance = null;
  173. }
  174. }
  175. if (DEBUG) {
  176. Slog.i(TAG, "No longer tracking job " + taskStatus);
  177. }
  178. mTrackedTasks.remove(taskStatus);
  179. }
  180. }
  181. @Override
  182. public void rescheduleForFailure(JobStatus newJob, JobStatus failureToReschedule) {
  183. if (failureToReschedule.hasContentTriggerConstraint()
  184. && newJob.hasContentTriggerConstraint()) {
  185. synchronized (mLock) {
  186. // Our job has failed, and we are scheduling a new job for it.
  187. // Copy the last reported content changes in to the new job, so when
  188. // we schedule the new one we will pick them up and report them again.
  189. newJob.changedAuthorities = failureToReschedule.changedAuthorities;
  190. newJob.changedUris = failureToReschedule.changedUris;
  191. }
  192. }
  193. }
  194. final class ObserverInstance extends ContentObserver {
  195. final JobInfo.TriggerContentUri mUri;
  196. final @UserIdInt int mUserId;
  197. final ArraySet<JobInstance> mJobs = new ArraySet<>();
  198. public ObserverInstance(Handler handler, JobInfo.TriggerContentUri uri,
  199. @UserIdInt int userId) {
  200. super(handler);
  201. mUri = uri;
  202. mUserId = userId;
  203. }
  204. @Override
  205. public void onChange(boolean selfChange, Uri uri) {
  206. if (DEBUG) {
  207. Slog.i(TAG, "onChange(self=" + selfChange + ") for " + uri
  208. + " when mUri=" + mUri + " mUserId=" + mUserId);
  209. }
  210. synchronized (mLock) {
  211. final int N = mJobs.size();
  212. for (int i=0; i<N; i++) {
  213. JobInstance inst = mJobs.valueAt(i);
  214. if (inst.mChangedUris == null) {
  215. inst.mChangedUris = new ArraySet<>();
  216. }
  217. if (inst.mChangedUris.size() < MAX_URIS_REPORTED) {
  218. inst.mChangedUris.add(uri);
  219. }
  220. if (inst.mChangedAuthorities == null) {
  221. inst.mChangedAuthorities = new ArraySet<>();
  222. }
  223. inst.mChangedAuthorities.add(uri.getAuthority());
  224. inst.scheduleLocked();
  225. }
  226. }
  227. }
  228. }
  229. static final class TriggerRunnable implements Runnable {
  230. final JobInstance mInstance;
  231. TriggerRunnable(JobInstance instance) {
  232. mInstance = instance;
  233. }
  234. @Override public void run() {
  235. mInstance.trigger();
  236. }
  237. }
  238. final class JobInstance {
  239. final ArrayList<ObserverInstance> mMyObservers = new ArrayList<>();
  240. final JobStatus mJobStatus;
  241. final Runnable mExecuteRunner;
  242. final Runnable mTimeoutRunner;
  243. ArraySet<Uri> mChangedUris;
  244. ArraySet<String> mChangedAuthorities;
  245. boolean mTriggerPending;
  246. // This constructor must be called with the master job scheduler lock held.
  247. JobInstance(JobStatus jobStatus) {
  248. mJobStatus = jobStatus;
  249. mExecuteRunner = new TriggerRunnable(this);
  250. mTimeoutRunner = new TriggerRunnable(this);
  251. final JobInfo.TriggerContentUri[] uris = jobStatus.getJob().getTriggerContentUris();
  252. final int sourceUserId = jobStatus.getSourceUserId();
  253. ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
  254. mObservers.get(sourceUserId);
  255. if (observersOfUser == null) {
  256. observersOfUser = new ArrayMap<>();
  257. mObservers.put(sourceUserId, observersOfUser);
  258. }
  259. if (uris != null) {
  260. for (JobInfo.TriggerContentUri uri : uris) {
  261. ObserverInstance obs = observersOfUser.get(uri);
  262. if (obs == null) {
  263. obs = new ObserverInstance(mHandler, uri, jobStatus.getSourceUserId());
  264. observersOfUser.put(uri, obs);
  265. final boolean andDescendants = (uri.getFlags() &
  266. JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS) != 0;
  267. if (DEBUG) {
  268. Slog.v(TAG, "New observer " + obs + " for " + uri.getUri()
  269. + " andDescendants=" + andDescendants
  270. + " sourceUserId=" + sourceUserId);
  271. }
  272. mContext.getContentResolver().registerContentObserver(
  273. uri.getUri(),
  274. andDescendants,
  275. obs,
  276. sourceUserId
  277. );
  278. } else {
  279. if (DEBUG) {
  280. final boolean andDescendants = (uri.getFlags() &
  281. JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS) != 0;
  282. Slog.v(TAG, "Reusing existing observer " + obs + " for " + uri.getUri()
  283. + " andDescendants=" + andDescendants);
  284. }
  285. }
  286. obs.mJobs.add(this);
  287. mMyObservers.add(obs);
  288. }
  289. }
  290. }
  291. void trigger() {
  292. boolean reportChange = false;
  293. synchronized (mLock) {
  294. if (mTriggerPending) {
  295. if (mJobStatus.setContentTriggerConstraintSatisfied(true)) {
  296. reportChange = true;
  297. }
  298. unscheduleLocked();
  299. }
  300. }
  301. // Let the scheduler know that state has changed. This may or may not result in an
  302. // execution.
  303. if (reportChange) {
  304. mStateChangedListener.onControllerStateChanged();
  305. }
  306. }
  307. void scheduleLocked() {
  308. if (!mTriggerPending) {
  309. mTriggerPending = true;
  310. mHandler.postDelayed(mTimeoutRunner, mJobStatus.getTriggerContentMaxDelay());
  311. }
  312. mHandler.removeCallbacks(mExecuteRunner);
  313. if (mChangedUris.size() >= URIS_URGENT_THRESHOLD) {
  314. // If we start getting near the limit, GO NOW!
  315. mHandler.post(mExecuteRunner);
  316. } else {
  317. mHandler.postDelayed(mExecuteRunner, mJobStatus.getTriggerContentUpdateDelay());
  318. }
  319. }
  320. void unscheduleLocked() {
  321. if (mTriggerPending) {
  322. mHandler.removeCallbacks(mExecuteRunner);
  323. mHandler.removeCallbacks(mTimeoutRunner);
  324. mTriggerPending = false;
  325. }
  326. }
  327. void detachLocked() {
  328. final int N = mMyObservers.size();
  329. for (int i=0; i<N; i++) {
  330. final ObserverInstance obs = mMyObservers.get(i);
  331. obs.mJobs.remove(this);
  332. if (obs.mJobs.size() == 0) {
  333. if (DEBUG) {
  334. Slog.i(TAG, "Unregistering observer " + obs + " for " + obs.mUri.getUri());
  335. }
  336. mContext.getContentResolver().unregisterContentObserver(obs);
  337. ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observerOfUser =
  338. mObservers.get(obs.mUserId);
  339. if (observerOfUser != null) {
  340. observerOfUser.remove(obs.mUri);
  341. }
  342. }
  343. }
  344. }
  345. }
  346. @Override
  347. public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
  348. pw.println("Content:");
  349. Iterator<JobStatus> it = mTrackedTasks.iterator();
  350. while (it.hasNext()) {
  351. JobStatus js = it.next();
  352. if (!js.shouldDump(filterUid)) {
  353. continue;
  354. }
  355. pw.print(" #");
  356. js.printUniqueId(pw);
  357. pw.print(" from ");
  358. UserHandle.formatUid(pw, js.getSourceUid());
  359. pw.println();
  360. }
  361. int N = mObservers.size();
  362. if (N > 0) {
  363. pw.println(" Observers:");
  364. for (int userIdx = 0; userIdx < N; userIdx++) {
  365. final int userId = mObservers.keyAt(userIdx);
  366. ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
  367. mObservers.get(userId);
  368. int numbOfObserversPerUser = observersOfUser.size();
  369. for (int observerIdx = 0 ; observerIdx < numbOfObserversPerUser; observerIdx++) {
  370. ObserverInstance obs = observersOfUser.valueAt(observerIdx);
  371. int M = obs.mJobs.size();
  372. boolean shouldDump = false;
  373. for (int j = 0; j < M; j++) {
  374. JobInstance inst = obs.mJobs.valueAt(j);
  375. if (inst.mJobStatus.shouldDump(filterUid)) {
  376. shouldDump = true;
  377. break;
  378. }
  379. }
  380. if (!shouldDump) {
  381. continue;
  382. }
  383. pw.print(" ");
  384. JobInfo.TriggerContentUri trigger = observersOfUser.keyAt(observerIdx);
  385. pw.print(trigger.getUri());
  386. pw.print(" 0x");
  387. pw.print(Integer.toHexString(trigger.getFlags()));
  388. pw.print(" (");
  389. pw.print(System.identityHashCode(obs));
  390. pw.println("):");
  391. pw.println(" Jobs:");
  392. for (int j = 0; j < M; j++) {
  393. JobInstance inst = obs.mJobs.valueAt(j);
  394. pw.print(" #");
  395. inst.mJobStatus.printUniqueId(pw);
  396. pw.print(" from ");
  397. UserHandle.formatUid(pw, inst.mJobStatus.getSourceUid());
  398. if (inst.mChangedAuthorities != null) {
  399. pw.println(":");
  400. if (inst.mTriggerPending) {
  401. pw.print(" Trigger pending: update=");
  402. TimeUtils.formatDuration(
  403. inst.mJobStatus.getTriggerContentUpdateDelay(), pw);
  404. pw.print(", max=");
  405. TimeUtils.formatDuration(
  406. inst.mJobStatus.getTriggerContentMaxDelay(), pw);
  407. pw.println();
  408. }
  409. pw.println(" Changed Authorities:");
  410. for (int k = 0; k < inst.mChangedAuthorities.size(); k++) {
  411. pw.print(" ");
  412. pw.println(inst.mChangedAuthorities.valueAt(k));
  413. }
  414. if (inst.mChangedUris != null) {
  415. pw.println(" Changed URIs:");
  416. for (int k = 0; k < inst.mChangedUris.size(); k++) {
  417. pw.print(" ");
  418. pw.println(inst.mChangedUris.valueAt(k));
  419. }
  420. }
  421. } else {
  422. pw.println();
  423. }
  424. }
  425. }
  426. }
  427. }
  428. }
  429. }