1 /*
2  * Copyright (C) 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.server.wm.lifecycle;
18 
19 import static android.server.wm.StateLogger.log;
20 
21 import android.app.Activity;
22 import android.content.ContentProvider;
23 import android.content.ContentProviderClient;
24 import android.content.ContentValues;
25 import android.database.Cursor;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.util.Pair;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 /**
35  * Used as a shared log storage of activity lifecycle transitions. Methods must be synchronized to
36  * prevent concurrent modification of the log store.
37  */
38 public class LifecycleLog extends ContentProvider {
39 
40     public enum ActivityCallback {
41         ON_CREATE,
42         ON_START,
43         ON_RESUME,
44         ON_PAUSE,
45         ON_STOP,
46         ON_RESTART,
47         ON_DESTROY,
48         ON_ACTIVITY_RESULT,
49         ON_POST_CREATE,
50         ON_NEW_INTENT,
51         ON_MULTI_WINDOW_MODE_CHANGED,
52         ON_TOP_POSITION_GAINED,
53         ON_TOP_POSITION_LOST,
54         ON_USER_LEAVE_HINT
55     }
56 
57     interface LifecycleTrackerCallback {
onActivityLifecycleChanged()58         void onActivityLifecycleChanged();
59     }
60 
61     /** Identifies the activity to which the event corresponds. */
62     private static final String EXTRA_KEY_ACTIVITY = "key_activity";
63     /** Puts a lifecycle or callback into the container. */
64     private static final String METHOD_ADD_CALLBACK = "add_callback";
65     /** Content provider URI for cross-process lifecycle transitions collecting. */
66     private static final Uri URI = Uri.parse("content://android.server.wm.lifecycle.logprovider");
67 
68     /**
69      * Log for encountered activity callbacks. Note that methods accessing or modifying this
70      * list should be synchronized as it can be accessed from different threads.
71      */
72     private final static List<Pair<String, ActivityCallback>> sLog = new ArrayList<>();
73 
74     /**
75      * Lifecycle tracker interface that waits for correct states or sequences.
76      */
77     private static LifecycleTrackerCallback sLifecycleTracker;
78 
79     /** Clear the entire transition log. */
clear()80     void clear() {
81         synchronized(sLog) {
82             sLog.clear();
83         }
84     }
85 
setLifecycleTracker(LifecycleTrackerCallback lifecycleTracker)86     public void setLifecycleTracker(LifecycleTrackerCallback lifecycleTracker) {
87         sLifecycleTracker = lifecycleTracker;
88     }
89 
90     /** Add activity callback to the log. */
onActivityCallback(String activityCanonicalName, ActivityCallback callback)91     private void onActivityCallback(String activityCanonicalName,
92             ActivityCallback callback) {
93         synchronized (sLog) {
94             sLog.add(new Pair<>(activityCanonicalName, callback));
95         }
96         log("Activity " + activityCanonicalName + " receiver callback " + callback);
97         // Trigger check for valid state in the tracker
98         if (sLifecycleTracker != null) {
99             sLifecycleTracker.onActivityLifecycleChanged();
100         }
101     }
102 
103     /** Get logs for all recorded transitions. */
getLog()104     List<Pair<String, ActivityCallback>> getLog() {
105         // Wrap in a new list to prevent concurrent modification
106         synchronized(sLog) {
107             return new ArrayList<>(sLog);
108         }
109     }
110 
111     /** Get transition logs for the specified activity. */
getActivityLog(Class<? extends Activity> activityClass)112     List<ActivityCallback> getActivityLog(Class<? extends Activity> activityClass) {
113         final String activityName = activityClass.getCanonicalName();
114         log("Looking up log for activity: " + activityName);
115         final List<ActivityCallback> activityLog = new ArrayList<>();
116         synchronized(sLog) {
117             for (Pair<String, ActivityCallback> transition : sLog) {
118                 if (transition.first.equals(activityName)) {
119                     activityLog.add(transition.second);
120                 }
121             }
122         }
123         return activityLog;
124     }
125 
126 
127     // ContentProvider implementation for cross-process tracking
128 
129     public static class LifecycleLogClient implements AutoCloseable {
130         private static final String EMPTY_ARG = "";
131         private final ContentProviderClient mClient;
132         private final String mOwner;
133 
LifecycleLogClient(ContentProviderClient client, Activity owner)134         LifecycleLogClient(ContentProviderClient client, Activity owner) {
135             mClient = client;
136             mOwner = owner.getClass().getCanonicalName();
137         }
138 
onActivityCallback(ActivityCallback callback)139         void onActivityCallback(ActivityCallback callback) {
140             final Bundle extras = new Bundle();
141             extras.putInt(METHOD_ADD_CALLBACK, callback.ordinal());
142             extras.putString(EXTRA_KEY_ACTIVITY, mOwner);
143             try {
144                 mClient.call(METHOD_ADD_CALLBACK, EMPTY_ARG, extras);
145             } catch (RemoteException e) {
146                 throw new RuntimeException(e);
147             }
148         }
149 
150         @Override
close()151         public void close() {
152             mClient.close();
153         }
154 
create(Activity owner)155         static LifecycleLogClient create(Activity owner) {
156             final ContentProviderClient client = owner.getContentResolver()
157                     .acquireContentProviderClient(URI);
158             if (client == null) {
159                 throw new RuntimeException("Unable to acquire " + URI);
160             }
161             return new LifecycleLogClient(client, owner);
162         }
163     }
164 
165     @Override
call(String method, String arg, Bundle extras)166     public Bundle call(String method, String arg, Bundle extras) {
167         if (!METHOD_ADD_CALLBACK.equals(method)) {
168             throw new UnsupportedOperationException();
169         }
170         onActivityCallback(extras.getString(EXTRA_KEY_ACTIVITY),
171                 ActivityCallback.values()[extras.getInt(method)]);
172         return null;
173     }
174 
175     @Override
onCreate()176     public boolean onCreate() {
177         return true;
178     }
179 
180     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)181     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
182             String sortOrder) {
183         return null;
184     }
185 
186     @Override
getType(Uri uri)187     public String getType(Uri uri) {
188         return null;
189     }
190 
191     @Override
insert(Uri uri, ContentValues values)192     public Uri insert(Uri uri, ContentValues values) {
193         return null;
194     }
195 
196     @Override
delete(Uri uri, String selection, String[] selectionArgs)197     public int delete(Uri uri, String selection, String[] selectionArgs) {
198         return 0;
199     }
200 
201     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)202     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
203         return 0;
204     }
205 }
206