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