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 // TODO: The following three trivial getters are purely for testing and will be removed 1015 // once we have higher level logic in place, e.g for serializing this WorkChain to a proto, 1016 // diffing it etc. 1017 1018 1019 /** @hide */ 1020 @VisibleForTesting 1021 public int[] getUids() { 1022 int[] uids = new int[mSize]; 1023 System.arraycopy(mUids, 0, uids, 0, mSize); 1024 return uids; 1025 } 1026 1027 /** @hide */ 1028 @VisibleForTesting 1029 public String[] getTags() { 1030 String[] tags = new String[mSize]; 1031 System.arraycopy(mTags, 0, tags, 0, mSize); 1032 return tags; 1033 } 1034 1035 /** @hide */ 1036 @VisibleForTesting 1037 public int getSize() { 1038 return mSize; 1039 } 1040 1041 private void resizeArrays() { 1042 final int newSize = mSize * 2; 1043 int[] uids = new int[newSize]; 1044 String[] tags = new String[newSize]; 1045 1046 System.arraycopy(mUids, 0, uids, 0, mSize); 1047 System.arraycopy(mTags, 0, tags, 0, mSize); 1048 1049 mUids = uids; 1050 mTags = tags; 1051 } 1052 1053 @NonNull 1054 @Override 1055 public String toString() { 1056 StringBuilder result = new StringBuilder("WorkChain{"); 1057 for (int i = 0; i < mSize; ++i) { 1058 if (i != 0) { 1059 result.append(", "); 1060 } 1061 result.append("("); 1062 result.append(mUids[i]); 1063 if (mTags[i] != null) { 1064 result.append(", "); 1065 result.append(mTags[i]); 1066 } 1067 result.append(")"); 1068 } 1069 1070 result.append("}"); 1071 return result.toString(); 1072 } 1073 1074 @Override 1075 public int hashCode() { 1076 return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags); 1077 } 1078 1079 @Override 1080 public boolean equals(@Nullable Object o) { 1081 if (o instanceof WorkChain) { 1082 WorkChain other = (WorkChain) o; 1083 1084 return mSize == other.mSize 1085 && Arrays.equals(mUids, other.mUids) 1086 && Arrays.equals(mTags, other.mTags); 1087 } 1088 1089 return false; 1090 } 1091 1092 @Override 1093 public int describeContents() { 1094 return 0; 1095 } 1096 1097 @Override 1098 public void writeToParcel(Parcel dest, int flags) { 1099 dest.writeInt(mSize); 1100 dest.writeIntArray(mUids); 1101 dest.writeStringArray(mTags); 1102 } 1103 1104 public static final @android.annotation.NonNull Parcelable.Creator<WorkChain> CREATOR = 1105 new Parcelable.Creator<WorkChain>() { 1106 public WorkChain createFromParcel(Parcel in) { 1107 return new WorkChain(in); 1108 } 1109 public WorkChain[] newArray(int size) { 1110 return new WorkChain[size]; 1111 } 1112 }; 1113 } 1114 1115 /** 1116 * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}. 1117 * 1118 * Returns {@code null} if no differences exist, otherwise returns a two element array. The 1119 * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in 1120 * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in 1121 * {@code oldWs} but not in {@code newWs}. 1122 * 1123 * @hide 1124 */ 1125 public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) { 1126 ArrayList<WorkChain> newChains = null; 1127 ArrayList<WorkChain> goneChains = null; 1128 1129 // TODO(narayan): This is a naive O(M*N) algorithm that determines what has changed across 1130 // WorkSource objects. We can replace this with something smarter, for e.g by defining 1131 // a Comparator between WorkChains. It's unclear whether that will be more efficient if 1132 // the number of chains associated with a WorkSource is expected to be small 1133 if (oldWs.mChains != null) { 1134 for (int i = 0; i < oldWs.mChains.size(); ++i) { 1135 final WorkChain wc = oldWs.mChains.get(i); 1136 if (newWs.mChains == null || !newWs.mChains.contains(wc)) { 1137 if (goneChains == null) { 1138 goneChains = new ArrayList<>(oldWs.mChains.size()); 1139 } 1140 goneChains.add(wc); 1141 } 1142 } 1143 } 1144 1145 if (newWs.mChains != null) { 1146 for (int i = 0; i < newWs.mChains.size(); ++i) { 1147 final WorkChain wc = newWs.mChains.get(i); 1148 if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) { 1149 if (newChains == null) { 1150 newChains = new ArrayList<>(newWs.mChains.size()); 1151 } 1152 newChains.add(wc); 1153 } 1154 } 1155 } 1156 1157 if (newChains != null || goneChains != null) { 1158 return new ArrayList[] { newChains, goneChains }; 1159 } 1160 1161 return null; 1162 } 1163 1164 @Override 1165 public int describeContents() { 1166 return 0; 1167 } 1168 1169 @Override 1170 public void writeToParcel(Parcel dest, int flags) { 1171 dest.writeInt(mNum); 1172 dest.writeIntArray(mUids); 1173 dest.writeStringArray(mNames); 1174 1175 if (mChains == null) { 1176 dest.writeInt(-1); 1177 } else { 1178 dest.writeInt(mChains.size()); 1179 dest.writeParcelableList(mChains, flags); 1180 } 1181 } 1182 1183 @Override 1184 public String toString() { 1185 StringBuilder result = new StringBuilder(); 1186 result.append("WorkSource{"); 1187 for (int i = 0; i < mNum; i++) { 1188 if (i != 0) { 1189 result.append(", "); 1190 } 1191 result.append(mUids[i]); 1192 if (mNames != null) { 1193 result.append(" "); 1194 result.append(mNames[i]); 1195 } 1196 } 1197 1198 if (mChains != null) { 1199 result.append(" chains="); 1200 for (int i = 0; i < mChains.size(); ++i) { 1201 if (i != 0) { 1202 result.append(", "); 1203 } 1204 result.append(mChains.get(i)); 1205 } 1206 } 1207 1208 result.append("}"); 1209 return result.toString(); 1210 } 1211 1212 /** @hide */ 1213 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 1214 final long workSourceToken = proto.start(fieldId); 1215 for (int i = 0; i < mNum; i++) { 1216 final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS); 1217 proto.write(WorkSourceProto.WorkSourceContentProto.UID, mUids[i]); 1218 if (mNames != null) { 1219 proto.write(WorkSourceProto.WorkSourceContentProto.NAME, mNames[i]); 1220 } 1221 proto.end(contentProto); 1222 } 1223 1224 if (mChains != null) { 1225 for (int i = 0; i < mChains.size(); i++) { 1226 final WorkChain wc = mChains.get(i); 1227 final long workChain = proto.start(WorkSourceProto.WORK_CHAINS); 1228 1229 final String[] tags = wc.getTags(); 1230 final int[] uids = wc.getUids(); 1231 for (int j = 0; j < tags.length; j++) { 1232 final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS); 1233 proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]); 1234 proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]); 1235 proto.end(contentProto); 1236 } 1237 1238 proto.end(workChain); 1239 } 1240 } 1241 1242 proto.end(workSourceToken); 1243 } 1244 1245 @NonNull 1246 public static final Parcelable.Creator<WorkSource> CREATOR = new Parcelable.Creator<>() { 1247 public WorkSource createFromParcel(Parcel in) { 1248 return new WorkSource(in); 1249 } 1250 public WorkSource[] newArray(int size) { 1251 return new WorkSource[size]; 1252 } 1253 }; 1254 } 1255