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