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