1 /*
2  * Copyright (C) 2015 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.performanceapp.tests;
18 
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.List;
26 
27 import android.app.ActivityManager;
28 import android.app.ActivityManager.RunningAppProcessInfo;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.pm.ActivityInfo;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.os.Bundle;
36 import android.os.Environment;
37 import android.os.ParcelFileDescriptor;
38 import android.support.test.InstrumentationRegistry;
39 import android.test.InstrumentationTestCase;
40 import android.test.suitebuilder.annotation.MediumTest;
41 import android.util.Log;
42 
43 /**
44  * To test the App launch performance on the given target package for the list of activities. It
45  * launches the activities present in the target package the number of times in launch count or it
46  * launches only the activities mentioned in custom activity list the launch count times and returns
47  * the path to the files where the atrace logs are stored corresponding to each activity launch
48  */
49 public class AppLaunchTests extends InstrumentationTestCase {
50 
51     private static final String TAG = "AppLaunchInstrumentation";
52     private static final String TARGETPACKAGE = "targetpackage";
53     private static final String ACTIVITYLIST = "activitylist";
54     private static final String LAUNCHCOUNT = "launchcount";
55     private static final String RECORDTRACE = "recordtrace";
56     private static final String ATRACE_START = "atrace --async_start am view gfx";
57     private static final String ATRACE_DUMP = "atrace --async_dump";
58     private static final String ATRACE_STOP = "atrace --async_stop";
59     private static final String FORCE_STOP = "am force-stop ";
60 
61     private Context mContext;
62     private Bundle mResult;
63     private String mTargetPackageName;
64     private int mLaunchCount;
65     private String mCustomActivityList;
66     private PackageInfo mPackageInfo;
67     private boolean mRecordTrace = true;
68     private List<String> mActivityList;
69 
70     /**
71      * {@inheritDoc}
72      */
73     @Override
setUp()74     public void setUp() throws Exception {
75         super.setUp();
76         mContext = getInstrumentation().getTargetContext();
77         assertNotNull("Failed to get context", mContext);
78         Bundle args = InstrumentationRegistry.getArguments();
79         assertNotNull("Unable to get the args", args);
80         mTargetPackageName = args.getString(TARGETPACKAGE);
81         assertNotNull("Target package name not set", mTargetPackageName);
82         mCustomActivityList = args.getString(ACTIVITYLIST);
83         if (mCustomActivityList == null || mCustomActivityList.isEmpty()) {
84             // Get full list of activities from the target package
85             mActivityList = getActivityList("");
86         } else {
87             // Get only the user defined list of activities from the target package
88             mActivityList = getActivityList(mCustomActivityList);
89         }
90         assertTrue("Activity List is empty", (mActivityList.size() > 0));
91         mLaunchCount = Integer.parseInt(args.getString(LAUNCHCOUNT));
92         assertTrue("Invalid Launch Count", mLaunchCount > 0);
93         if (args.getString(RECORDTRACE) != null
94                 && args.getString(RECORDTRACE).equalsIgnoreCase("false")) {
95             mRecordTrace = false;
96         }
97         mResult = new Bundle();
98     }
99 
100     @MediumTest
testAppLaunchPerformance()101     public void testAppLaunchPerformance() throws Exception {
102         assertTrue("Cannot write in External File", isExternalStorageWritable());
103         File root = Environment.getExternalStorageDirectory();
104         assertNotNull("Unable to get the root of the external storage", root);
105         File logsDir = new File(root, "atrace_logs");
106         assertTrue("Unable to create the directory to store atrace logs", logsDir.mkdir());
107         for (int count = 0; count < mLaunchCount; count++) {
108             for (String activityName : mActivityList) {
109                 ComponentName cn = new ComponentName(mTargetPackageName,
110                         activityName);
111                 Intent intent = new Intent(Intent.ACTION_MAIN);
112                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
113                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
114                 intent.setComponent(cn);
115 
116                 // Start the atrace
117                 if (mRecordTrace) {
118                     assertNotNull(
119                             "Unable to start atrace async",
120                             getInstrumentation().getUiAutomation()
121                                     .executeShellCommand(ATRACE_START));
122                     // Sleep for 10 secs to make sure atrace command is started
123                     Thread.sleep(10 * 1000);
124                 }
125 
126                 // Launch the activity
127                 mContext.startActivity(intent);
128                 Thread.sleep(5 * 1000);
129 
130                 // Dump atrace info and write it to file
131                 if (mRecordTrace) {
132                     int processId = getProcessId(mTargetPackageName);
133                     assertTrue("Not able to retrive the process id for the package:"
134                             + mTargetPackageName, processId > 0);
135                     String fileName = String.format("%s-%d-%d", activityName, count, processId);
136                     ParcelFileDescriptor parcelFile =
137                             getInstrumentation().getUiAutomation().executeShellCommand(ATRACE_DUMP);
138                     assertNotNull("Unable to get the File descriptor to standard out",
139                             parcelFile);
140                     InputStream inputStream = new FileInputStream(parcelFile.getFileDescriptor());
141                     File file = new File(logsDir, fileName);
142                     FileOutputStream outputStream = new FileOutputStream(file);
143                     try {
144                         byte[] buffer = new byte[1024];
145                         int length;
146                         while ((length = inputStream.read(buffer)) > 0) {
147                             outputStream.write(buffer, 0, length);
148                         }
149                     } catch (IOException e) {
150                         Log.w(TAG, "Error writing atrace info to file", e);
151                     }
152                     inputStream.close();
153                     outputStream.close();
154 
155                     // Stop the atrace
156                     assertNotNull(
157                             "Unable to stop the atrace",
158                             getInstrumentation().getUiAutomation().executeShellCommand(ATRACE_STOP));
159 
160                     // To keep track of the activity name,list of atrace file name
161                     registerTraceFileNames(activityName, fileName);
162                 }
163                 assertNotNull("Unable to stop recent activity launched",
164                         getInstrumentation().getUiAutomation().executeShellCommand(
165                                 FORCE_STOP + mTargetPackageName));
166                 Thread.sleep(5 * 1000);
167             }
168         }
169         getInstrumentation().sendStatus(0, mResult);
170     }
171 
172     /**
173      * Method to check if external storage is writable
174      * @return
175      */
isExternalStorageWritable()176     public boolean isExternalStorageWritable() {
177         return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
178     }
179 
180     /**
181      * Method to get list of activities present in given target package If customActivityList is
182      * passed then include only those activities
183      * @return list of activity names
184      */
getActivityList(String customActivityList)185     private List<String> getActivityList(String customActivityList) {
186         mActivityList = new ArrayList<String>();
187         try {
188             mPackageInfo = mContext.getPackageManager().getPackageInfo(
189                     mTargetPackageName, 1);
190             assertNotNull("Unable to get  the target package info", mPackageInfo);
191         } catch (NameNotFoundException e) {
192             fail(String.format("Target application: %s not found", mTargetPackageName));
193         }
194         for (ActivityInfo activityInfo : mPackageInfo.activities) {
195             mActivityList.add(activityInfo.name);
196         }
197         if (!customActivityList.isEmpty()) {
198             List<String> finalActivityList = new
199                     ArrayList<String>();
200             String customList[] = customActivityList.split(",");
201             for (int count = 0; count < customList.length; count++) {
202                 if (mActivityList.contains(customList[count])) {
203                     finalActivityList.add(customList[count]);
204                 } else {
205                     fail(String.format("Activity: %s not present in the target package : %s ",
206                             customList[count], mTargetPackageName));
207                 }
208             }
209             mActivityList = finalActivityList;
210         }
211         return mActivityList;
212     }
213 
214     /**
215      * Method to retrieve process id from the activity manager
216      * @param processName
217      * @return
218      */
getProcessId(String processName)219     private int getProcessId(String processName) {
220         ActivityManager am = (ActivityManager) getInstrumentation()
221                 .getContext().getSystemService(Context.ACTIVITY_SERVICE);
222         List<RunningAppProcessInfo> appsInfo = am.getRunningAppProcesses();
223         assertNotNull("Unable to retrieve running apps info", appsInfo);
224         for (RunningAppProcessInfo appInfo : appsInfo) {
225             if (appInfo.processName.equals(processName)) {
226                 return appInfo.pid;
227             }
228         }
229         return -1;
230     }
231 
232     /**
233      * To add the process id to the result map
234      * @param activityNamereturn
235      * @return
236      * @throws IOException
237      */
registerTraceFileNames(String activityName, String absPath)238     private void registerTraceFileNames(String activityName, String absPath)
239             throws IOException {
240         if (mResult.containsKey(activityName)) {
241             String existingResult = (String) mResult.get(activityName);
242             mResult.putString(activityName, existingResult + "," + absPath);
243         } else {
244             mResult.putString(activityName, "" + absPath);
245         }
246     }
247 }
248 
249