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