Home | History | Annotate | Download | only in am
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.am;
     18 
     19 import static com.android.server.am.ActivityManagerDebugConfig.*;
     20 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
     21 
     22 import android.app.ActivityManager;
     23 import android.app.AppGlobals;
     24 import android.content.ComponentName;
     25 import android.content.Intent;
     26 import android.content.pm.ActivityInfo;
     27 import android.content.pm.ApplicationInfo;
     28 import android.content.pm.IPackageManager;
     29 import android.content.pm.PackageManager;
     30 import android.os.RemoteException;
     31 import android.os.UserHandle;
     32 import android.util.Slog;
     33 
     34 import java.util.ArrayList;
     35 import java.util.Collections;
     36 import java.util.Comparator;
     37 import java.util.HashMap;
     38 
     39 /**
     40  * Class for managing the recent tasks list.
     41  */
     42 class RecentTasks extends ArrayList<TaskRecord> {
     43     private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
     44     private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
     45     private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
     46 
     47     // Maximum number recent bitmaps to keep in memory.
     48     private static final int MAX_RECENT_BITMAPS = 3;
     49 
     50     // Activity manager service.
     51     private final ActivityManagerService mService;
     52 
     53     // Mainly to avoid object recreation on multiple calls.
     54     private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
     55     private final HashMap<ComponentName, ActivityInfo> tmpAvailActCache = new HashMap<>();
     56     private final HashMap<String, ApplicationInfo> tmpAvailAppCache = new HashMap<>();
     57     private final ActivityInfo tmpActivityInfo = new ActivityInfo();
     58     private final ApplicationInfo tmpAppInfo = new ApplicationInfo();
     59 
     60     RecentTasks(ActivityManagerService service) {
     61         mService = service;
     62     }
     63 
     64     TaskRecord taskForIdLocked(int id) {
     65         final int recentsCount = size();
     66         for (int i = 0; i < recentsCount; i++) {
     67             TaskRecord tr = get(i);
     68             if (tr.taskId == id) {
     69                 return tr;
     70             }
     71         }
     72         return null;
     73     }
     74 
     75     /** Remove recent tasks for a user. */
     76     void removeTasksForUserLocked(int userId) {
     77         if(userId <= 0) {
     78             Slog.i(TAG, "Can't remove recent task on user " + userId);
     79             return;
     80         }
     81 
     82         for (int i = size() - 1; i >= 0; --i) {
     83             TaskRecord tr = get(i);
     84             if (tr.userId == userId) {
     85                 if(DEBUG_TASKS) Slog.i(TAG_TASKS,
     86                         "remove RecentTask " + tr + " when finishing user" + userId);
     87                 remove(i);
     88                 tr.removedFromRecents();
     89             }
     90         }
     91 
     92         // Remove tasks from persistent storage.
     93         mService.notifyTaskPersisterLocked(null, true);
     94     }
     95 
     96     /**
     97      * Update the recent tasks lists: make sure tasks should still be here (their
     98      * applications / activities still exist), update their availability, fix-up ordering
     99      * of affiliations.
    100      */
    101     void cleanupLocked(int userId) {
    102         int recentsCount = size();
    103         if (recentsCount == 0) {
    104             // Happens when called from the packagemanager broadcast before boot,
    105             // or just any empty list.
    106             return;
    107         }
    108 
    109         final IPackageManager pm = AppGlobals.getPackageManager();
    110         final int[] users = (userId == UserHandle.USER_ALL)
    111                 ? mService.getUsersLocked() : new int[] { userId };
    112         for (int userIdx = 0; userIdx < users.length; userIdx++) {
    113             final int user = users[userIdx];
    114             recentsCount = size() - 1;
    115             for (int i = recentsCount; i >= 0; i--) {
    116                 TaskRecord task = get(i);
    117                 if (task.userId != user) {
    118                     // Only look at tasks for the user ID of interest.
    119                     continue;
    120                 }
    121                 if (task.autoRemoveRecents && task.getTopActivity() == null) {
    122                     // This situation is broken, and we should just get rid of it now.
    123                     remove(i);
    124                     task.removedFromRecents();
    125                     Slog.w(TAG, "Removing auto-remove without activity: " + task);
    126                     continue;
    127                 }
    128                 // Check whether this activity is currently available.
    129                 if (task.realActivity != null) {
    130                     ActivityInfo ai = tmpAvailActCache.get(task.realActivity);
    131                     if (ai == null) {
    132                         try {
    133                             ai = pm.getActivityInfo(task.realActivity,
    134                                     PackageManager.GET_UNINSTALLED_PACKAGES
    135                                             | PackageManager.GET_DISABLED_COMPONENTS, user);
    136                         } catch (RemoteException e) {
    137                             // Will never happen.
    138                             continue;
    139                         }
    140                         if (ai == null) {
    141                             ai = tmpActivityInfo;
    142                         }
    143                         tmpAvailActCache.put(task.realActivity, ai);
    144                     }
    145                     if (ai == tmpActivityInfo) {
    146                         // This could be either because the activity no longer exists, or the
    147                         // app is temporarily gone.  For the former we want to remove the recents
    148                         // entry; for the latter we want to mark it as unavailable.
    149                         ApplicationInfo app = tmpAvailAppCache.get(task.realActivity.getPackageName());
    150                         if (app == null) {
    151                             try {
    152                                 app = pm.getApplicationInfo(task.realActivity.getPackageName(),
    153                                         PackageManager.GET_UNINSTALLED_PACKAGES
    154                                                 | PackageManager.GET_DISABLED_COMPONENTS, user);
    155                             } catch (RemoteException e) {
    156                                 // Will never happen.
    157                                 continue;
    158                             }
    159                             if (app == null) {
    160                                 app = tmpAppInfo;
    161                             }
    162                             tmpAvailAppCache.put(task.realActivity.getPackageName(), app);
    163                         }
    164                         if (app == tmpAppInfo || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
    165                             // Doesn't exist any more!  Good-bye.
    166                             remove(i);
    167                             task.removedFromRecents();
    168                             Slog.w(TAG, "Removing no longer valid recent: " + task);
    169                             continue;
    170                         } else {
    171                             // Otherwise just not available for now.
    172                             if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
    173                                     "Making recent unavailable: " + task);
    174                             task.isAvailable = false;
    175                         }
    176                     } else {
    177                         if (!ai.enabled || !ai.applicationInfo.enabled
    178                                 || (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
    179                             if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
    180                                     "Making recent unavailable: " + task
    181                                     + " (enabled=" + ai.enabled + "/" + ai.applicationInfo.enabled
    182                                     + " flags=" + Integer.toHexString(ai.applicationInfo.flags)
    183                                     + ")");
    184                             task.isAvailable = false;
    185                         } else {
    186                             if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS,
    187                                     "Making recent available: " + task);
    188                             task.isAvailable = true;
    189                         }
    190                     }
    191                 }
    192             }
    193         }
    194 
    195         // Verify the affiliate chain for each task.
    196         int i = 0;
    197         recentsCount = size();
    198         while (i < recentsCount) {
    199             i = processNextAffiliateChainLocked(i);
    200         }
    201         // recent tasks are now in sorted, affiliated order.
    202     }
    203 
    204     private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
    205         int recentsCount = size();
    206         TaskRecord top = task;
    207         int topIndex = taskIndex;
    208         while (top.mNextAffiliate != null && topIndex > 0) {
    209             top = top.mNextAffiliate;
    210             topIndex--;
    211         }
    212         if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at "
    213                 + topIndex + " from intial " + taskIndex);
    214         // Find the end of the chain, doing a sanity check along the way.
    215         boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
    216         int endIndex = topIndex;
    217         TaskRecord prev = top;
    218         while (endIndex < recentsCount) {
    219             TaskRecord cur = get(endIndex);
    220             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @"
    221                     + endIndex + " " + cur);
    222             if (cur == top) {
    223                 // Verify start of the chain.
    224                 if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
    225                     Slog.wtf(TAG, "Bad chain @" + endIndex
    226                             + ": first task has next affiliate: " + prev);
    227                     sane = false;
    228                     break;
    229                 }
    230             } else {
    231                 // Verify middle of the chain's next points back to the one before.
    232                 if (cur.mNextAffiliate != prev
    233                         || cur.mNextAffiliateTaskId != prev.taskId) {
    234                     Slog.wtf(TAG, "Bad chain @" + endIndex
    235                             + ": middle task " + cur + " @" + endIndex
    236                             + " has bad next affiliate "
    237                             + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId
    238                             + ", expected " + prev);
    239                     sane = false;
    240                     break;
    241                 }
    242             }
    243             if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
    244                 // Chain ends here.
    245                 if (cur.mPrevAffiliate != null) {
    246                     Slog.wtf(TAG, "Bad chain @" + endIndex
    247                             + ": last task " + cur + " has previous affiliate "
    248                             + cur.mPrevAffiliate);
    249                     sane = false;
    250                 }
    251                 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex);
    252                 break;
    253             } else {
    254                 // Verify middle of the chain's prev points to a valid item.
    255                 if (cur.mPrevAffiliate == null) {
    256                     Slog.wtf(TAG, "Bad chain @" + endIndex
    257                             + ": task " + cur + " has previous affiliate "
    258                             + cur.mPrevAffiliate + " but should be id "
    259                             + cur.mPrevAffiliate);
    260                     sane = false;
    261                     break;
    262                 }
    263             }
    264             if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) {
    265                 Slog.wtf(TAG, "Bad chain @" + endIndex
    266                         + ": task " + cur + " has affiliated id "
    267                         + cur.mAffiliatedTaskId + " but should be "
    268                         + task.mAffiliatedTaskId);
    269                 sane = false;
    270                 break;
    271             }
    272             prev = cur;
    273             endIndex++;
    274             if (endIndex >= recentsCount) {
    275                 Slog.wtf(TAG, "Bad chain ran off index " + endIndex
    276                         + ": last task " + prev);
    277                 sane = false;
    278                 break;
    279             }
    280         }
    281         if (sane) {
    282             if (endIndex < taskIndex) {
    283                 Slog.wtf(TAG, "Bad chain @" + endIndex
    284                         + ": did not extend to task " + task + " @" + taskIndex);
    285                 sane = false;
    286             }
    287         }
    288         if (sane) {
    289             // All looks good, we can just move all of the affiliated tasks
    290             // to the top.
    291             for (int i=topIndex; i<=endIndex; i++) {
    292                 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task
    293                         + " from " + i + " to " + (i-topIndex));
    294                 TaskRecord cur = remove(i);
    295                 add(i - topIndex, cur);
    296             }
    297             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks  " +  topIndex
    298                     + " to " + endIndex);
    299             return true;
    300         }
    301 
    302         // Whoops, couldn't do it.
    303         return false;
    304     }
    305 
    306     final void addLocked(TaskRecord task) {
    307         final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
    308                 || task.mNextAffiliateTaskId != INVALID_TASK_ID
    309                 || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
    310 
    311         int recentsCount = size();
    312         // Quick case: never add voice sessions.
    313         if (task.voiceSession != null) {
    314             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
    315                     "addRecent: not adding voice interaction " + task);
    316             return;
    317         }
    318         // Another quick case: check if the top-most recent task is the same.
    319         if (!isAffiliated && recentsCount > 0 && get(0) == task) {
    320             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task);
    321             return;
    322         }
    323         // Another quick case: check if this is part of a set of affiliated
    324         // tasks that are at the top.
    325         if (isAffiliated && recentsCount > 0 && task.inRecents
    326                 && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) {
    327             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0)
    328                     + " at top when adding " + task);
    329             return;
    330         }
    331 
    332         boolean needAffiliationFix = false;
    333 
    334         // Slightly less quick case: the task is already in recents, so all we need
    335         // to do is move it.
    336         if (task.inRecents) {
    337             int taskIndex = indexOf(task);
    338             if (taskIndex >= 0) {
    339                 if (!isAffiliated) {
    340                     // Simple case: this is not an affiliated task, so we just move it to the front.
    341                     remove(taskIndex);
    342                     add(0, task);
    343                     mService.notifyTaskPersisterLocked(task, false);
    344                     if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
    345                             + " from " + taskIndex);
    346                     return;
    347                 } else {
    348                     // More complicated: need to keep all affiliated tasks together.
    349                     if (moveAffiliatedTasksToFront(task, taskIndex)) {
    350                         // All went well.
    351                         return;
    352                     }
    353 
    354                     // Uh oh...  something bad in the affiliation chain, try to rebuild
    355                     // everything and then go through our general path of adding a new task.
    356                     needAffiliationFix = true;
    357                 }
    358             } else {
    359                 Slog.wtf(TAG, "Task with inRecent not in recents: " + task);
    360                 needAffiliationFix = true;
    361             }
    362         }
    363 
    364         if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
    365         trimForTaskLocked(task, true);
    366 
    367         recentsCount = size();
    368         final int maxRecents = ActivityManager.getMaxRecentTasksStatic();
    369         while (recentsCount >= maxRecents) {
    370             final TaskRecord tr = remove(recentsCount - 1);
    371             tr.removedFromRecents();
    372             recentsCount--;
    373         }
    374         task.inRecents = true;
    375         if (!isAffiliated || needAffiliationFix) {
    376             // If this is a simple non-affiliated task, or we had some failure trying to
    377             // handle it as part of an affilated task, then just place it at the top.
    378             add(0, task);
    379             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
    380         } else if (isAffiliated) {
    381             // If this is a new affiliated task, then move all of the affiliated tasks
    382             // to the front and insert this new one.
    383             TaskRecord other = task.mNextAffiliate;
    384             if (other == null) {
    385                 other = task.mPrevAffiliate;
    386             }
    387             if (other != null) {
    388                 int otherIndex = indexOf(other);
    389                 if (otherIndex >= 0) {
    390                     // Insert new task at appropriate location.
    391                     int taskIndex;
    392                     if (other == task.mNextAffiliate) {
    393                         // We found the index of our next affiliation, which is who is
    394                         // before us in the list, so add after that point.
    395                         taskIndex = otherIndex+1;
    396                     } else {
    397                         // We found the index of our previous affiliation, which is who is
    398                         // after us in the list, so add at their position.
    399                         taskIndex = otherIndex;
    400                     }
    401                     if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
    402                             "addRecent: new affiliated task added at " + taskIndex + ": " + task);
    403                     add(taskIndex, task);
    404 
    405                     // Now move everything to the front.
    406                     if (moveAffiliatedTasksToFront(task, taskIndex)) {
    407                         // All went well.
    408                         return;
    409                     }
    410 
    411                     // Uh oh...  something bad in the affiliation chain, try to rebuild
    412                     // everything and then go through our general path of adding a new task.
    413                     needAffiliationFix = true;
    414                 } else {
    415                     if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
    416                             "addRecent: couldn't find other affiliation " + other);
    417                     needAffiliationFix = true;
    418                 }
    419             } else {
    420                 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
    421                         "addRecent: adding affiliated task without next/prev:" + task);
    422                 needAffiliationFix = true;
    423             }
    424         }
    425 
    426         if (needAffiliationFix) {
    427             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations");
    428             cleanupLocked(task.userId);
    429         }
    430     }
    431 
    432     /**
    433      * If needed, remove oldest existing entries in recents that are for the same kind
    434      * of task as the given one.
    435      */
    436     int trimForTaskLocked(TaskRecord task, boolean doTrim) {
    437         int recentsCount = size();
    438         final Intent intent = task.intent;
    439         final boolean document = intent != null && intent.isDocument();
    440         int maxRecents = task.maxRecents - 1;
    441         for (int i = 0; i < recentsCount; i++) {
    442             final TaskRecord tr = get(i);
    443             if (task != tr) {
    444                 if (task.userId != tr.userId) {
    445                     continue;
    446                 }
    447                 if (i > MAX_RECENT_BITMAPS) {
    448                     tr.freeLastThumbnail();
    449                 }
    450                 final Intent trIntent = tr.intent;
    451                 final boolean sameAffinity =
    452                         task.affinity != null && task.affinity.equals(tr.affinity);
    453                 final boolean sameIntent = (intent != null && intent.filterEquals(trIntent));
    454                 final boolean trIsDocument = trIntent != null && trIntent.isDocument();
    455                 final boolean bothDocuments = document && trIsDocument;
    456                 if (!sameAffinity && !sameIntent && !bothDocuments) {
    457                     continue;
    458                 }
    459 
    460                 if (bothDocuments) {
    461                     // Do these documents belong to the same activity?
    462                     final boolean sameActivity = task.realActivity != null
    463                             && tr.realActivity != null
    464                             && task.realActivity.equals(tr.realActivity);
    465                     if (!sameActivity) {
    466                         continue;
    467                     }
    468                     if (maxRecents > 0) {
    469                         --maxRecents;
    470                         continue;
    471                     }
    472                     // Hit the maximum number of documents for this task. Fall through
    473                     // and remove this document from recents.
    474                 } else if (document || trIsDocument) {
    475                     // Only one of these is a document. Not the droid we're looking for.
    476                     continue;
    477                 }
    478             }
    479 
    480             if (!doTrim) {
    481                 // If the caller is not actually asking for a trim, just tell them we reached
    482                 // a point where the trim would happen.
    483                 return i;
    484             }
    485 
    486             // Either task and tr are the same or, their affinities match or their intents match
    487             // and neither of them is a document, or they are documents using the same activity
    488             // and their maxRecents has been reached.
    489             tr.disposeThumbnail();
    490             remove(i);
    491             if (task != tr) {
    492                 tr.removedFromRecents();
    493             }
    494             i--;
    495             recentsCount--;
    496             if (task.intent == null) {
    497                 // If the new recent task we are adding is not fully
    498                 // specified, then replace it with the existing recent task.
    499                 task = tr;
    500             }
    501             mService.notifyTaskPersisterLocked(tr, false);
    502         }
    503 
    504         return -1;
    505     }
    506 
    507     // Sort by taskId
    508     private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() {
    509         @Override
    510         public int compare(TaskRecord lhs, TaskRecord rhs) {
    511             return rhs.taskId - lhs.taskId;
    512         }
    513     };
    514 
    515     // Extract the affiliates of the chain containing recent at index start.
    516     private int processNextAffiliateChainLocked(int start) {
    517         final TaskRecord startTask = get(start);
    518         final int affiliateId = startTask.mAffiliatedTaskId;
    519 
    520         // Quick identification of isolated tasks. I.e. those not launched behind.
    521         if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null &&
    522                 startTask.mNextAffiliate == null) {
    523             // There is still a slim chance that there are other tasks that point to this task
    524             // and that the chain is so messed up that this task no longer points to them but
    525             // the gain of this optimization outweighs the risk.
    526             startTask.inRecents = true;
    527             return start + 1;
    528         }
    529 
    530         // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents.
    531         mTmpRecents.clear();
    532         for (int i = size() - 1; i >= start; --i) {
    533             final TaskRecord task = get(i);
    534             if (task.mAffiliatedTaskId == affiliateId) {
    535                 remove(i);
    536                 mTmpRecents.add(task);
    537             }
    538         }
    539 
    540         // Sort them all by taskId. That is the order they were create in and that order will
    541         // always be correct.
    542         Collections.sort(mTmpRecents, sTaskRecordComparator);
    543 
    544         // Go through and fix up the linked list.
    545         // The first one is the end of the chain and has no next.
    546         final TaskRecord first = mTmpRecents.get(0);
    547         first.inRecents = true;
    548         if (first.mNextAffiliate != null) {
    549             Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
    550             first.setNextAffiliate(null);
    551             mService.notifyTaskPersisterLocked(first, false);
    552         }
    553         // Everything in the middle is doubly linked from next to prev.
    554         final int tmpSize = mTmpRecents.size();
    555         for (int i = 0; i < tmpSize - 1; ++i) {
    556             final TaskRecord next = mTmpRecents.get(i);
    557             final TaskRecord prev = mTmpRecents.get(i + 1);
    558             if (next.mPrevAffiliate != prev) {
    559                 Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
    560                         " setting prev=" + prev);
    561                 next.setPrevAffiliate(prev);
    562                 mService.notifyTaskPersisterLocked(next, false);
    563             }
    564             if (prev.mNextAffiliate != next) {
    565                 Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
    566                         " setting next=" + next);
    567                 prev.setNextAffiliate(next);
    568                 mService.notifyTaskPersisterLocked(prev, false);
    569             }
    570             prev.inRecents = true;
    571         }
    572         // The last one is the beginning of the list and has no prev.
    573         final TaskRecord last = mTmpRecents.get(tmpSize - 1);
    574         if (last.mPrevAffiliate != null) {
    575             Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
    576             last.setPrevAffiliate(null);
    577             mService.notifyTaskPersisterLocked(last, false);
    578         }
    579 
    580         // Insert the group back into mRecentTasks at start.
    581         addAll(start, mTmpRecents);
    582         mTmpRecents.clear();
    583 
    584         // Let the caller know where we left off.
    585         return start + tmpSize;
    586     }
    587 
    588 }
    589