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