blob: bb043b0109d5ffb32adc76b190653c26a507cfa6 [file] [log] [blame]
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -07001package android.os;
2
Narayan Kamathf0202a92017-12-07 15:45:33 +00003import android.annotation.Nullable;
Netta P958d0a52017-02-07 11:20:55 -08004import android.os.WorkSourceProto;
Dianne Hackborn002a54e2013-01-10 17:34:55 -08005import android.util.Log;
Netta P958d0a52017-02-07 11:20:55 -08006import android.util.proto.ProtoOutputStream;
Jeff Brown96307042012-07-27 15:51:34 -07007
Narayan Kamathf0202a92017-12-07 15:45:33 +00008import java.util.ArrayList;
Jeff Brown96307042012-07-27 15:51:34 -07009import java.util.Arrays;
Narayan Kamathf0202a92017-12-07 15:45:33 +000010import java.util.Objects;
Jeff Brown96307042012-07-27 15:51:34 -070011
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070012/**
13 * Describes the source of some work that may be done by someone else.
14 * Currently the public representation of what a work source is is not
15 * defined; this is an opaque container.
16 */
17public class WorkSource implements Parcelable {
Dianne Hackborn002a54e2013-01-10 17:34:55 -080018 static final String TAG = "WorkSource";
Dianne Hackborn5e45ee62013-01-24 19:13:44 -080019 static final boolean DEBUG = false;
Dianne Hackborn002a54e2013-01-10 17:34:55 -080020
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070021 int mNum;
22 int[] mUids;
Dianne Hackborn002a54e2013-01-10 17:34:55 -080023 String[] mNames;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070024
Narayan Kamathf0202a92017-12-07 15:45:33 +000025 private ArrayList<WorkChain> mChains;
26
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070027 /**
28 * Internal statics to avoid object allocations in some operations.
29 * The WorkSource object itself is not thread safe, but we need to
30 * hold sTmpWorkSource lock while working with these statics.
31 */
32 static final WorkSource sTmpWorkSource = new WorkSource(0);
33 /**
34 * For returning newbie work from a modification operation.
35 */
36 static WorkSource sNewbWork;
37 /**
38 * For returning gone work form a modification operation.
39 */
40 static WorkSource sGoneWork;
41
42 /**
43 * Create an empty work source.
44 */
45 public WorkSource() {
46 mNum = 0;
Narayan Kamathf0202a92017-12-07 15:45:33 +000047 mChains = null;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070048 }
49
50 /**
51 * Create a new WorkSource that is a copy of an existing one.
52 * If <var>orig</var> is null, an empty WorkSource is created.
53 */
54 public WorkSource(WorkSource orig) {
55 if (orig == null) {
56 mNum = 0;
Narayan Kamathf0202a92017-12-07 15:45:33 +000057 mChains = null;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070058 return;
59 }
60 mNum = orig.mNum;
61 if (orig.mUids != null) {
62 mUids = orig.mUids.clone();
Dianne Hackborn002a54e2013-01-10 17:34:55 -080063 mNames = orig.mNames != null ? orig.mNames.clone() : null;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070064 } else {
65 mUids = null;
Dianne Hackborn002a54e2013-01-10 17:34:55 -080066 mNames = null;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070067 }
Narayan Kamathf0202a92017-12-07 15:45:33 +000068
69 if (orig.mChains != null) {
70 // Make a copy of all WorkChains that exist on |orig| since they are mutable.
71 mChains = new ArrayList<>(orig.mChains.size());
72 for (WorkChain chain : orig.mChains) {
73 mChains.add(new WorkChain(chain));
74 }
75 } else {
76 mChains = null;
77 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070078 }
79
80 /** @hide */
81 public WorkSource(int uid) {
82 mNum = 1;
83 mUids = new int[] { uid, 0 };
Dianne Hackborn002a54e2013-01-10 17:34:55 -080084 mNames = null;
Narayan Kamathf0202a92017-12-07 15:45:33 +000085 mChains = null;
Dianne Hackborn002a54e2013-01-10 17:34:55 -080086 }
87
88 /** @hide */
89 public WorkSource(int uid, String name) {
90 if (name == null) {
91 throw new NullPointerException("Name can't be null");
92 }
93 mNum = 1;
94 mUids = new int[] { uid, 0 };
95 mNames = new String[] { name, null };
Narayan Kamathf0202a92017-12-07 15:45:33 +000096 mChains = null;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070097 }
98
99 WorkSource(Parcel in) {
100 mNum = in.readInt();
101 mUids = in.createIntArray();
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800102 mNames = in.createStringArray();
Narayan Kamathf0202a92017-12-07 15:45:33 +0000103
104 int numChains = in.readInt();
105 if (numChains > 0) {
106 mChains = new ArrayList<>(numChains);
107 in.readParcelableList(mChains, WorkChain.class.getClassLoader());
108 } else {
109 mChains = null;
110 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700111 }
112
113 /** @hide */
114 public int size() {
115 return mNum;
116 }
117
118 /** @hide */
119 public int get(int index) {
120 return mUids[index];
121 }
122
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800123 /** @hide */
124 public String getName(int index) {
125 return mNames != null ? mNames[index] : null;
126 }
127
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700128 /**
Narayan Kamathf0202a92017-12-07 15:45:33 +0000129 * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
130 * intact.
David Christie6ab22842013-09-13 17:11:53 -0700131 *
132 * <p>Useful when combining with another WorkSource that doesn't have names.
133 * @hide
134 */
135 public void clearNames() {
David Christiea31510e2013-09-20 10:44:01 -0700136 if (mNames != null) {
137 mNames = null;
138 // Clear out any duplicate uids now that we don't have names to disambiguate them.
139 int destIndex = 1;
140 int newNum = mNum;
141 for (int sourceIndex = 1; sourceIndex < mNum; sourceIndex++) {
142 if (mUids[sourceIndex] == mUids[sourceIndex - 1]) {
143 newNum--;
144 } else {
145 mUids[destIndex] = mUids[sourceIndex];
146 destIndex++;
147 }
148 }
149 mNum = newNum;
150 }
David Christie6ab22842013-09-13 17:11:53 -0700151 }
152
153 /**
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700154 * Clear this WorkSource to be empty.
155 */
156 public void clear() {
157 mNum = 0;
Narayan Kamathf0202a92017-12-07 15:45:33 +0000158 if (mChains != null) {
159 mChains.clear();
160 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700161 }
162
Jeff Brown94838912012-07-27 12:04:37 -0700163 @Override
164 public boolean equals(Object o) {
Narayan Kamathf0202a92017-12-07 15:45:33 +0000165 return o instanceof WorkSource
166 && !diff((WorkSource) o)
167 && Objects.equals(mChains, ((WorkSource) o).mChains);
Jeff Brown94838912012-07-27 12:04:37 -0700168 }
169
170 @Override
171 public int hashCode() {
172 int result = 0;
173 for (int i = 0; i < mNum; i++) {
174 result = ((result << 4) | (result >>> 28)) ^ mUids[i];
175 }
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800176 if (mNames != null) {
177 for (int i = 0; i < mNum; i++) {
178 result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
179 }
180 }
Narayan Kamathf0202a92017-12-07 15:45:33 +0000181
182 if (mChains != null) {
183 result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
184 }
185
Jeff Brown94838912012-07-27 12:04:37 -0700186 return result;
187 }
188
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700189 /**
190 * Compare this WorkSource with another.
191 * @param other The WorkSource to compare against.
192 * @return If there is a difference, true is returned.
193 */
Narayan Kamathf0202a92017-12-07 15:45:33 +0000194 // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
195 // we keep its semantics the same and ignore any differences in WorkChains (if any).
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700196 public boolean diff(WorkSource other) {
197 int N = mNum;
198 if (N != other.mNum) {
199 return true;
200 }
201 final int[] uids1 = mUids;
202 final int[] uids2 = other.mUids;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800203 final String[] names1 = mNames;
204 final String[] names2 = other.mNames;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700205 for (int i=0; i<N; i++) {
206 if (uids1[i] != uids2[i]) {
207 return true;
208 }
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800209 if (names1 != null && names2 != null && !names1[i].equals(names2[i])) {
210 return true;
211 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700212 }
213 return false;
214 }
215
216 /**
217 * Replace the current contents of this work source with the given
Narayan Kamathf0202a92017-12-07 15:45:33 +0000218 * work source. If {@code other} is null, the current work source
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700219 * will be made empty.
220 */
221 public void set(WorkSource other) {
222 if (other == null) {
223 mNum = 0;
Narayan Kamathf0202a92017-12-07 15:45:33 +0000224 if (mChains != null) {
225 mChains.clear();
226 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700227 return;
228 }
229 mNum = other.mNum;
230 if (other.mUids != null) {
231 if (mUids != null && mUids.length >= mNum) {
232 System.arraycopy(other.mUids, 0, mUids, 0, mNum);
233 } else {
234 mUids = other.mUids.clone();
235 }
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800236 if (other.mNames != null) {
237 if (mNames != null && mNames.length >= mNum) {
238 System.arraycopy(other.mNames, 0, mNames, 0, mNum);
239 } else {
240 mNames = other.mNames.clone();
241 }
242 } else {
243 mNames = null;
244 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700245 } else {
246 mUids = null;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800247 mNames = null;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700248 }
Narayan Kamathf0202a92017-12-07 15:45:33 +0000249
250 if (other.mChains != null) {
251 if (mChains != null) {
252 mChains.clear();
253 } else {
254 mChains = new ArrayList<>(other.mChains.size());
255 }
256
257 for (WorkChain chain : other.mChains) {
258 mChains.add(new WorkChain(chain));
259 }
260 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700261 }
262
263 /** @hide */
264 public void set(int uid) {
265 mNum = 1;
266 if (mUids == null) mUids = new int[2];
267 mUids[0] = uid;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800268 mNames = null;
Narayan Kamath6192f732017-12-21 09:43:38 +0000269 if (mChains != null) {
270 mChains.clear();
271 }
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800272 }
273
274 /** @hide */
275 public void set(int uid, String name) {
276 if (name == null) {
277 throw new NullPointerException("Name can't be null");
278 }
279 mNum = 1;
280 if (mUids == null) {
281 mUids = new int[2];
282 mNames = new String[2];
283 }
284 mUids[0] = uid;
285 mNames[0] = name;
Narayan Kamath6192f732017-12-21 09:43:38 +0000286 if (mChains != null) {
287 mChains.clear();
288 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700289 }
290
Narayan Kamathf0202a92017-12-07 15:45:33 +0000291 /**
292 * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
293 * differences in chains are returned. This will be removed once its callers have been
294 * rewritten.
295 *
296 * NOTE: This is currently only used in GnssLocationProvider.
297 *
298 * @hide
299 * @deprecated for internal use only. WorkSources are opaque and no external callers should need
300 * to be aware of internal differences.
301 */
302 @Deprecated
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700303 public WorkSource[] setReturningDiffs(WorkSource other) {
304 synchronized (sTmpWorkSource) {
305 sNewbWork = null;
306 sGoneWork = null;
307 updateLocked(other, true, true);
308 if (sNewbWork != null || sGoneWork != null) {
309 WorkSource[] diffs = new WorkSource[2];
310 diffs[0] = sNewbWork;
311 diffs[1] = sGoneWork;
312 return diffs;
313 }
314 return null;
315 }
316 }
317
318 /**
319 * Merge the contents of <var>other</var> WorkSource in to this one.
320 *
321 * @param other The other WorkSource whose contents are to be merged.
322 * @return Returns true if any new sources were added.
323 */
324 public boolean add(WorkSource other) {
325 synchronized (sTmpWorkSource) {
Narayan Kamathf0202a92017-12-07 15:45:33 +0000326 boolean uidAdded = updateLocked(other, false, false);
327
328 boolean chainAdded = false;
329 if (other.mChains != null) {
330 // NOTE: This is quite an expensive operation, especially if the number of chains
331 // is large. We could look into optimizing it if it proves problematic.
332 if (mChains == null) {
333 mChains = new ArrayList<>(other.mChains.size());
334 }
335
336 for (WorkChain wc : other.mChains) {
337 if (!mChains.contains(wc)) {
338 mChains.add(new WorkChain(wc));
339 }
340 }
341 }
342
343 return uidAdded || chainAdded;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700344 }
345 }
346
Narayan Kamathf0202a92017-12-07 15:45:33 +0000347 /**
348 * Legacy API: DO NOT USE. Only in use from unit tests.
349 *
350 * @hide
351 * @deprecated meant for unit testing use only. Will be removed in a future API revision.
352 */
353 @Deprecated
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700354 public WorkSource addReturningNewbs(WorkSource other) {
355 synchronized (sTmpWorkSource) {
356 sNewbWork = null;
357 updateLocked(other, false, true);
358 return sNewbWork;
359 }
360 }
361
362 /** @hide */
363 public boolean add(int uid) {
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800364 if (mNum <= 0) {
365 mNames = null;
366 insert(0, uid);
367 return true;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700368 }
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800369 if (mNames != null) {
370 throw new IllegalArgumentException("Adding without name to named " + this);
371 }
372 int i = Arrays.binarySearch(mUids, 0, mNum, uid);
373 if (DEBUG) Log.d(TAG, "Adding uid " + uid + " to " + this + ": binsearch res = " + i);
374 if (i >= 0) {
375 return false;
376 }
377 insert(-i-1, uid);
378 return true;
379 }
380
381 /** @hide */
382 public boolean add(int uid, String name) {
383 if (mNum <= 0) {
384 insert(0, uid, name);
385 return true;
386 }
387 if (mNames == null) {
388 throw new IllegalArgumentException("Adding name to unnamed " + this);
389 }
390 int i;
391 for (i=0; i<mNum; i++) {
392 if (mUids[i] > uid) {
393 break;
394 }
395 if (mUids[i] == uid) {
Netta P958d0a52017-02-07 11:20:55 -0800396 int diff = mNames[i].compareTo(name);
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800397 if (diff > 0) {
398 break;
399 }
400 if (diff == 0) {
401 return false;
402 }
403 }
404 }
405 insert(i, uid, name);
406 return true;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700407 }
408
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700409 public boolean remove(WorkSource other) {
Narayan Kamathee07f622018-01-08 12:20:28 +0000410 if (isEmpty() || other.isEmpty()) {
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800411 return false;
412 }
Narayan Kamathf0202a92017-12-07 15:45:33 +0000413
Narayan Kamathee07f622018-01-08 12:20:28 +0000414 boolean uidRemoved;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800415 if (mNames == null && other.mNames == null) {
Narayan Kamathf0202a92017-12-07 15:45:33 +0000416 uidRemoved = removeUids(other);
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800417 } else {
418 if (mNames == null) {
419 throw new IllegalArgumentException("Other " + other + " has names, but target "
420 + this + " does not");
421 }
422 if (other.mNames == null) {
423 throw new IllegalArgumentException("Target " + this + " has names, but other "
424 + other + " does not");
425 }
Narayan Kamathf0202a92017-12-07 15:45:33 +0000426 uidRemoved = removeUidsAndNames(other);
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800427 }
Narayan Kamathf0202a92017-12-07 15:45:33 +0000428
429 boolean chainRemoved = false;
Narayan Kamathee07f622018-01-08 12:20:28 +0000430 if (other.mChains != null && mChains != null) {
431 chainRemoved = mChains.removeAll(other.mChains);
Narayan Kamathf0202a92017-12-07 15:45:33 +0000432 }
433
434 return uidRemoved || chainRemoved;
435 }
436
437 /**
438 * Create a new {@code WorkChain} associated with this WorkSource and return it.
439 *
440 * @hide
441 */
442 public WorkChain createWorkChain() {
443 if (mChains == null) {
444 mChains = new ArrayList<>(4);
445 }
446
447 final WorkChain wc = new WorkChain();
448 mChains.add(wc);
449
450 return wc;
451 }
452
453 /**
Narayan Kamath81822022017-12-08 11:56:01 +0000454 * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to
455 * attribute usage to.
456 *
457 * @hide for internal use only.
458 */
459 public boolean isEmpty() {
460 return mNum == 0 && (mChains == null || mChains.isEmpty());
461 }
462
463 /**
Narayan Kamathf0202a92017-12-07 15:45:33 +0000464 * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
465 * @hide
466 */
467 public ArrayList<WorkChain> getWorkChains() {
468 return mChains;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800469 }
470
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800471 private boolean removeUids(WorkSource other) {
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700472 int N1 = mNum;
473 final int[] uids1 = mUids;
474 final int N2 = other.mNum;
475 final int[] uids2 = other.mUids;
476 boolean changed = false;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800477 int i1 = 0, i2 = 0;
478 if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
479 while (i1 < N1 && i2 < N2) {
480 if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
481 + " of " + N2);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700482 if (uids2[i2] == uids1[i1]) {
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800483 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
484 + ": remove " + uids1[i1]);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700485 N1--;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800486 changed = true;
Dianne Hackborn83770282010-09-14 11:45:44 -0700487 if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800488 i2++;
489 } else if (uids2[i2] > uids1[i1]) {
490 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700491 i1++;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800492 } else {
493 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
494 i2++;
495 }
496 }
497
498 mNum = N1;
499
500 return changed;
501 }
502
503 private boolean removeUidsAndNames(WorkSource other) {
504 int N1 = mNum;
505 final int[] uids1 = mUids;
506 final String[] names1 = mNames;
507 final int N2 = other.mNum;
508 final int[] uids2 = other.mUids;
509 final String[] names2 = other.mNames;
510 boolean changed = false;
511 int i1 = 0, i2 = 0;
512 if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
513 while (i1 < N1 && i2 < N2) {
514 if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
515 + " of " + N2 + ": " + uids1[i1] + " " + names1[i1]);
516 if (uids2[i2] == uids1[i1] && names2[i2].equals(names1[i1])) {
517 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
518 + ": remove " + uids1[i1] + " " + names1[i1]);
519 N1--;
520 changed = true;
521 if (i1 < N1) {
522 System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
523 System.arraycopy(names1, i1+1, names1, i1, N1-i1);
524 }
525 i2++;
526 } else if (uids2[i2] > uids1[i1]
527 || (uids2[i2] == uids1[i1] && names2[i2].compareTo(names1[i1]) > 0)) {
528 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
529 i1++;
530 } else {
531 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
532 i2++;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700533 }
534 }
535
536 mNum = N1;
537
538 return changed;
539 }
540
541 private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800542 if (mNames == null && other.mNames == null) {
543 return updateUidsLocked(other, set, returnNewbs);
544 } else {
545 if (mNum > 0 && mNames == null) {
546 throw new IllegalArgumentException("Other " + other + " has names, but target "
547 + this + " does not");
548 }
549 if (other.mNum > 0 && other.mNames == null) {
550 throw new IllegalArgumentException("Target " + this + " has names, but other "
551 + other + " does not");
552 }
553 return updateUidsAndNamesLocked(other, set, returnNewbs);
554 }
555 }
556
557 private static WorkSource addWork(WorkSource cur, int newUid) {
558 if (cur == null) {
559 return new WorkSource(newUid);
560 }
561 cur.insert(cur.mNum, newUid);
562 return cur;
563 }
564
565 private boolean updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs) {
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700566 int N1 = mNum;
567 int[] uids1 = mUids;
568 final int N2 = other.mNum;
569 final int[] uids2 = other.mUids;
570 boolean changed = false;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800571 int i1 = 0, i2 = 0;
572 if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
573 + " returnNewbs=" + returnNewbs);
574 while (i1 < N1 || i2 < N2) {
575 if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
576 + " of " + N2);
577 if (i1 >= N1 || (i2 < N2 && uids2[i2] < uids1[i1])) {
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700578 // Need to insert a new uid.
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800579 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
580 + ": insert " + uids2[i2]);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700581 changed = true;
582 if (uids1 == null) {
583 uids1 = new int[4];
584 uids1[0] = uids2[i2];
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800585 } else if (N1 >= uids1.length) {
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700586 int[] newuids = new int[(uids1.length*3)/2];
587 if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
588 if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
589 uids1 = newuids;
590 uids1[i1] = uids2[i2];
591 } else {
592 if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1);
593 uids1[i1] = uids2[i2];
594 }
595 if (returnNewbs) {
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800596 sNewbWork = addWork(sNewbWork, uids2[i2]);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700597 }
598 N1++;
599 i1++;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800600 i2++;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700601 } else {
602 if (!set) {
603 // Skip uids that already exist or are not in 'other'.
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800604 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
605 if (i2 < N2 && uids2[i2] == uids1[i1]) {
606 i2++;
607 }
608 i1++;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700609 } else {
610 // Remove any uids that don't exist in 'other'.
611 int start = i1;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800612 while (i1 < N1 && (i2 >= N2 || uids2[i2] > uids1[i1])) {
613 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + uids1[i1]);
614 sGoneWork = addWork(sGoneWork, uids1[i1]);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700615 i1++;
616 }
617 if (start < i1) {
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800618 System.arraycopy(uids1, i1, uids1, start, N1-i1);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700619 N1 -= i1-start;
620 i1 = start;
621 }
622 // If there is a matching uid, skip it.
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800623 if (i1 < N1 && i2 < N2 && uids2[i2] == uids1[i1]) {
624 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700625 i1++;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800626 i2++;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700627 }
628 }
629 }
630 }
631
632 mNum = N1;
633 mUids = uids1;
634
635 return changed;
636 }
637
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800638 /**
639 * Returns 0 if equal, negative if 'this' is before 'other', positive if 'this' is after 'other'.
640 */
641 private int compare(WorkSource other, int i1, int i2) {
642 final int diff = mUids[i1] - other.mUids[i2];
643 if (diff != 0) {
644 return diff;
645 }
646 return mNames[i1].compareTo(other.mNames[i2]);
647 }
648
649 private static WorkSource addWork(WorkSource cur, int newUid, String newName) {
650 if (cur == null) {
651 return new WorkSource(newUid, newName);
652 }
653 cur.insert(cur.mNum, newUid, newName);
654 return cur;
655 }
656
657 private boolean updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs) {
658 final int N2 = other.mNum;
659 final int[] uids2 = other.mUids;
660 String[] names2 = other.mNames;
661 boolean changed = false;
662 int i1 = 0, i2 = 0;
663 if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
664 + " returnNewbs=" + returnNewbs);
665 while (i1 < mNum || i2 < N2) {
666 if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + mNum + ", other @ " + i2
667 + " of " + N2);
668 int diff = -1;
669 if (i1 >= mNum || (i2 < N2 && (diff=compare(other, i1, i2)) > 0)) {
670 // Need to insert a new uid.
671 changed = true;
672 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum
673 + ": insert " + uids2[i2] + " " + names2[i2]);
674 insert(i1, uids2[i2], names2[i2]);
675 if (returnNewbs) {
676 sNewbWork = addWork(sNewbWork, uids2[i2], names2[i2]);
677 }
678 i1++;
679 i2++;
680 } else {
681 if (!set) {
682 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
683 if (i2 < N2 && diff == 0) {
684 i2++;
685 }
686 i1++;
687 } else {
688 // Remove any uids that don't exist in 'other'.
689 int start = i1;
690 while (diff < 0) {
691 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + mUids[i1]
692 + " " + mNames[i1]);
693 sGoneWork = addWork(sGoneWork, mUids[i1], mNames[i1]);
694 i1++;
695 if (i1 >= mNum) {
696 break;
697 }
698 diff = i2 < N2 ? compare(other, i1, i2) : -1;
699 }
700 if (start < i1) {
701 System.arraycopy(mUids, i1, mUids, start, mNum-i1);
702 System.arraycopy(mNames, i1, mNames, start, mNum-i1);
703 mNum -= i1-start;
704 i1 = start;
705 }
706 // If there is a matching uid, skip it.
707 if (i1 < mNum && diff == 0) {
708 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
709 i1++;
710 i2++;
711 }
712 }
713 }
714 }
715
716 return changed;
717 }
718
719 private void insert(int index, int uid) {
720 if (DEBUG) Log.d(TAG, "Insert in " + this + " @ " + index + " uid " + uid);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700721 if (mUids == null) {
722 mUids = new int[4];
723 mUids[0] = uid;
724 mNum = 1;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800725 } else if (mNum >= mUids.length) {
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700726 int[] newuids = new int[(mNum*3)/2];
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800727 if (index > 0) {
728 System.arraycopy(mUids, 0, newuids, 0, index);
729 }
730 if (index < mNum) {
731 System.arraycopy(mUids, index, newuids, index+1, mNum-index);
732 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700733 mUids = newuids;
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800734 mUids[index] = uid;
735 mNum++;
736 } else {
737 if (index < mNum) {
738 System.arraycopy(mUids, index, mUids, index+1, mNum-index);
739 }
740 mUids[index] = uid;
741 mNum++;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700742 }
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800743 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700744
Dianne Hackborn002a54e2013-01-10 17:34:55 -0800745 private void insert(int index, int uid, String name) {
746 if (mUids == null) {
747 mUids = new int[4];
748 mUids[0] = uid;
749 mNames = new String[4];
750 mNames[0] = name;
751 mNum = 1;
752 } else if (mNum >= mUids.length) {
753 int[] newuids = new int[(mNum*3)/2];
754 String[] newnames = new String[(mNum*3)/2];
755 if (index > 0) {
756 System.arraycopy(mUids, 0, newuids, 0, index);
757 System.arraycopy(mNames, 0, newnames, 0, index);
758 }
759 if (index < mNum) {
760 System.arraycopy(mUids, index, newuids, index+1, mNum-index);
761 System.arraycopy(mNames, index, newnames, index+1, mNum-index);
762 }
763 mUids = newuids;
764 mNames = newnames;
765 mUids[index] = uid;
766 mNames[index] = name;
767 mNum++;
768 } else {
769 if (index < mNum) {
770 System.arraycopy(mUids, index, mUids, index+1, mNum-index);
771 System.arraycopy(mNames, index, mNames, index+1, mNum-index);
772 }
773 mUids[index] = uid;
774 mNames[index] = name;
775 mNum++;
776 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700777 }
778
Narayan Kamathf0202a92017-12-07 15:45:33 +0000779 /**
780 * Represents an attribution chain for an item of work being performed. An attribution chain is
781 * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
782 * of the work, and the node at the highest index performs the work. Nodes at other indices
783 * are intermediaries that facilitate the work. Examples :
784 *
785 * (1) Work being performed by uid=2456 (no chaining):
786 * <pre>
787 * WorkChain {
788 * mUids = { 2456 }
789 * mTags = { null }
790 * mSize = 1;
791 * }
792 * </pre>
793 *
794 * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
795 *
796 * <pre>
797 * WorkChain {
798 * mUids = { 5678, 2456 }
799 * mTags = { null, "c1" }
800 * mSize = 1
801 * }
802 * </pre>
803 *
804 * Attribution chains are mutable, though the only operation that can be performed on them
805 * is the addition of a new node at the end of the attribution chain to represent
806 *
807 * @hide
808 */
809 public static class WorkChain implements Parcelable {
810 private int mSize;
811 private int[] mUids;
812 private String[] mTags;
813
814 // @VisibleForTesting
815 public WorkChain() {
816 mSize = 0;
817 mUids = new int[4];
818 mTags = new String[4];
819 }
820
821 // @VisibleForTesting
822 public WorkChain(WorkChain other) {
823 mSize = other.mSize;
824 mUids = other.mUids.clone();
825 mTags = other.mTags.clone();
826 }
827
828 private WorkChain(Parcel in) {
829 mSize = in.readInt();
830 mUids = in.createIntArray();
831 mTags = in.createStringArray();
832 }
833
834 /**
835 * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
836 * {@code WorkChain}.
837 */
838 public WorkChain addNode(int uid, @Nullable String tag) {
839 if (mSize == mUids.length) {
840 resizeArrays();
841 }
842
843 mUids[mSize] = uid;
844 mTags[mSize] = tag;
845 mSize++;
846
847 return this;
848 }
849
Narayan Kamath81822022017-12-08 11:56:01 +0000850 /**
Narayan Kamathcbe06772017-12-27 14:22:47 +0000851 * Return the UID to which this WorkChain should be attributed to, i.e, the UID that
852 * initiated the work and not the UID performing it.
Narayan Kamath81822022017-12-08 11:56:01 +0000853 */
854 public int getAttributionUid() {
Narayan Kamathcbe06772017-12-27 14:22:47 +0000855 return mUids[0];
Narayan Kamath81822022017-12-08 11:56:01 +0000856 }
857
Narayan Kamathf0202a92017-12-07 15:45:33 +0000858 // TODO: The following three trivial getters are purely for testing and will be removed
859 // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
860 // diffing it etc.
861 //
862 // @VisibleForTesting
863 public int[] getUids() {
864 return mUids;
865 }
866 // @VisibleForTesting
867 public String[] getTags() {
868 return mTags;
869 }
870 // @VisibleForTesting
871 public int getSize() {
872 return mSize;
873 }
874
875 private void resizeArrays() {
876 final int newSize = mSize * 2;
877 int[] uids = new int[newSize];
878 String[] tags = new String[newSize];
879
880 System.arraycopy(mUids, 0, uids, 0, mSize);
881 System.arraycopy(mTags, 0, tags, 0, mSize);
882
883 mUids = uids;
884 mTags = tags;
885 }
886
887 @Override
888 public String toString() {
889 StringBuilder result = new StringBuilder("WorkChain{");
890 for (int i = 0; i < mSize; ++i) {
891 if (i != 0) {
892 result.append(", ");
893 }
894 result.append("(");
895 result.append(mUids[i]);
896 if (mTags[i] != null) {
897 result.append(", ");
898 result.append(mTags[i]);
899 }
900 result.append(")");
901 }
902
903 result.append("}");
904 return result.toString();
905 }
906
907 @Override
908 public int hashCode() {
909 return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
910 }
911
912 @Override
913 public boolean equals(Object o) {
914 if (o instanceof WorkChain) {
915 WorkChain other = (WorkChain) o;
916
917 return mSize == other.mSize
918 && Arrays.equals(mUids, other.mUids)
919 && Arrays.equals(mTags, other.mTags);
920 }
921
922 return false;
923 }
924
925 @Override
926 public int describeContents() {
927 return 0;
928 }
929
930 @Override
931 public void writeToParcel(Parcel dest, int flags) {
932 dest.writeInt(mSize);
933 dest.writeIntArray(mUids);
934 dest.writeStringArray(mTags);
935 }
936
937 public static final Parcelable.Creator<WorkChain> CREATOR =
938 new Parcelable.Creator<WorkChain>() {
939 public WorkChain createFromParcel(Parcel in) {
940 return new WorkChain(in);
941 }
942 public WorkChain[] newArray(int size) {
943 return new WorkChain[size];
944 }
945 };
946 }
947
Narayan Kamath81822022017-12-08 11:56:01 +0000948 /**
949 * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}.
950 *
951 * Returns {@code null} if no differences exist, otherwise returns a two element array. The
952 * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in
953 * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in
954 * {@code oldWs} but not in {@code newWs}.
955 *
956 * @hide
957 */
958 public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) {
959 ArrayList<WorkChain> newChains = null;
960 ArrayList<WorkChain> goneChains = null;
961
962 // TODO(narayan): This is a dumb O(M*N) algorithm that determines what has changed across
963 // WorkSource objects. We can replace this with something smarter, for e.g by defining
964 // a Comparator between WorkChains. It's unclear whether that will be more efficient if
965 // the number of chains associated with a WorkSource is expected to be small
966 if (oldWs.mChains != null) {
967 for (int i = 0; i < oldWs.mChains.size(); ++i) {
968 final WorkChain wc = oldWs.mChains.get(i);
969 if (newWs.mChains == null || !newWs.mChains.contains(wc)) {
970 if (goneChains == null) {
971 goneChains = new ArrayList<>(oldWs.mChains.size());
972 }
973 goneChains.add(wc);
974 }
975 }
976 }
977
978 if (newWs.mChains != null) {
979 for (int i = 0; i < newWs.mChains.size(); ++i) {
980 final WorkChain wc = newWs.mChains.get(i);
981 if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) {
982 if (newChains == null) {
983 newChains = new ArrayList<>(newWs.mChains.size());
984 }
985 newChains.add(wc);
986 }
987 }
988 }
989
990 if (newChains != null || goneChains != null) {
991 return new ArrayList[] { newChains, goneChains };
992 }
993
994 return null;
995 }
996
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700997 @Override
998 public int describeContents() {
999 return 0;
1000 }
1001
1002 @Override
1003 public void writeToParcel(Parcel dest, int flags) {
1004 dest.writeInt(mNum);
1005 dest.writeIntArray(mUids);
Dianne Hackborn002a54e2013-01-10 17:34:55 -08001006 dest.writeStringArray(mNames);
Narayan Kamathf0202a92017-12-07 15:45:33 +00001007
1008 if (mChains == null) {
1009 dest.writeInt(-1);
1010 } else {
1011 dest.writeInt(mChains.size());
1012 dest.writeParcelableList(mChains, flags);
1013 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -07001014 }
1015
Jeff Brown96307042012-07-27 15:51:34 -07001016 @Override
1017 public String toString() {
1018 StringBuilder result = new StringBuilder();
Dianne Hackborn002a54e2013-01-10 17:34:55 -08001019 result.append("WorkSource{");
Jeff Brown96307042012-07-27 15:51:34 -07001020 for (int i = 0; i < mNum; i++) {
1021 if (i != 0) {
1022 result.append(", ");
1023 }
1024 result.append(mUids[i]);
Dianne Hackborn002a54e2013-01-10 17:34:55 -08001025 if (mNames != null) {
1026 result.append(" ");
1027 result.append(mNames[i]);
1028 }
Jeff Brown96307042012-07-27 15:51:34 -07001029 }
Narayan Kamathf0202a92017-12-07 15:45:33 +00001030
1031 if (mChains != null) {
1032 result.append(" chains=");
1033 for (int i = 0; i < mChains.size(); ++i) {
1034 if (i != 0) {
1035 result.append(", ");
1036 }
1037 result.append(mChains.get(i));
1038 }
1039 }
1040
Dianne Hackborn002a54e2013-01-10 17:34:55 -08001041 result.append("}");
Jeff Brown96307042012-07-27 15:51:34 -07001042 return result.toString();
1043 }
1044
Netta P958d0a52017-02-07 11:20:55 -08001045 /** @hide */
1046 public void writeToProto(ProtoOutputStream proto, long fieldId) {
1047 final long workSourceToken = proto.start(fieldId);
1048 for (int i = 0; i < mNum; i++) {
1049 final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
1050 proto.write(WorkSourceProto.WorkSourceContentProto.UID, mUids[i]);
1051 if (mNames != null) {
1052 proto.write(WorkSourceProto.WorkSourceContentProto.NAME, mNames[i]);
1053 }
1054 proto.end(contentProto);
1055 }
Narayan Kamath80434a72017-12-27 15:44:17 +00001056
1057 if (mChains != null) {
1058 for (int i = 0; i < mChains.size(); i++) {
1059 final WorkChain wc = mChains.get(i);
1060 final long workChain = proto.start(WorkSourceProto.WORK_CHAINS);
1061
1062 final String[] tags = wc.getTags();
1063 final int[] uids = wc.getUids();
1064 for (int j = 0; j < tags.length; j++) {
1065 final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
1066 proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]);
1067 proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]);
1068 proto.end(contentProto);
1069 }
1070
1071 proto.end(workChain);
1072 }
1073 }
1074
Netta P958d0a52017-02-07 11:20:55 -08001075 proto.end(workSourceToken);
1076 }
1077
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -07001078 public static final Parcelable.Creator<WorkSource> CREATOR
1079 = new Parcelable.Creator<WorkSource>() {
1080 public WorkSource createFromParcel(Parcel in) {
1081 return new WorkSource(in);
1082 }
1083 public WorkSource[] newArray(int size) {
1084 return new WorkSource[size];
1085 }
1086 };
1087}