1 package android.os; 2 3 import android.os.WorkSourceProto; 4 import android.util.Log; 5 import android.util.proto.ProtoOutputStream; 6 7 import java.util.Arrays; 8 9 /** 10 * Describes the source of some work that may be done by someone else. 11 * Currently the public representation of what a work source is is not 12 * defined; this is an opaque container. 13 */ 14 public class WorkSource implements Parcelable { 15 static final String TAG = "WorkSource"; 16 static final boolean DEBUG = false; 17 18 int mNum; 19 int[] mUids; 20 String[] mNames; 21 22 /** 23 * Internal statics to avoid object allocations in some operations. 24 * The WorkSource object itself is not thread safe, but we need to 25 * hold sTmpWorkSource lock while working with these statics. 26 */ 27 static final WorkSource sTmpWorkSource = new WorkSource(0); 28 /** 29 * For returning newbie work from a modification operation. 30 */ 31 static WorkSource sNewbWork; 32 /** 33 * For returning gone work form a modification operation. 34 */ 35 static WorkSource sGoneWork; 36 37 /** 38 * Create an empty work source. 39 */ WorkSource()40 public WorkSource() { 41 mNum = 0; 42 } 43 44 /** 45 * Create a new WorkSource that is a copy of an existing one. 46 * If <var>orig</var> is null, an empty WorkSource is created. 47 */ WorkSource(WorkSource orig)48 public WorkSource(WorkSource orig) { 49 if (orig == null) { 50 mNum = 0; 51 return; 52 } 53 mNum = orig.mNum; 54 if (orig.mUids != null) { 55 mUids = orig.mUids.clone(); 56 mNames = orig.mNames != null ? orig.mNames.clone() : null; 57 } else { 58 mUids = null; 59 mNames = null; 60 } 61 } 62 63 /** @hide */ WorkSource(int uid)64 public WorkSource(int uid) { 65 mNum = 1; 66 mUids = new int[] { uid, 0 }; 67 mNames = null; 68 } 69 70 /** @hide */ WorkSource(int uid, String name)71 public WorkSource(int uid, String name) { 72 if (name == null) { 73 throw new NullPointerException("Name can't be null"); 74 } 75 mNum = 1; 76 mUids = new int[] { uid, 0 }; 77 mNames = new String[] { name, null }; 78 } 79 WorkSource(Parcel in)80 WorkSource(Parcel in) { 81 mNum = in.readInt(); 82 mUids = in.createIntArray(); 83 mNames = in.createStringArray(); 84 } 85 86 /** @hide */ size()87 public int size() { 88 return mNum; 89 } 90 91 /** @hide */ get(int index)92 public int get(int index) { 93 return mUids[index]; 94 } 95 96 /** @hide */ getName(int index)97 public String getName(int index) { 98 return mNames != null ? mNames[index] : null; 99 } 100 101 /** 102 * Clear names from this WorkSource. Uids are left intact. 103 * 104 * <p>Useful when combining with another WorkSource that doesn't have names. 105 * @hide 106 */ clearNames()107 public void clearNames() { 108 if (mNames != null) { 109 mNames = null; 110 // Clear out any duplicate uids now that we don't have names to disambiguate them. 111 int destIndex = 1; 112 int newNum = mNum; 113 for (int sourceIndex = 1; sourceIndex < mNum; sourceIndex++) { 114 if (mUids[sourceIndex] == mUids[sourceIndex - 1]) { 115 newNum--; 116 } else { 117 mUids[destIndex] = mUids[sourceIndex]; 118 destIndex++; 119 } 120 } 121 mNum = newNum; 122 } 123 } 124 125 /** 126 * Clear this WorkSource to be empty. 127 */ clear()128 public void clear() { 129 mNum = 0; 130 } 131 132 @Override equals(Object o)133 public boolean equals(Object o) { 134 return o instanceof WorkSource && !diff((WorkSource)o); 135 } 136 137 @Override hashCode()138 public int hashCode() { 139 int result = 0; 140 for (int i = 0; i < mNum; i++) { 141 result = ((result << 4) | (result >>> 28)) ^ mUids[i]; 142 } 143 if (mNames != null) { 144 for (int i = 0; i < mNum; i++) { 145 result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode(); 146 } 147 } 148 return result; 149 } 150 151 /** 152 * Compare this WorkSource with another. 153 * @param other The WorkSource to compare against. 154 * @return If there is a difference, true is returned. 155 */ diff(WorkSource other)156 public boolean diff(WorkSource other) { 157 int N = mNum; 158 if (N != other.mNum) { 159 return true; 160 } 161 final int[] uids1 = mUids; 162 final int[] uids2 = other.mUids; 163 final String[] names1 = mNames; 164 final String[] names2 = other.mNames; 165 for (int i=0; i<N; i++) { 166 if (uids1[i] != uids2[i]) { 167 return true; 168 } 169 if (names1 != null && names2 != null && !names1[i].equals(names2[i])) { 170 return true; 171 } 172 } 173 return false; 174 } 175 176 /** 177 * Replace the current contents of this work source with the given 178 * work source. If <var>other</var> is null, the current work source 179 * will be made empty. 180 */ set(WorkSource other)181 public void set(WorkSource other) { 182 if (other == null) { 183 mNum = 0; 184 return; 185 } 186 mNum = other.mNum; 187 if (other.mUids != null) { 188 if (mUids != null && mUids.length >= mNum) { 189 System.arraycopy(other.mUids, 0, mUids, 0, mNum); 190 } else { 191 mUids = other.mUids.clone(); 192 } 193 if (other.mNames != null) { 194 if (mNames != null && mNames.length >= mNum) { 195 System.arraycopy(other.mNames, 0, mNames, 0, mNum); 196 } else { 197 mNames = other.mNames.clone(); 198 } 199 } else { 200 mNames = null; 201 } 202 } else { 203 mUids = null; 204 mNames = null; 205 } 206 } 207 208 /** @hide */ set(int uid)209 public void set(int uid) { 210 mNum = 1; 211 if (mUids == null) mUids = new int[2]; 212 mUids[0] = uid; 213 mNames = null; 214 } 215 216 /** @hide */ set(int uid, String name)217 public void set(int uid, String name) { 218 if (name == null) { 219 throw new NullPointerException("Name can't be null"); 220 } 221 mNum = 1; 222 if (mUids == null) { 223 mUids = new int[2]; 224 mNames = new String[2]; 225 } 226 mUids[0] = uid; 227 mNames[0] = name; 228 } 229 230 /** @hide */ setReturningDiffs(WorkSource other)231 public WorkSource[] setReturningDiffs(WorkSource other) { 232 synchronized (sTmpWorkSource) { 233 sNewbWork = null; 234 sGoneWork = null; 235 updateLocked(other, true, true); 236 if (sNewbWork != null || sGoneWork != null) { 237 WorkSource[] diffs = new WorkSource[2]; 238 diffs[0] = sNewbWork; 239 diffs[1] = sGoneWork; 240 return diffs; 241 } 242 return null; 243 } 244 } 245 246 /** 247 * Merge the contents of <var>other</var> WorkSource in to this one. 248 * 249 * @param other The other WorkSource whose contents are to be merged. 250 * @return Returns true if any new sources were added. 251 */ add(WorkSource other)252 public boolean add(WorkSource other) { 253 synchronized (sTmpWorkSource) { 254 return updateLocked(other, false, false); 255 } 256 } 257 258 /** @hide */ addReturningNewbs(WorkSource other)259 public WorkSource addReturningNewbs(WorkSource other) { 260 synchronized (sTmpWorkSource) { 261 sNewbWork = null; 262 updateLocked(other, false, true); 263 return sNewbWork; 264 } 265 } 266 267 /** @hide */ add(int uid)268 public boolean add(int uid) { 269 if (mNum <= 0) { 270 mNames = null; 271 insert(0, uid); 272 return true; 273 } 274 if (mNames != null) { 275 throw new IllegalArgumentException("Adding without name to named " + this); 276 } 277 int i = Arrays.binarySearch(mUids, 0, mNum, uid); 278 if (DEBUG) Log.d(TAG, "Adding uid " + uid + " to " + this + ": binsearch res = " + i); 279 if (i >= 0) { 280 return false; 281 } 282 insert(-i-1, uid); 283 return true; 284 } 285 286 /** @hide */ add(int uid, String name)287 public boolean add(int uid, String name) { 288 if (mNum <= 0) { 289 insert(0, uid, name); 290 return true; 291 } 292 if (mNames == null) { 293 throw new IllegalArgumentException("Adding name to unnamed " + this); 294 } 295 int i; 296 for (i=0; i<mNum; i++) { 297 if (mUids[i] > uid) { 298 break; 299 } 300 if (mUids[i] == uid) { 301 int diff = mNames[i].compareTo(name); 302 if (diff > 0) { 303 break; 304 } 305 if (diff == 0) { 306 return false; 307 } 308 } 309 } 310 insert(i, uid, name); 311 return true; 312 } 313 314 /** @hide */ addReturningNewbs(int uid)315 public WorkSource addReturningNewbs(int uid) { 316 synchronized (sTmpWorkSource) { 317 sNewbWork = null; 318 sTmpWorkSource.mUids[0] = uid; 319 updateLocked(sTmpWorkSource, false, true); 320 return sNewbWork; 321 } 322 } 323 remove(WorkSource other)324 public boolean remove(WorkSource other) { 325 if (mNum <= 0 || other.mNum <= 0) { 326 return false; 327 } 328 if (mNames == null && other.mNames == null) { 329 return removeUids(other); 330 } else { 331 if (mNames == null) { 332 throw new IllegalArgumentException("Other " + other + " has names, but target " 333 + this + " does not"); 334 } 335 if (other.mNames == null) { 336 throw new IllegalArgumentException("Target " + this + " has names, but other " 337 + other + " does not"); 338 } 339 return removeUidsAndNames(other); 340 } 341 } 342 343 /** @hide */ stripNames()344 public WorkSource stripNames() { 345 if (mNum <= 0) { 346 return new WorkSource(); 347 } 348 WorkSource result = new WorkSource(); 349 int lastUid = -1; 350 for (int i=0; i<mNum; i++) { 351 int uid = mUids[i]; 352 if (i == 0 || lastUid != uid) { 353 result.add(uid); 354 } 355 } 356 return result; 357 } 358 removeUids(WorkSource other)359 private boolean removeUids(WorkSource other) { 360 int N1 = mNum; 361 final int[] uids1 = mUids; 362 final int N2 = other.mNum; 363 final int[] uids2 = other.mUids; 364 boolean changed = false; 365 int i1 = 0, i2 = 0; 366 if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this); 367 while (i1 < N1 && i2 < N2) { 368 if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2 369 + " of " + N2); 370 if (uids2[i2] == uids1[i1]) { 371 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 372 + ": remove " + uids1[i1]); 373 N1--; 374 changed = true; 375 if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1); 376 i2++; 377 } else if (uids2[i2] > uids1[i1]) { 378 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1"); 379 i1++; 380 } else { 381 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2"); 382 i2++; 383 } 384 } 385 386 mNum = N1; 387 388 return changed; 389 } 390 removeUidsAndNames(WorkSource other)391 private boolean removeUidsAndNames(WorkSource other) { 392 int N1 = mNum; 393 final int[] uids1 = mUids; 394 final String[] names1 = mNames; 395 final int N2 = other.mNum; 396 final int[] uids2 = other.mUids; 397 final String[] names2 = other.mNames; 398 boolean changed = false; 399 int i1 = 0, i2 = 0; 400 if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this); 401 while (i1 < N1 && i2 < N2) { 402 if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2 403 + " of " + N2 + ": " + uids1[i1] + " " + names1[i1]); 404 if (uids2[i2] == uids1[i1] && names2[i2].equals(names1[i1])) { 405 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 406 + ": remove " + uids1[i1] + " " + names1[i1]); 407 N1--; 408 changed = true; 409 if (i1 < N1) { 410 System.arraycopy(uids1, i1+1, uids1, i1, N1-i1); 411 System.arraycopy(names1, i1+1, names1, i1, N1-i1); 412 } 413 i2++; 414 } else if (uids2[i2] > uids1[i1] 415 || (uids2[i2] == uids1[i1] && names2[i2].compareTo(names1[i1]) > 0)) { 416 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1"); 417 i1++; 418 } else { 419 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2"); 420 i2++; 421 } 422 } 423 424 mNum = N1; 425 426 return changed; 427 } 428 updateLocked(WorkSource other, boolean set, boolean returnNewbs)429 private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) { 430 if (mNames == null && other.mNames == null) { 431 return updateUidsLocked(other, set, returnNewbs); 432 } else { 433 if (mNum > 0 && mNames == null) { 434 throw new IllegalArgumentException("Other " + other + " has names, but target " 435 + this + " does not"); 436 } 437 if (other.mNum > 0 && other.mNames == null) { 438 throw new IllegalArgumentException("Target " + this + " has names, but other " 439 + other + " does not"); 440 } 441 return updateUidsAndNamesLocked(other, set, returnNewbs); 442 } 443 } 444 addWork(WorkSource cur, int newUid)445 private static WorkSource addWork(WorkSource cur, int newUid) { 446 if (cur == null) { 447 return new WorkSource(newUid); 448 } 449 cur.insert(cur.mNum, newUid); 450 return cur; 451 } 452 updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs)453 private boolean updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs) { 454 int N1 = mNum; 455 int[] uids1 = mUids; 456 final int N2 = other.mNum; 457 final int[] uids2 = other.mUids; 458 boolean changed = false; 459 int i1 = 0, i2 = 0; 460 if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set 461 + " returnNewbs=" + returnNewbs); 462 while (i1 < N1 || i2 < N2) { 463 if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2 464 + " of " + N2); 465 if (i1 >= N1 || (i2 < N2 && uids2[i2] < uids1[i1])) { 466 // Need to insert a new uid. 467 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 468 + ": insert " + uids2[i2]); 469 changed = true; 470 if (uids1 == null) { 471 uids1 = new int[4]; 472 uids1[0] = uids2[i2]; 473 } else if (N1 >= uids1.length) { 474 int[] newuids = new int[(uids1.length*3)/2]; 475 if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1); 476 if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1); 477 uids1 = newuids; 478 uids1[i1] = uids2[i2]; 479 } else { 480 if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1); 481 uids1[i1] = uids2[i2]; 482 } 483 if (returnNewbs) { 484 sNewbWork = addWork(sNewbWork, uids2[i2]); 485 } 486 N1++; 487 i1++; 488 i2++; 489 } else { 490 if (!set) { 491 // Skip uids that already exist or are not in 'other'. 492 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip"); 493 if (i2 < N2 && uids2[i2] == uids1[i1]) { 494 i2++; 495 } 496 i1++; 497 } else { 498 // Remove any uids that don't exist in 'other'. 499 int start = i1; 500 while (i1 < N1 && (i2 >= N2 || uids2[i2] > uids1[i1])) { 501 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + uids1[i1]); 502 sGoneWork = addWork(sGoneWork, uids1[i1]); 503 i1++; 504 } 505 if (start < i1) { 506 System.arraycopy(uids1, i1, uids1, start, N1-i1); 507 N1 -= i1-start; 508 i1 = start; 509 } 510 // If there is a matching uid, skip it. 511 if (i1 < N1 && i2 < N2 && uids2[i2] == uids1[i1]) { 512 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip"); 513 i1++; 514 i2++; 515 } 516 } 517 } 518 } 519 520 mNum = N1; 521 mUids = uids1; 522 523 return changed; 524 } 525 526 /** 527 * Returns 0 if equal, negative if 'this' is before 'other', positive if 'this' is after 'other'. 528 */ compare(WorkSource other, int i1, int i2)529 private int compare(WorkSource other, int i1, int i2) { 530 final int diff = mUids[i1] - other.mUids[i2]; 531 if (diff != 0) { 532 return diff; 533 } 534 return mNames[i1].compareTo(other.mNames[i2]); 535 } 536 addWork(WorkSource cur, int newUid, String newName)537 private static WorkSource addWork(WorkSource cur, int newUid, String newName) { 538 if (cur == null) { 539 return new WorkSource(newUid, newName); 540 } 541 cur.insert(cur.mNum, newUid, newName); 542 return cur; 543 } 544 updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs)545 private boolean updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs) { 546 final int N2 = other.mNum; 547 final int[] uids2 = other.mUids; 548 String[] names2 = other.mNames; 549 boolean changed = false; 550 int i1 = 0, i2 = 0; 551 if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set 552 + " returnNewbs=" + returnNewbs); 553 while (i1 < mNum || i2 < N2) { 554 if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + mNum + ", other @ " + i2 555 + " of " + N2); 556 int diff = -1; 557 if (i1 >= mNum || (i2 < N2 && (diff=compare(other, i1, i2)) > 0)) { 558 // Need to insert a new uid. 559 changed = true; 560 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum 561 + ": insert " + uids2[i2] + " " + names2[i2]); 562 insert(i1, uids2[i2], names2[i2]); 563 if (returnNewbs) { 564 sNewbWork = addWork(sNewbWork, uids2[i2], names2[i2]); 565 } 566 i1++; 567 i2++; 568 } else { 569 if (!set) { 570 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip"); 571 if (i2 < N2 && diff == 0) { 572 i2++; 573 } 574 i1++; 575 } else { 576 // Remove any uids that don't exist in 'other'. 577 int start = i1; 578 while (diff < 0) { 579 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + mUids[i1] 580 + " " + mNames[i1]); 581 sGoneWork = addWork(sGoneWork, mUids[i1], mNames[i1]); 582 i1++; 583 if (i1 >= mNum) { 584 break; 585 } 586 diff = i2 < N2 ? compare(other, i1, i2) : -1; 587 } 588 if (start < i1) { 589 System.arraycopy(mUids, i1, mUids, start, mNum-i1); 590 System.arraycopy(mNames, i1, mNames, start, mNum-i1); 591 mNum -= i1-start; 592 i1 = start; 593 } 594 // If there is a matching uid, skip it. 595 if (i1 < mNum && diff == 0) { 596 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip"); 597 i1++; 598 i2++; 599 } 600 } 601 } 602 } 603 604 return changed; 605 } 606 607 private void insert(int index, int uid) { 608 if (DEBUG) Log.d(TAG, "Insert in " + this + " @ " + index + " uid " + uid); 609 if (mUids == null) { 610 mUids = new int[4]; 611 mUids[0] = uid; 612 mNum = 1; 613 } else if (mNum >= mUids.length) { 614 int[] newuids = new int[(mNum*3)/2]; 615 if (index > 0) { 616 System.arraycopy(mUids, 0, newuids, 0, index); 617 } 618 if (index < mNum) { 619 System.arraycopy(mUids, index, newuids, index+1, mNum-index); 620 } 621 mUids = newuids; 622 mUids[index] = uid; 623 mNum++; 624 } else { 625 if (index < mNum) { 626 System.arraycopy(mUids, index, mUids, index+1, mNum-index); 627 } 628 mUids[index] = uid; 629 mNum++; 630 } 631 } 632 633 private void insert(int index, int uid, String name) { 634 if (mUids == null) { 635 mUids = new int[4]; 636 mUids[0] = uid; 637 mNames = new String[4]; 638 mNames[0] = name; 639 mNum = 1; 640 } else if (mNum >= mUids.length) { 641 int[] newuids = new int[(mNum*3)/2]; 642 String[] newnames = new String[(mNum*3)/2]; 643 if (index > 0) { 644 System.arraycopy(mUids, 0, newuids, 0, index); 645 System.arraycopy(mNames, 0, newnames, 0, index); 646 } 647 if (index < mNum) { 648 System.arraycopy(mUids, index, newuids, index+1, mNum-index); 649 System.arraycopy(mNames, index, newnames, index+1, mNum-index); 650 } 651 mUids = newuids; 652 mNames = newnames; 653 mUids[index] = uid; 654 mNames[index] = name; 655 mNum++; 656 } else { 657 if (index < mNum) { 658 System.arraycopy(mUids, index, mUids, index+1, mNum-index); 659 System.arraycopy(mNames, index, mNames, index+1, mNum-index); 660 } 661 mUids[index] = uid; 662 mNames[index] = name; 663 mNum++; 664 } 665 } 666 667 @Override 668 public int describeContents() { 669 return 0; 670 } 671 672 @Override 673 public void writeToParcel(Parcel dest, int flags) { 674 dest.writeInt(mNum); 675 dest.writeIntArray(mUids); 676 dest.writeStringArray(mNames); 677 } 678 679 @Override 680 public String toString() { 681 StringBuilder result = new StringBuilder(); 682 result.append("WorkSource{"); 683 for (int i = 0; i < mNum; i++) { 684 if (i != 0) { 685 result.append(", "); 686 } 687 result.append(mUids[i]); 688 if (mNames != null) { 689 result.append(" "); 690 result.append(mNames[i]); 691 } 692 } 693 result.append("}"); 694 return result.toString(); 695 } 696 697 /** @hide */ 698 public void writeToProto(ProtoOutputStream proto, long fieldId) { 699 final long workSourceToken = proto.start(fieldId); 700 for (int i = 0; i < mNum; i++) { 701 final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS); 702 proto.write(WorkSourceProto.WorkSourceContentProto.UID, mUids[i]); 703 if (mNames != null) { 704 proto.write(WorkSourceProto.WorkSourceContentProto.NAME, mNames[i]); 705 } 706 proto.end(contentProto); 707 } 708 proto.end(workSourceToken); 709 } 710 711 public static final Parcelable.Creator<WorkSource> CREATOR 712 = new Parcelable.Creator<WorkSource>() { 713 public WorkSource createFromParcel(Parcel in) { 714 return new WorkSource(in); 715 } 716 public WorkSource[] newArray(int size) { 717 return new WorkSource[size]; 718 } 719 }; 720 } 721