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