ContentObserverController.java revision 33d31c5b70c7d056e799e34bb6eccbe6939714ea
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 17package com.android.server.job.controllers; 18 19import android.app.job.JobInfo; 20import android.content.Context; 21import android.database.ContentObserver; 22import android.net.Uri; 23import android.os.Handler; 24import android.util.ArrayMap; 25import android.util.ArraySet; 26 27import com.android.internal.annotations.VisibleForTesting; 28import com.android.server.job.JobSchedulerService; 29import com.android.server.job.StateChangedListener; 30 31import java.io.PrintWriter; 32import java.util.ArrayList; 33import java.util.Iterator; 34import java.util.List; 35 36/** 37 * Controller for monitoring changes to content URIs through a ContentObserver. 38 */ 39public class ContentObserverController extends StateController { 40 private static final String TAG = "JobScheduler.Content"; 41 42 /** 43 * Maximum number of changing URIs we will batch together to report. 44 * XXX Should be smarter about this, restricting it by the maximum number 45 * of characters we will retain. 46 */ 47 private static final int MAX_URIS_REPORTED = 50; 48 49 private static final Object sCreationLock = new Object(); 50 private static volatile ContentObserverController sController; 51 52 final private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); 53 ArrayMap<Uri, ObserverInstance> mObservers = new ArrayMap<>(); 54 final Handler mHandler = new Handler(); 55 56 public static ContentObserverController get(JobSchedulerService taskManagerService) { 57 synchronized (sCreationLock) { 58 if (sController == null) { 59 sController = new ContentObserverController(taskManagerService, 60 taskManagerService.getContext(), taskManagerService.getLock()); 61 } 62 } 63 return sController; 64 } 65 66 @VisibleForTesting 67 public static ContentObserverController getForTesting(StateChangedListener stateChangedListener, 68 Context context) { 69 return new ContentObserverController(stateChangedListener, context, new Object()); 70 } 71 72 private ContentObserverController(StateChangedListener stateChangedListener, Context context, 73 Object lock) { 74 super(stateChangedListener, context, lock); 75 } 76 77 @Override 78 public void maybeStartTrackingJob(JobStatus taskStatus, JobStatus lastJob) { 79 if (taskStatus.hasContentTriggerConstraint()) { 80 synchronized (mLock) { 81 if (taskStatus.contentObserverJobInstance == null) { 82 taskStatus.contentObserverJobInstance = new JobInstance(taskStatus); 83 } 84 mTrackedTasks.add(taskStatus); 85 boolean havePendingUris = false; 86 // If there is a previous job associated with the new job, propagate over 87 // any pending content URI trigger reports. 88 if (lastJob != null && lastJob.contentObserverJobInstance != null 89 && lastJob.contentObserverJobInstance 90 != taskStatus.contentObserverJobInstance 91 && lastJob.contentObserverJobInstance.mChangedAuthorities != null) { 92 havePendingUris = true; 93 taskStatus.contentObserverJobInstance.mChangedAuthorities 94 = lastJob.contentObserverJobInstance.mChangedAuthorities; 95 taskStatus.contentObserverJobInstance.mChangedUris 96 = lastJob.contentObserverJobInstance.mChangedUris; 97 lastJob.contentObserverJobInstance.mChangedAuthorities = null; 98 lastJob.contentObserverJobInstance.mChangedUris = null; 99 } 100 // If we have previously reported changed authorities/uris, then we failed 101 // to complete the job with them so will re-record them to report again. 102 if (taskStatus.changedAuthorities != null) { 103 havePendingUris = true; 104 if (taskStatus.contentObserverJobInstance.mChangedAuthorities == null) { 105 taskStatus.contentObserverJobInstance.mChangedAuthorities 106 = new ArraySet<>(); 107 } 108 for (String auth : taskStatus.changedAuthorities) { 109 taskStatus.contentObserverJobInstance.mChangedAuthorities.add(auth); 110 } 111 if (taskStatus.changedUris != null) { 112 if (taskStatus.contentObserverJobInstance.mChangedUris == null) { 113 taskStatus.contentObserverJobInstance.mChangedUris = new ArraySet<>(); 114 } 115 for (Uri uri : taskStatus.changedUris) { 116 taskStatus.contentObserverJobInstance.mChangedUris.add(uri); 117 } 118 } 119 taskStatus.changedAuthorities = null; 120 taskStatus.changedUris = null; 121 } 122 taskStatus.changedAuthorities = null; 123 taskStatus.changedUris = null; 124 taskStatus.contentTriggerConstraintSatisfied.set(havePendingUris); 125 } 126 } 127 } 128 129 @Override 130 public void prepareForExecution(JobStatus taskStatus) { 131 if (taskStatus.hasContentTriggerConstraint()) { 132 synchronized (mLock) { 133 if (taskStatus.contentObserverJobInstance != null) { 134 taskStatus.changedUris = taskStatus.contentObserverJobInstance.mChangedUris; 135 taskStatus.changedAuthorities 136 = taskStatus.contentObserverJobInstance.mChangedAuthorities; 137 taskStatus.contentObserverJobInstance.mChangedUris = null; 138 taskStatus.contentObserverJobInstance.mChangedAuthorities = null; 139 } 140 } 141 } 142 } 143 144 @Override 145 public void maybeStopTrackingJob(JobStatus taskStatus, boolean forUpdate) { 146 if (taskStatus.hasContentTriggerConstraint()) { 147 synchronized (mLock) { 148 if (!forUpdate) { 149 // We won't do this reset if being called for an update, because 150 // we know it will be immediately followed by maybeStartTrackingJob... 151 // and we don't want to lose any content changes in-between. 152 if (taskStatus.contentObserverJobInstance != null) { 153 taskStatus.contentObserverJobInstance.detach(); 154 taskStatus.contentObserverJobInstance = null; 155 } 156 } 157 mTrackedTasks.remove(taskStatus); 158 } 159 } 160 } 161 162 @Override 163 public void rescheduleForFailure(JobStatus newJob, JobStatus failureToReschedule) { 164 if (failureToReschedule.hasContentTriggerConstraint() 165 && newJob.hasContentTriggerConstraint()) { 166 synchronized (mLock) { 167 // Our job has failed, and we are scheduling a new job for it. 168 // Copy the last reported content changes in to the new job, so when 169 // we schedule the new one we will pick them up and report them again. 170 newJob.changedAuthorities = failureToReschedule.changedAuthorities; 171 newJob.changedUris = failureToReschedule.changedUris; 172 } 173 } 174 } 175 176 class ObserverInstance extends ContentObserver { 177 final Uri mUri; 178 final ArrayList<JobInstance> mJobs = new ArrayList<>(); 179 180 public ObserverInstance(Handler handler, Uri uri) { 181 super(handler); 182 mUri = uri; 183 } 184 185 @Override 186 public void onChange(boolean selfChange, Uri uri) { 187 boolean reportChange = false; 188 synchronized (mLock) { 189 final int N = mJobs.size(); 190 for (int i=0; i<N; i++) { 191 JobInstance inst = mJobs.get(i); 192 if (inst.mChangedUris == null) { 193 inst.mChangedUris = new ArraySet<>(); 194 } 195 if (inst.mChangedUris.size() < MAX_URIS_REPORTED) { 196 inst.mChangedUris.add(uri); 197 } 198 if (inst.mChangedAuthorities == null) { 199 inst.mChangedAuthorities = new ArraySet<>(); 200 } 201 inst.mChangedAuthorities.add(uri.getAuthority()); 202 boolean previous 203 = inst.mJobStatus.contentTriggerConstraintSatisfied.getAndSet(true); 204 if (!previous) { 205 reportChange = true; 206 } 207 } 208 } 209 // Let the scheduler know that state has changed. This may or may not result in an 210 // execution. 211 if (reportChange) { 212 mStateChangedListener.onControllerStateChanged(); 213 } 214 } 215 } 216 217 class JobInstance extends ArrayList<ObserverInstance> { 218 private final JobStatus mJobStatus; 219 private ArraySet<Uri> mChangedUris; 220 private ArraySet<String> mChangedAuthorities; 221 222 JobInstance(JobStatus jobStatus) { 223 mJobStatus = jobStatus; 224 final JobInfo.TriggerContentUri[] uris = jobStatus.getJob().getTriggerContentUris(); 225 if (uris != null) { 226 for (JobInfo.TriggerContentUri uri : uris) { 227 ObserverInstance obs = mObservers.get(uri.getUri()); 228 if (obs == null) { 229 obs = new ObserverInstance(mHandler, uri.getUri()); 230 mObservers.put(uri.getUri(), obs); 231 mContext.getContentResolver().registerContentObserver( 232 uri.getUri(), 233 (uri.getFlags() & 234 JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS) 235 != 0, 236 obs); 237 } 238 obs.mJobs.add(this); 239 add(obs); 240 } 241 } 242 } 243 244 void detach() { 245 final int N = size(); 246 for (int i=0; i<N; i++) { 247 final ObserverInstance obs = get(i); 248 obs.mJobs.remove(this); 249 if (obs.mJobs.size() == 0) { 250 mContext.getContentResolver().unregisterContentObserver(obs); 251 mObservers.remove(obs.mUri); 252 } 253 } 254 } 255 } 256 257 @Override 258 public void dumpControllerState(PrintWriter pw) { 259 pw.println("Content."); 260 synchronized (mLock) { 261 Iterator<JobStatus> it = mTrackedTasks.iterator(); 262 if (it.hasNext()) { 263 pw.print(String.valueOf(it.next().hashCode())); 264 } 265 while (it.hasNext()) { 266 pw.print("," + String.valueOf(it.next().hashCode())); 267 } 268 pw.println(); 269 int N = mObservers.size(); 270 if (N > 0) { 271 pw.println("URIs:"); 272 for (int i = 0; i < N; i++) { 273 ObserverInstance obs = mObservers.valueAt(i); 274 pw.print(" "); 275 pw.print(mObservers.keyAt(i)); 276 pw.println(":"); 277 pw.print(" "); 278 pw.println(obs); 279 pw.println(" Jobs:"); 280 int M = obs.mJobs.size(); 281 for (int j=0; j<M; j++) { 282 JobInstance inst = obs.mJobs.get(j); 283 pw.print(" "); 284 pw.print(inst.hashCode()); 285 if (inst.mChangedAuthorities != null) { 286 pw.println(":"); 287 pw.println(" Changed Authorities:"); 288 for (int k=0; k<inst.mChangedAuthorities.size(); k++) { 289 pw.print(" "); 290 pw.println(inst.mChangedAuthorities.valueAt(k)); 291 } 292 if (inst.mChangedUris != null) { 293 pw.println(" Changed URIs:"); 294 for (int k = 0; k<inst.mChangedUris.size(); k++) { 295 pw.print(" "); 296 pw.println(inst.mChangedUris.valueAt(k)); 297 } 298 } 299 } else { 300 pw.println(); 301 } 302 } 303 } 304 } 305 } 306 } 307} 308