Home | History | Annotate | Download | only in servertransaction
      1 /*
      2  * Copyright 2018 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 android.app.servertransaction;
     18 
     19 import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
     20 import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
     21 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
     22 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESTART;
     23 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
     24 import static android.app.servertransaction.ActivityLifecycleItem.ON_START;
     25 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
     26 import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
     27 import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
     28 
     29 import android.app.ActivityThread.ActivityClientRecord;
     30 import android.util.IntArray;
     31 
     32 import com.android.internal.annotations.VisibleForTesting;
     33 
     34 import java.util.List;
     35 
     36 /**
     37  * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution.
     38  * @hide
     39  */
     40 public class TransactionExecutorHelper {
     41     // A penalty applied to path with destruction when looking for the shortest one.
     42     private static final int DESTRUCTION_PENALTY = 10;
     43 
     44     private static final int[] ON_RESUME_PRE_EXCUTION_STATES = new int[] { ON_START, ON_PAUSE };
     45 
     46     // Temp holder for lifecycle path.
     47     // No direct transition between two states should take more than one complete cycle of 6 states.
     48     @ActivityLifecycleItem.LifecycleState
     49     private IntArray mLifecycleSequence = new IntArray(6);
     50 
     51     /**
     52      * Calculate the path through main lifecycle states for an activity and fill
     53      * @link #mLifecycleSequence} with values starting with the state that follows the initial
     54      * state.
     55      * <p>NOTE: The returned value is used internally in this class and is not a copy. It's contents
     56      * may change after calling other methods of this class.</p>
     57      */
     58     @VisibleForTesting
     59     public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) {
     60         if (start == UNDEFINED || finish == UNDEFINED) {
     61             throw new IllegalArgumentException("Can't resolve lifecycle path for undefined state");
     62         }
     63         if (start == ON_RESTART || finish == ON_RESTART) {
     64             throw new IllegalArgumentException(
     65                     "Can't start or finish in intermittent RESTART state");
     66         }
     67         if (finish == PRE_ON_CREATE && start != finish) {
     68             throw new IllegalArgumentException("Can only start in pre-onCreate state");
     69         }
     70 
     71         mLifecycleSequence.clear();
     72         if (finish >= start) {
     73             // just go there
     74             for (int i = start + 1; i <= finish; i++) {
     75                 mLifecycleSequence.add(i);
     76             }
     77         } else { // finish < start, can't just cycle down
     78             if (start == ON_PAUSE && finish == ON_RESUME) {
     79                 // Special case when we can just directly go to resumed state.
     80                 mLifecycleSequence.add(ON_RESUME);
     81             } else if (start <= ON_STOP && finish >= ON_START) {
     82                 // Restart and go to required state.
     83 
     84                 // Go to stopped state first.
     85                 for (int i = start + 1; i <= ON_STOP; i++) {
     86                     mLifecycleSequence.add(i);
     87                 }
     88                 // Restart
     89                 mLifecycleSequence.add(ON_RESTART);
     90                 // Go to required state
     91                 for (int i = ON_START; i <= finish; i++) {
     92                     mLifecycleSequence.add(i);
     93                 }
     94             } else {
     95                 // Relaunch and go to required state
     96 
     97                 // Go to destroyed state first.
     98                 for (int i = start + 1; i <= ON_DESTROY; i++) {
     99                     mLifecycleSequence.add(i);
    100                 }
    101                 // Go to required state
    102                 for (int i = ON_CREATE; i <= finish; i++) {
    103                     mLifecycleSequence.add(i);
    104                 }
    105             }
    106         }
    107 
    108         // Remove last transition in case we want to perform it with some specific params.
    109         if (excludeLastState && mLifecycleSequence.size() != 0) {
    110             mLifecycleSequence.remove(mLifecycleSequence.size() - 1);
    111         }
    112 
    113         return mLifecycleSequence;
    114     }
    115 
    116     /**
    117      * Pick a state that goes before provided post-execution state and would require the least
    118      * lifecycle transitions to get to.
    119      * It will also make sure to try avoiding a path with activity destruction and relaunch if
    120      * possible.
    121      * @param r An activity that we're trying to resolve the transition for.
    122      * @param postExecutionState Post execution state to compute for.
    123      * @return One of states that precede the provided post-execution state, or
    124      *         {@link ActivityLifecycleItem#UNDEFINED} if there is not path.
    125      */
    126     @VisibleForTesting
    127     public int getClosestPreExecutionState(ActivityClientRecord r,
    128             int postExecutionState) {
    129         switch (postExecutionState) {
    130             case UNDEFINED:
    131                 return UNDEFINED;
    132             case ON_RESUME:
    133                 return getClosestOfStates(r, ON_RESUME_PRE_EXCUTION_STATES);
    134             default:
    135                 throw new UnsupportedOperationException("Pre-execution states for state: "
    136                         + postExecutionState + " is not supported.");
    137         }
    138     }
    139 
    140     /**
    141      * Pick a state that would require the least lifecycle transitions to get to.
    142      * It will also make sure to try avoiding a path with activity destruction and relaunch if
    143      * possible.
    144      * @param r An activity that we're trying to resolve the transition for.
    145      * @param finalStates An array of valid final states.
    146      * @return One of the provided final states, or {@link ActivityLifecycleItem#UNDEFINED} if none
    147      *         were provided or there is not path.
    148      */
    149     @VisibleForTesting
    150     public int getClosestOfStates(ActivityClientRecord r, int[] finalStates) {
    151         if (finalStates == null || finalStates.length == 0) {
    152             return UNDEFINED;
    153         }
    154 
    155         final int currentState = r.getLifecycleState();
    156         int closestState = UNDEFINED;
    157         for (int i = 0, shortestPath = Integer.MAX_VALUE, pathLength; i < finalStates.length; i++) {
    158             getLifecyclePath(currentState, finalStates[i], false /* excludeLastState */);
    159             pathLength = mLifecycleSequence.size();
    160             if (pathInvolvesDestruction(mLifecycleSequence)) {
    161                 pathLength += DESTRUCTION_PENALTY;
    162             }
    163             if (shortestPath > pathLength) {
    164                 shortestPath = pathLength;
    165                 closestState = finalStates[i];
    166             }
    167         }
    168         return closestState;
    169     }
    170 
    171     /** Get the lifecycle state request to match the current state in the end of a transaction. */
    172     public static ActivityLifecycleItem getLifecycleRequestForCurrentState(ActivityClientRecord r) {
    173         final int prevState = r.getLifecycleState();
    174         final ActivityLifecycleItem lifecycleItem;
    175         switch (prevState) {
    176             // TODO(lifecycler): Extend to support all possible states.
    177             case ON_PAUSE:
    178                 lifecycleItem = PauseActivityItem.obtain();
    179                 break;
    180             case ON_STOP:
    181                 lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(),
    182                         0 /* configChanges */);
    183                 break;
    184             default:
    185                 lifecycleItem = ResumeActivityItem.obtain(false /* isForward */);
    186                 break;
    187         }
    188 
    189         return lifecycleItem;
    190     }
    191 
    192     /**
    193      * Check if there is a destruction involved in the path. We want to avoid a lifecycle sequence
    194      * that involves destruction and recreation if there is another path.
    195      */
    196     private static boolean pathInvolvesDestruction(IntArray lifecycleSequence) {
    197         final int size = lifecycleSequence.size();
    198         for (int i = 0; i < size; i++) {
    199             if (lifecycleSequence.get(i) == ON_DESTROY) {
    200                 return true;
    201             }
    202         }
    203         return false;
    204     }
    205 
    206     /**
    207      * Return the index of the last callback that requests the state in which activity will be after
    208      * execution. If there is a group of callbacks in the end that requests the same specific state
    209      * or doesn't request any - we will find the first one from such group.
    210      *
    211      * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
    212      * specific state. If there is a sequence
    213      *   Configuration - ActivityResult - Configuration - ActivityResult
    214      * index 1 will be returned, because ActivityResult request on position 1 will be the last
    215      * request that moves activity to the RESUMED state where it will eventually end.
    216      */
    217     static int lastCallbackRequestingState(ClientTransaction transaction) {
    218         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
    219         if (callbacks == null || callbacks.size() == 0) {
    220             return -1;
    221         }
    222 
    223         // Go from the back of the list to front, look for the request closes to the beginning that
    224         // requests the state in which activity will end after all callbacks are executed.
    225         int lastRequestedState = UNDEFINED;
    226         int lastRequestingCallback = -1;
    227         for (int i = callbacks.size() - 1; i >= 0; i--) {
    228             final ClientTransactionItem callback = callbacks.get(i);
    229             final int postExecutionState = callback.getPostExecutionState();
    230             if (postExecutionState != UNDEFINED) {
    231                 // Found a callback that requests some post-execution state.
    232                 if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
    233                     // It's either a first-from-end callback that requests state or it requests
    234                     // the same state as the last one. In both cases, we will use it as the new
    235                     // candidate.
    236                     lastRequestedState = postExecutionState;
    237                     lastRequestingCallback = i;
    238                 } else {
    239                     break;
    240                 }
    241             }
    242         }
    243 
    244         return lastRequestingCallback;
    245     }
    246 }
    247