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