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