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