1 package android.os;
2 
3 import android.annotation.Nullable;
4 import android.annotation.SystemApi;
5 import android.content.Context;
6 import android.os.WorkSourceProto;
7 import android.provider.Settings;
8 import android.provider.Settings.Global;
9 import android.util.Log;
10 import android.util.proto.ProtoOutputStream;
11 
12 import com.android.internal.annotations.VisibleForTesting;
13 
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 
17 /**
18  * Describes the source of some work that may be done by someone else.
19  * Currently the public representation of what a work source is is not
20  * defined; this is an opaque container.
21  */
22 public class WorkSource implements Parcelable {
23     static final String TAG = "WorkSource";
24     static final boolean DEBUG = false;
25 
26     int mNum;
27     int[] mUids;
28     String[] mNames;
29 
30     private ArrayList<WorkChain> mChains;
31 
32     /**
33      * Internal statics to avoid object allocations in some operations.
34      * The WorkSource object itself is not thread safe, but we need to
35      * hold sTmpWorkSource lock while working with these statics.
36      */
37     static final WorkSource sTmpWorkSource = new WorkSource(0);
38     /**
39      * For returning newbie work from a modification operation.
40      */
41     static WorkSource sNewbWork;
42     /**
43      * For returning gone work form a modification operation.
44      */
45     static WorkSource sGoneWork;
46 
47     /**
48      * Create an empty work source.
49      */
WorkSource()50     public WorkSource() {
51         mNum = 0;
52         mChains = null;
53     }
54 
55     /**
56      * Create a new WorkSource that is a copy of an existing one.
57      * If <var>orig</var> is null, an empty WorkSource is created.
58      */
WorkSource(WorkSource orig)59     public WorkSource(WorkSource orig) {
60         if (orig == null) {
61             mNum = 0;
62             mChains = null;
63             return;
64         }
65         mNum = orig.mNum;
66         if (orig.mUids != null) {
67             mUids = orig.mUids.clone();
68             mNames = orig.mNames != null ? orig.mNames.clone() : null;
69         } else {
70             mUids = null;
71             mNames = null;
72         }
73 
74         if (orig.mChains != null) {
75             // Make a copy of all WorkChains that exist on |orig| since they are mutable.
76             mChains = new ArrayList<>(orig.mChains.size());
77             for (WorkChain chain : orig.mChains) {
78                 mChains.add(new WorkChain(chain));
79             }
80         } else {
81             mChains = null;
82         }
83     }
84 
85     /** @hide */
WorkSource(int uid)86     public WorkSource(int uid) {
87         mNum = 1;
88         mUids = new int[] { uid, 0 };
89         mNames = null;
90         mChains = null;
91     }
92 
93     /** @hide */
WorkSource(int uid, String name)94     public WorkSource(int uid, String name) {
95         if (name == null) {
96             throw new NullPointerException("Name can't be null");
97         }
98         mNum = 1;
99         mUids = new int[] { uid, 0 };
100         mNames = new String[] { name, null };
101         mChains = null;
102     }
103 
WorkSource(Parcel in)104     WorkSource(Parcel in) {
105         mNum = in.readInt();
106         mUids = in.createIntArray();
107         mNames = in.createStringArray();
108 
109         int numChains = in.readInt();
110         if (numChains > 0) {
111             mChains = new ArrayList<>(numChains);
112             in.readParcelableList(mChains, WorkChain.class.getClassLoader());
113         } else {
114             mChains = null;
115         }
116     }
117 
118     /**
119      * Whether system services should create {@code WorkChains} (wherever possible) in the place
120      * of flat UID lists.
121      *
122      * @hide
123      */
isChainedBatteryAttributionEnabled(Context context)124     public static boolean isChainedBatteryAttributionEnabled(Context context) {
125         return Settings.Global.getInt(context.getContentResolver(),
126                 Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1;
127     }
128 
129     /** @hide */
size()130     public int size() {
131         return mNum;
132     }
133 
134     /** @hide */
get(int index)135     public int get(int index) {
136         return mUids[index];
137     }
138 
139     /** @hide */
getName(int index)140     public String getName(int index) {
141         return mNames != null ? mNames[index] : null;
142     }
143 
144     /**
145      * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
146      * intact.
147      *
148      * <p>Useful when combining with another WorkSource that doesn't have names.
149      * @hide
150      */
clearNames()151     public void clearNames() {
152         if (mNames != null) {
153             mNames = null;
154             // Clear out any duplicate uids now that we don't have names to disambiguate them.
155             int destIndex = 1;
156             int newNum = mNum;
157             for (int sourceIndex = 1; sourceIndex < mNum; sourceIndex++) {
158                 if (mUids[sourceIndex] == mUids[sourceIndex - 1]) {
159                     newNum--;
160                 } else {
161                     mUids[destIndex] = mUids[sourceIndex];
162                     destIndex++;
163                 }
164             }
165             mNum = newNum;
166         }
167     }
168 
169     /**
170      * Clear this WorkSource to be empty.
171      */
clear()172     public void clear() {
173         mNum = 0;
174         if (mChains != null) {
175             mChains.clear();
176         }
177     }
178 
179     @Override
equals(Object o)180     public boolean equals(Object o) {
181         if (o instanceof WorkSource) {
182             WorkSource other = (WorkSource) o;
183 
184             if (diff(other)) {
185                 return false;
186             }
187 
188             if (mChains != null && !mChains.isEmpty()) {
189                 return mChains.equals(other.mChains);
190             } else {
191                 return other.mChains == null || other.mChains.isEmpty();
192             }
193         }
194 
195         return false;
196     }
197 
198     @Override
hashCode()199     public int hashCode() {
200         int result = 0;
201         for (int i = 0; i < mNum; i++) {
202             result = ((result << 4) | (result >>> 28)) ^ mUids[i];
203         }
204         if (mNames != null) {
205             for (int i = 0; i < mNum; i++) {
206                 result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
207             }
208         }
209 
210         if (mChains != null) {
211             result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
212         }
213 
214         return result;
215     }
216 
217     /**
218      * Compare this WorkSource with another.
219      * @param other The WorkSource to compare against.
220      * @return If there is a difference, true is returned.
221      */
222     // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
223     // we keep its semantics the same and ignore any differences in WorkChains (if any).
diff(WorkSource other)224     public boolean diff(WorkSource other) {
225         int N = mNum;
226         if (N != other.mNum) {
227             return true;
228         }
229         final int[] uids1 = mUids;
230         final int[] uids2 = other.mUids;
231         final String[] names1 = mNames;
232         final String[] names2 = other.mNames;
233         for (int i=0; i<N; i++) {
234             if (uids1[i] != uids2[i]) {
235                 return true;
236             }
237             if (names1 != null && names2 != null && !names1[i].equals(names2[i])) {
238                 return true;
239             }
240         }
241         return false;
242     }
243 
244     /**
245      * Replace the current contents of this work source with the given
246      * work source.  If {@code other} is null, the current work source
247      * will be made empty.
248      */
set(WorkSource other)249     public void set(WorkSource other) {
250         if (other == null) {
251             mNum = 0;
252             if (mChains != null) {
253                 mChains.clear();
254             }
255             return;
256         }
257         mNum = other.mNum;
258         if (other.mUids != null) {
259             if (mUids != null && mUids.length >= mNum) {
260                 System.arraycopy(other.mUids, 0, mUids, 0, mNum);
261             } else {
262                 mUids = other.mUids.clone();
263             }
264             if (other.mNames != null) {
265                 if (mNames != null && mNames.length >= mNum) {
266                     System.arraycopy(other.mNames, 0, mNames, 0, mNum);
267                 } else {
268                     mNames = other.mNames.clone();
269                 }
270             } else {
271                 mNames = null;
272             }
273         } else {
274             mUids = null;
275             mNames = null;
276         }
277 
278         if (other.mChains != null) {
279             if (mChains != null) {
280                 mChains.clear();
281             } else {
282                 mChains = new ArrayList<>(other.mChains.size());
283             }
284 
285             for (WorkChain chain : other.mChains) {
286                 mChains.add(new WorkChain(chain));
287             }
288         }
289     }
290 
291     /** @hide */
set(int uid)292     public void set(int uid) {
293         mNum = 1;
294         if (mUids == null) mUids = new int[2];
295         mUids[0] = uid;
296         mNames = null;
297         if (mChains != null) {
298             mChains.clear();
299         }
300     }
301 
302     /** @hide */
set(int uid, String name)303     public void set(int uid, String name) {
304         if (name == null) {
305             throw new NullPointerException("Name can't be null");
306         }
307         mNum = 1;
308         if (mUids == null) {
309             mUids = new int[2];
310             mNames = new String[2];
311         }
312         mUids[0] = uid;
313         mNames[0] = name;
314         if (mChains != null) {
315             mChains.clear();
316         }
317     }
318 
319     /**
320      * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
321      * differences in chains are returned. This will be removed once its callers have been
322      * rewritten.
323      *
324      * NOTE: This is currently only used in GnssLocationProvider.
325      *
326      * @hide
327      * @deprecated for internal use only. WorkSources are opaque and no external callers should need
328      *     to be aware of internal differences.
329      */
330     @Deprecated
setReturningDiffs(WorkSource other)331     public WorkSource[] setReturningDiffs(WorkSource other) {
332         synchronized (sTmpWorkSource) {
333             sNewbWork = null;
334             sGoneWork = null;
335             updateLocked(other, true, true);
336             if (sNewbWork != null || sGoneWork != null) {
337                 WorkSource[] diffs = new WorkSource[2];
338                 diffs[0] = sNewbWork;
339                 diffs[1] = sGoneWork;
340                 return diffs;
341             }
342             return null;
343         }
344     }
345 
346     /**
347      * Merge the contents of <var>other</var> WorkSource in to this one.
348      *
349      * @param other The other WorkSource whose contents are to be merged.
350      * @return Returns true if any new sources were added.
351      */
add(WorkSource other)352     public boolean add(WorkSource other) {
353         synchronized (sTmpWorkSource) {
354             boolean uidAdded = updateLocked(other, false, false);
355 
356             boolean chainAdded = false;
357             if (other.mChains != null) {
358                 // NOTE: This is quite an expensive operation, especially if the number of chains
359                 // is large. We could look into optimizing it if it proves problematic.
360                 if (mChains == null) {
361                     mChains = new ArrayList<>(other.mChains.size());
362                 }
363 
364                 for (WorkChain wc : other.mChains) {
365                     if (!mChains.contains(wc)) {
366                         mChains.add(new WorkChain(wc));
367                     }
368                 }
369             }
370 
371             return uidAdded || chainAdded;
372         }
373     }
374 
375     /**
376      * Legacy API: DO NOT USE. Only in use from unit tests.
377      *
378      * @hide
379      * @deprecated meant for unit testing use only. Will be removed in a future API revision.
380      */
381     @Deprecated
addReturningNewbs(WorkSource other)382     public WorkSource addReturningNewbs(WorkSource other) {
383         synchronized (sTmpWorkSource) {
384             sNewbWork = null;
385             updateLocked(other, false, true);
386             return sNewbWork;
387         }
388     }
389 
390     /** @hide */
add(int uid)391     public boolean add(int uid) {
392         if (mNum <= 0) {
393             mNames = null;
394             insert(0, uid);
395             return true;
396         }
397         if (mNames != null) {
398             throw new IllegalArgumentException("Adding without name to named " + this);
399         }
400         int i = Arrays.binarySearch(mUids, 0, mNum, uid);
401         if (DEBUG) Log.d(TAG, "Adding uid " + uid + " to " + this + ": binsearch res = " + i);
402         if (i >= 0) {
403             return false;
404         }
405         insert(-i-1, uid);
406         return true;
407     }
408 
409     /** @hide */
add(int uid, String name)410     public boolean add(int uid, String name) {
411         if (mNum <= 0) {
412             insert(0, uid, name);
413             return true;
414         }
415         if (mNames == null) {
416             throw new IllegalArgumentException("Adding name to unnamed " + this);
417         }
418         int i;
419         for (i=0; i<mNum; i++) {
420             if (mUids[i] > uid) {
421                 break;
422             }
423             if (mUids[i] == uid) {
424                 int diff = mNames[i].compareTo(name);
425                 if (diff > 0) {
426                     break;
427                 }
428                 if (diff == 0) {
429                     return false;
430                 }
431             }
432         }
433         insert(i, uid, name);
434         return true;
435     }
436 
remove(WorkSource other)437     public boolean remove(WorkSource other) {
438         if (isEmpty() || other.isEmpty()) {
439             return false;
440         }
441 
442         boolean uidRemoved;
443         if (mNames == null && other.mNames == null) {
444             uidRemoved = removeUids(other);
445         } else {
446             if (mNames == null) {
447                 throw new IllegalArgumentException("Other " + other + " has names, but target "
448                         + this + " does not");
449             }
450             if (other.mNames == null) {
451                 throw new IllegalArgumentException("Target " + this + " has names, but other "
452                         + other + " does not");
453             }
454             uidRemoved = removeUidsAndNames(other);
455         }
456 
457         boolean chainRemoved = false;
458         if (other.mChains != null && mChains != null) {
459             chainRemoved = mChains.removeAll(other.mChains);
460         }
461 
462         return uidRemoved || chainRemoved;
463     }
464 
465     /**
466      * Create a new {@code WorkChain} associated with this WorkSource and return it.
467      *
468      * @hide
469      */
470     @SystemApi
createWorkChain()471     public WorkChain createWorkChain() {
472         if (mChains == null) {
473             mChains = new ArrayList<>(4);
474         }
475 
476         final WorkChain wc = new WorkChain();
477         mChains.add(wc);
478 
479         return wc;
480     }
481 
482     /**
483      * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to
484      * attribute usage to.
485      *
486      * @hide for internal use only.
487      */
isEmpty()488     public boolean isEmpty() {
489         return mNum == 0 && (mChains == null || mChains.isEmpty());
490     }
491 
492     /**
493      * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
494      * @hide
495      */
getWorkChains()496     public ArrayList<WorkChain> getWorkChains() {
497         return mChains;
498     }
499 
500     /**
501      * DO NOT USE: Hacky API provided solely for {@code GnssLocationProvider}. See
502      * {@code setReturningDiffs} as well.
503      *
504      * @hide
505      */
transferWorkChains(WorkSource other)506     public void transferWorkChains(WorkSource other) {
507         if (mChains != null) {
508             mChains.clear();
509         }
510 
511         if (other.mChains == null || other.mChains.isEmpty()) {
512             return;
513         }
514 
515         if (mChains == null) {
516             mChains = new ArrayList<>(4);
517         }
518 
519         mChains.addAll(other.mChains);
520         other.mChains.clear();
521     }
522 
removeUids(WorkSource other)523     private boolean removeUids(WorkSource other) {
524         int N1 = mNum;
525         final int[] uids1 = mUids;
526         final int N2 = other.mNum;
527         final int[] uids2 = other.mUids;
528         boolean changed = false;
529         int i1 = 0, i2 = 0;
530         if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
531         while (i1 < N1 && i2 < N2) {
532             if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
533                     + " of " + N2);
534             if (uids2[i2] == uids1[i1]) {
535                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
536                         + ": remove " + uids1[i1]);
537                 N1--;
538                 changed = true;
539                 if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
540                 i2++;
541             } else if (uids2[i2] > uids1[i1]) {
542                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
543                 i1++;
544             } else {
545                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
546                 i2++;
547             }
548         }
549 
550         mNum = N1;
551 
552         return changed;
553     }
554 
removeUidsAndNames(WorkSource other)555     private boolean removeUidsAndNames(WorkSource other) {
556         int N1 = mNum;
557         final int[] uids1 = mUids;
558         final String[] names1 = mNames;
559         final int N2 = other.mNum;
560         final int[] uids2 = other.mUids;
561         final String[] names2 = other.mNames;
562         boolean changed = false;
563         int i1 = 0, i2 = 0;
564         if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
565         while (i1 < N1 && i2 < N2) {
566             if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
567                     + " of " + N2 + ": " + uids1[i1] + " " + names1[i1]);
568             if (uids2[i2] == uids1[i1] && names2[i2].equals(names1[i1])) {
569                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
570                         + ": remove " + uids1[i1] + " " + names1[i1]);
571                 N1--;
572                 changed = true;
573                 if (i1 < N1) {
574                     System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
575                     System.arraycopy(names1, i1+1, names1, i1, N1-i1);
576                 }
577                 i2++;
578             } else if (uids2[i2] > uids1[i1]
579                     || (uids2[i2] == uids1[i1] && names2[i2].compareTo(names1[i1]) > 0)) {
580                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
581                 i1++;
582             } else {
583                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
584                 i2++;
585             }
586         }
587 
588         mNum = N1;
589 
590         return changed;
591     }
592 
updateLocked(WorkSource other, boolean set, boolean returnNewbs)593     private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
594         if (mNames == null && other.mNames == null) {
595             return updateUidsLocked(other, set, returnNewbs);
596         } else {
597             if (mNum > 0 && mNames == null) {
598                 throw new IllegalArgumentException("Other " + other + " has names, but target "
599                         + this + " does not");
600             }
601             if (other.mNum > 0 && other.mNames == null) {
602                 throw new IllegalArgumentException("Target " + this + " has names, but other "
603                         + other + " does not");
604             }
605             return updateUidsAndNamesLocked(other, set, returnNewbs);
606         }
607     }
608 
addWork(WorkSource cur, int newUid)609     private static WorkSource addWork(WorkSource cur, int newUid) {
610         if (cur == null) {
611             return new WorkSource(newUid);
612         }
613         cur.insert(cur.mNum, newUid);
614         return cur;
615     }
616 
updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs)617     private boolean updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs) {
618         int N1 = mNum;
619         int[] uids1 = mUids;
620         final int N2 = other.mNum;
621         final int[] uids2 = other.mUids;
622         boolean changed = false;
623         int i1 = 0, i2 = 0;
624         if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
625                 + " returnNewbs=" + returnNewbs);
626         while (i1 < N1 || i2 < N2) {
627             if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
628                     + " of " + N2);
629             if (i1 >= N1 || (i2 < N2 && uids2[i2] < uids1[i1])) {
630                 // Need to insert a new uid.
631                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
632                         + ": insert " + uids2[i2]);
633                 changed = true;
634                 if (uids1 == null) {
635                     uids1 = new int[4];
636                     uids1[0] = uids2[i2];
637                 } else if (N1 >= uids1.length) {
638                     int[] newuids = new int[(uids1.length*3)/2];
639                     if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
640                     if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
641                     uids1 = newuids;
642                     uids1[i1] = uids2[i2];
643                 } else {
644                     if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1);
645                     uids1[i1] = uids2[i2];
646                 }
647                 if (returnNewbs) {
648                     sNewbWork = addWork(sNewbWork, uids2[i2]);
649                 }
650                 N1++;
651                 i1++;
652                 i2++;
653             } else {
654                 if (!set) {
655                     // Skip uids that already exist or are not in 'other'.
656                     if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
657                     if (i2 < N2 && uids2[i2] == uids1[i1]) {
658                         i2++;
659                     }
660                     i1++;
661                 } else {
662                     // Remove any uids that don't exist in 'other'.
663                     int start = i1;
664                     while (i1 < N1 && (i2 >= N2 || uids2[i2] > uids1[i1])) {
665                         if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + uids1[i1]);
666                         sGoneWork = addWork(sGoneWork, uids1[i1]);
667                         i1++;
668                     }
669                     if (start < i1) {
670                         System.arraycopy(uids1, i1, uids1, start, N1-i1);
671                         N1 -= i1-start;
672                         i1 = start;
673                     }
674                     // If there is a matching uid, skip it.
675                     if (i1 < N1 && i2 < N2 && uids2[i2] == uids1[i1]) {
676                         if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
677                         i1++;
678                         i2++;
679                     }
680                 }
681             }
682         }
683 
684         mNum = N1;
685         mUids = uids1;
686 
687         return changed;
688     }
689 
690     /**
691      * Returns 0 if equal, negative if 'this' is before 'other', positive if 'this' is after 'other'.
692      */
compare(WorkSource other, int i1, int i2)693     private int compare(WorkSource other, int i1, int i2) {
694         final int diff = mUids[i1] - other.mUids[i2];
695         if (diff != 0) {
696             return diff;
697         }
698         return mNames[i1].compareTo(other.mNames[i2]);
699     }
700 
addWork(WorkSource cur, int newUid, String newName)701     private static WorkSource addWork(WorkSource cur, int newUid, String newName) {
702         if (cur == null) {
703             return new WorkSource(newUid, newName);
704         }
705         cur.insert(cur.mNum, newUid, newName);
706         return cur;
707     }
708 
updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs)709     private boolean updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs) {
710         final int N2 = other.mNum;
711         final int[] uids2 = other.mUids;
712         String[] names2 = other.mNames;
713         boolean changed = false;
714         int i1 = 0, i2 = 0;
715         if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
716                 + " returnNewbs=" + returnNewbs);
717         while (i1 < mNum || i2 < N2) {
718             if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + mNum + ", other @ " + i2
719                     + " of " + N2);
720             int diff = -1;
721             if (i1 >= mNum || (i2 < N2 && (diff=compare(other, i1, i2)) > 0)) {
722                 // Need to insert a new uid.
723                 changed = true;
724                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum
725                         + ": insert " + uids2[i2] + " " + names2[i2]);
726                 insert(i1, uids2[i2], names2[i2]);
727                 if (returnNewbs) {
728                     sNewbWork = addWork(sNewbWork, uids2[i2], names2[i2]);
729                 }
730                 i1++;
731                 i2++;
732             } else {
733                 if (!set) {
734                     if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
735                     if (i2 < N2 && diff == 0) {
736                         i2++;
737                     }
738                     i1++;
739                 } else {
740                     // Remove any uids that don't exist in 'other'.
741                     int start = i1;
742                     while (diff < 0) {
743                         if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + mUids[i1]
744                                 + " " + mNames[i1]);
745                         sGoneWork = addWork(sGoneWork, mUids[i1], mNames[i1]);
746                         i1++;
747                         if (i1 >= mNum) {
748                             break;
749                         }
750                         diff = i2 < N2 ? compare(other, i1, i2) : -1;
751                     }
752                     if (start < i1) {
753                         System.arraycopy(mUids, i1, mUids, start, mNum-i1);
754                         System.arraycopy(mNames, i1, mNames, start, mNum-i1);
755                         mNum -= i1-start;
756                         i1 = start;
757                     }
758                     // If there is a matching uid, skip it.
759                     if (i1 < mNum && diff == 0) {
760                         if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
761                         i1++;
762                         i2++;
763                     }
764                 }
765             }
766         }
767 
768         return changed;
769     }
770 
771     private void insert(int index, int uid)  {
772         if (DEBUG) Log.d(TAG, "Insert in " + this + " @ " + index + " uid " + uid);
773         if (mUids == null) {
774             mUids = new int[4];
775             mUids[0] = uid;
776             mNum = 1;
777         } else if (mNum >= mUids.length) {
778             int[] newuids = new int[(mNum*3)/2];
779             if (index > 0) {
780                 System.arraycopy(mUids, 0, newuids, 0, index);
781             }
782             if (index < mNum) {
783                 System.arraycopy(mUids, index, newuids, index+1, mNum-index);
784             }
785             mUids = newuids;
786             mUids[index] = uid;
787             mNum++;
788         } else {
789             if (index < mNum) {
790                 System.arraycopy(mUids, index, mUids, index+1, mNum-index);
791             }
792             mUids[index] = uid;
793             mNum++;
794         }
795     }
796 
797     private void insert(int index, int uid, String name)  {
798         if (mUids == null) {
799             mUids = new int[4];
800             mUids[0] = uid;
801             mNames = new String[4];
802             mNames[0] = name;
803             mNum = 1;
804         } else if (mNum >= mUids.length) {
805             int[] newuids = new int[(mNum*3)/2];
806             String[] newnames = new String[(mNum*3)/2];
807             if (index > 0) {
808                 System.arraycopy(mUids, 0, newuids, 0, index);
809                 System.arraycopy(mNames, 0, newnames, 0, index);
810             }
811             if (index < mNum) {
812                 System.arraycopy(mUids, index, newuids, index+1, mNum-index);
813                 System.arraycopy(mNames, index, newnames, index+1, mNum-index);
814             }
815             mUids = newuids;
816             mNames = newnames;
817             mUids[index] = uid;
818             mNames[index] = name;
819             mNum++;
820         } else {
821             if (index < mNum) {
822                 System.arraycopy(mUids, index, mUids, index+1, mNum-index);
823                 System.arraycopy(mNames, index, mNames, index+1, mNum-index);
824             }
825             mUids[index] = uid;
826             mNames[index] = name;
827             mNum++;
828         }
829     }
830 
831     /**
832      * Represents an attribution chain for an item of work being performed. An attribution chain is
833      * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
834      * of the work, and the node at the highest index performs the work. Nodes at other indices
835      * are intermediaries that facilitate the work. Examples :
836      *
837      * (1) Work being performed by uid=2456 (no chaining):
838      * <pre>
839      * WorkChain {
840      *   mUids = { 2456 }
841      *   mTags = { null }
842      *   mSize = 1;
843      * }
844      * </pre>
845      *
846      * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
847      *
848      * <pre>
849      * WorkChain {
850      *   mUids = { 5678, 2456 }
851      *   mTags = { null, "c1" }
852      *   mSize = 1
853      * }
854      * </pre>
855      *
856      * Attribution chains are mutable, though the only operation that can be performed on them
857      * is the addition of a new node at the end of the attribution chain to represent
858      *
859      * @hide
860      */
861     @SystemApi
862     public static final class WorkChain implements Parcelable {
863         private int mSize;
864         private int[] mUids;
865         private String[] mTags;
866 
867         // @VisibleForTesting
868         public WorkChain() {
869             mSize = 0;
870             mUids = new int[4];
871             mTags = new String[4];
872         }
873 
874         /** @hide */
875         @VisibleForTesting
876         public WorkChain(WorkChain other) {
877             mSize = other.mSize;
878             mUids = other.mUids.clone();
879             mTags = other.mTags.clone();
880         }
881 
882         private WorkChain(Parcel in) {
883             mSize = in.readInt();
884             mUids = in.createIntArray();
885             mTags = in.createStringArray();
886         }
887 
888         /**
889          * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
890          * {@code WorkChain}.
891          */
892         public WorkChain addNode(int uid, @Nullable String tag) {
893             if (mSize == mUids.length) {
894                 resizeArrays();
895             }
896 
897             mUids[mSize] = uid;
898             mTags[mSize] = tag;
899             mSize++;
900 
901             return this;
902         }
903 
904         /**
905          * Return the UID to which this WorkChain should be attributed to, i.e, the UID that
906          * initiated the work and not the UID performing it.
907          */
908         public int getAttributionUid() {
909             return mUids[0];
910         }
911 
912         /**
913          * Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
914          */
915         public String getAttributionTag() {
916             return mTags[0];
917         }
918 
919         // TODO: The following three trivial getters are purely for testing and will be removed
920         // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
921         // diffing it etc.
922 
923 
924         /** @hide */
925         @VisibleForTesting
926         public int[] getUids() {
927             int[] uids = new int[mSize];
928             System.arraycopy(mUids, 0, uids, 0, mSize);
929             return uids;
930         }
931 
932         /** @hide */
933         @VisibleForTesting
934         public String[] getTags() {
935             String[] tags = new String[mSize];
936             System.arraycopy(mTags, 0, tags, 0, mSize);
937             return tags;
938         }
939 
940         /** @hide */
941         @VisibleForTesting
942         public int getSize() {
943             return mSize;
944         }
945 
946         private void resizeArrays() {
947             final int newSize = mSize * 2;
948             int[] uids = new int[newSize];
949             String[] tags = new String[newSize];
950 
951             System.arraycopy(mUids, 0, uids, 0, mSize);
952             System.arraycopy(mTags, 0, tags, 0, mSize);
953 
954             mUids = uids;
955             mTags = tags;
956         }
957 
958         @Override
959         public String toString() {
960             StringBuilder result = new StringBuilder("WorkChain{");
961             for (int i = 0; i < mSize; ++i) {
962                 if (i != 0) {
963                     result.append(", ");
964                 }
965                 result.append("(");
966                 result.append(mUids[i]);
967                 if (mTags[i] != null) {
968                     result.append(", ");
969                     result.append(mTags[i]);
970                 }
971                 result.append(")");
972             }
973 
974             result.append("}");
975             return result.toString();
976         }
977 
978         @Override
979         public int hashCode() {
980             return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
981         }
982 
983         @Override
984         public boolean equals(Object o) {
985             if (o instanceof WorkChain) {
986                 WorkChain other = (WorkChain) o;
987 
988                 return mSize == other.mSize
989                     && Arrays.equals(mUids, other.mUids)
990                     && Arrays.equals(mTags, other.mTags);
991             }
992 
993             return false;
994         }
995 
996         @Override
997         public int describeContents() {
998             return 0;
999         }
1000 
1001         @Override
1002         public void writeToParcel(Parcel dest, int flags) {
1003             dest.writeInt(mSize);
1004             dest.writeIntArray(mUids);
1005             dest.writeStringArray(mTags);
1006         }
1007 
1008         public static final Parcelable.Creator<WorkChain> CREATOR =
1009                 new Parcelable.Creator<WorkChain>() {
1010                     public WorkChain createFromParcel(Parcel in) {
1011                         return new WorkChain(in);
1012                     }
1013                     public WorkChain[] newArray(int size) {
1014                         return new WorkChain[size];
1015                     }
1016                 };
1017     }
1018 
1019     /**
1020      * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}.
1021      *
1022      * Returns {@code null} if no differences exist, otherwise returns a two element array. The
1023      * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in
1024      * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in
1025      * {@code oldWs} but not in {@code newWs}.
1026      *
1027      * @hide
1028      */
1029     public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) {
1030         ArrayList<WorkChain> newChains = null;
1031         ArrayList<WorkChain> goneChains = null;
1032 
1033         // TODO(narayan): This is a dumb O(M*N) algorithm that determines what has changed across
1034         // WorkSource objects. We can replace this with something smarter, for e.g by defining
1035         // a Comparator between WorkChains. It's unclear whether that will be more efficient if
1036         // the number of chains associated with a WorkSource is expected to be small
1037         if (oldWs.mChains != null) {
1038             for (int i = 0; i < oldWs.mChains.size(); ++i) {
1039                 final WorkChain wc = oldWs.mChains.get(i);
1040                 if (newWs.mChains == null || !newWs.mChains.contains(wc)) {
1041                     if (goneChains == null) {
1042                         goneChains = new ArrayList<>(oldWs.mChains.size());
1043                     }
1044                     goneChains.add(wc);
1045                 }
1046             }
1047         }
1048 
1049         if (newWs.mChains != null) {
1050             for (int i = 0; i < newWs.mChains.size(); ++i) {
1051                 final WorkChain wc = newWs.mChains.get(i);
1052                 if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) {
1053                     if (newChains == null) {
1054                         newChains = new ArrayList<>(newWs.mChains.size());
1055                     }
1056                     newChains.add(wc);
1057                 }
1058             }
1059         }
1060 
1061         if (newChains != null || goneChains != null) {
1062             return new ArrayList[] { newChains, goneChains };
1063         }
1064 
1065         return null;
1066     }
1067 
1068     @Override
1069     public int describeContents() {
1070         return 0;
1071     }
1072 
1073     @Override
1074     public void writeToParcel(Parcel dest, int flags) {
1075         dest.writeInt(mNum);
1076         dest.writeIntArray(mUids);
1077         dest.writeStringArray(mNames);
1078 
1079         if (mChains == null) {
1080             dest.writeInt(-1);
1081         } else {
1082             dest.writeInt(mChains.size());
1083             dest.writeParcelableList(mChains, flags);
1084         }
1085     }
1086 
1087     @Override
1088     public String toString() {
1089         StringBuilder result = new StringBuilder();
1090         result.append("WorkSource{");
1091         for (int i = 0; i < mNum; i++) {
1092             if (i != 0) {
1093                 result.append(", ");
1094             }
1095             result.append(mUids[i]);
1096             if (mNames != null) {
1097                 result.append(" ");
1098                 result.append(mNames[i]);
1099             }
1100         }
1101 
1102         if (mChains != null) {
1103             result.append(" chains=");
1104             for (int i = 0; i < mChains.size(); ++i) {
1105                 if (i != 0) {
1106                     result.append(", ");
1107                 }
1108                 result.append(mChains.get(i));
1109             }
1110         }
1111 
1112         result.append("}");
1113         return result.toString();
1114     }
1115 
1116     /** @hide */
1117     public void writeToProto(ProtoOutputStream proto, long fieldId) {
1118         final long workSourceToken = proto.start(fieldId);
1119         for (int i = 0; i < mNum; i++) {
1120             final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
1121             proto.write(WorkSourceProto.WorkSourceContentProto.UID, mUids[i]);
1122             if (mNames != null) {
1123                 proto.write(WorkSourceProto.WorkSourceContentProto.NAME, mNames[i]);
1124             }
1125             proto.end(contentProto);
1126         }
1127 
1128         if (mChains != null) {
1129             for (int i = 0; i < mChains.size(); i++) {
1130                 final WorkChain wc = mChains.get(i);
1131                 final long workChain = proto.start(WorkSourceProto.WORK_CHAINS);
1132 
1133                 final String[] tags = wc.getTags();
1134                 final int[] uids = wc.getUids();
1135                 for (int j = 0; j < tags.length; j++) {
1136                     final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
1137                     proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]);
1138                     proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]);
1139                     proto.end(contentProto);
1140                 }
1141 
1142                 proto.end(workChain);
1143             }
1144         }
1145 
1146         proto.end(workSourceToken);
1147     }
1148 
1149     public static final Parcelable.Creator<WorkSource> CREATOR
1150             = new Parcelable.Creator<WorkSource>() {
1151         public WorkSource createFromParcel(Parcel in) {
1152             return new WorkSource(in);
1153         }
1154         public WorkSource[] newArray(int size) {
1155             return new WorkSource[size];
1156         }
1157     };
1158 }
1159