diff --git a/tests/AppLaunch/Android.mk b/tests/AppLaunch/Android.mk index c0560fd78e6c7..e6f6c394f42de 100644 --- a/tests/AppLaunch/Android.mk +++ b/tests/AppLaunch/Android.mk @@ -11,7 +11,9 @@ LOCAL_PACKAGE_NAME := AppLaunch LOCAL_CERTIFICATE := platform LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test + include $(BUILD_PACKAGE) # Use the following include to make our test apk. -include $(call all-makefiles-under,$(LOCAL_PATH)) \ No newline at end of file +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/AppLaunch/AndroidManifest.xml b/tests/AppLaunch/AndroidManifest.xml index ac6760bb8366e..7dfd7bafbaaa2 100644 --- a/tests/AppLaunch/AndroidManifest.xml +++ b/tests/AppLaunch/AndroidManifest.xml @@ -3,6 +3,14 @@ + + + + + + @@ -10,4 +18,4 @@ - \ No newline at end of file + diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 085b7aaaa9e57..2346f8547550f 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -15,14 +15,13 @@ */ package com.android.tests.applaunch; +import java.io.OutputStreamWriter; + import android.accounts.Account; import android.accounts.AccountManager; +import android.app.ActivityManagerNative; import android.app.ActivityManager; import android.app.ActivityManager.ProcessErrorStateInfo; -import android.app.ActivityManagerNative; -import android.app.IActivityManager; -import android.app.IActivityManager.WaitResult; -import android.app.UiAutomation; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -31,16 +30,29 @@ import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; +import android.app.UiAutomation; +import android.app.IActivityManager; +import android.app.IActivityManager.WaitResult; +import android.support.test.rule.logging.AtraceLogger; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestRunner; import android.util.Log; - +import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.ArrayList; import java.util.Map; import java.util.Set; +import android.os.ParcelFileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.InputStreamReader; /** * This test is intended to measure the time it takes for the apps to start. @@ -55,27 +67,66 @@ public class AppLaunch extends InstrumentationTestCase { private static final int JOIN_TIMEOUT = 10000; private static final String TAG = AppLaunch.class.getSimpleName(); - private static final String KEY_APPS = "apps"; - private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; // optional parameter: comma separated list of required account types before proceeding // with the app launch private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts"; - private static final String KEY_SKIP_INITIAL_LAUNCH = "skip_initial_launch"; + private static final String KEY_APPS = "apps"; + private static final String KEY_TRIAL_LAUNCH = "trial_launch"; + private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; + private static final String KEY_LAUNCH_ORDER = "launch_order"; + private static final String KEY_DROP_CACHE = "drop_cache"; + private static final String KEY_SIMPLEPPERF_CMD = "simpleperf_cmd"; + private static final String KEY_TRACE_ITERATIONS = "trace_iterations"; + private static final String KEY_LAUNCH_DIRECTORY = "launch_directory"; + private static final String KEY_TRACE_DIRECTORY = "trace_directory"; + private static final String KEY_TRACE_CATEGORY = "trace_categories"; + private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize"; + private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; private static final String WEARABLE_ACTION_GOOGLE = "com.google.android.wearable.action.GOOGLE"; private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 60000; //60s to allow app to idle private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches - private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps + private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 5000; //5s between launching apps + private static final String LAUNCH_SUB_DIRECTORY = "launch_logs"; + private static final String LAUNCH_FILE = "applaunch.txt"; + private static final String TRACE_SUB_DIRECTORY = "atrace_logs"; + private static final String DEFAULT_TRACE_CATEGORIES = "sched,freq,gfx,view,dalvik,webview," + + "input,wm,disk,am,wm"; + private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000"; + private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10"; + private static final String TRIAL_LAUNCH = "TRAIL_LAUNCH"; + private static final String DELIMITER = ","; + private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh"; + private static final String APP_LAUNCH_CMD = "am start -W -n"; + private static final String SUCCESS_MESSAGE = "Status: ok"; + private static final String THIS_TIME = "ThisTime:"; + private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d"; + private static final String TRACE_ITERATION = "TRACE_ITERATION - %d"; + private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION"; + private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION"; + private static final String LAUNCH_ORDER_CYCLIC = "cyclic"; + private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential"; + private Map mNameToIntent; private Map mNameToProcess; + private List mLaunchOrderList = new ArrayList(); private Map mNameToResultKey; - private Map mNameToLaunchTime; + private Map> mNameToLaunchTime; private IActivityManager mAm; + private String mSimplePerfCmd = null; + private String mLaunchOrder = null; + private boolean mDropCache = false; private int mLaunchIterations = 10; + private int mTraceLaunchCount = 0; + private String mTraceDirectoryStr = null; private Bundle mResult = new Bundle(); private Set mRequiredAccounts; - private boolean mSkipInitialLaunch = false; + private boolean mTrailLaunch = true; + private File mFile = null; + private FileOutputStream mOutputStream = null; + private BufferedWriter mBufferedWriter = null; + @Override protected void setUp() throws Exception { @@ -89,69 +140,231 @@ public class AppLaunch extends InstrumentationTestCase { super.tearDown(); } - public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException { + public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException, + IOException, InterruptedException { InstrumentationTestRunner instrumentation = (InstrumentationTestRunner)getInstrumentation(); Bundle args = instrumentation.getArguments(); mAm = ActivityManagerNative.getDefault(); - + String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); + mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY); + mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE)); + mSimplePerfCmd = args.getString(KEY_SIMPLEPPERF_CMD); + mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC); createMappings(); parseArgs(args); checkAccountSignIn(); - if (!mSkipInitialLaunch) { - // do initial app launch, without force stopping - for (String app : mNameToResultKey.keySet()) { - long launchTime = startApp(app, false); - if (launchTime <= 0) { - mNameToLaunchTime.put(app, -1L); - // simply pass the app if launch isn't successful - // error should have already been logged by startApp - continue; - } else { - mNameToLaunchTime.put(app, launchTime); - } - sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); - closeApp(app, false); - sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); + // Root directory for applaunch file to log the app launch output + // Will be useful in case of simpleperf command is used + File launchRootDir = null; + if (null != launchDirectory && !launchDirectory.isEmpty()) { + launchRootDir = new File(launchDirectory); + if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { + throw new IOException("Unable to create the destination directory"); } } - // do the real app launch now - for (int i = 0; i < mLaunchIterations; i++) { - for (String app : mNameToResultKey.keySet()) { - long prevLaunchTime = mNameToLaunchTime.get(app); - long launchTime = 0; - if (prevLaunchTime < 0) { - // skip if the app has previous failures - continue; + + try { + File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); + if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { + throw new IOException("Unable to create the lauch file sub directory"); + } + mFile = new File(launchSubDir, LAUNCH_FILE); + mOutputStream = new FileOutputStream(mFile); + mBufferedWriter = new BufferedWriter(new OutputStreamWriter( + mOutputStream)); + + // Root directory for trace file during the launches + File rootTrace = null; + File rootTraceSubDir = null; + int traceBufferSize = 0; + int traceDumpInterval = 0; + Set traceCategoriesSet = null; + if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) { + rootTrace = new File(mTraceDirectoryStr); + if (!rootTrace.exists() && !rootTrace.mkdirs()) { + throw new IOException("Unable to create the trace directory"); } - launchTime = startApp(app, true); - if (launchTime <= 0) { - // if it fails once, skip the rest of the launches - mNameToLaunchTime.put(app, -1L); - continue; + rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY); + if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) { + throw new IOException("Unable to create the trace sub directory"); } - // keep the min launch time - if (launchTime < prevLaunchTime) { - mNameToLaunchTime.put(app, launchTime); + assertNotNull("Trace iteration parameter is mandatory", + args.getString(KEY_TRACE_ITERATIONS)); + mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS)); + String traceCategoriesStr = args + .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES); + traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE, + DEFAULT_TRACE_BUFFER_SIZE)); + traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL, + DEFAULT_TRACE_DUMP_INTERVAL)); + traceCategoriesSet = new HashSet(); + if (!traceCategoriesStr.isEmpty()) { + String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER); + for (int i = 0; i < traceCategoriesSplit.length; i++) { + traceCategoriesSet.add(traceCategoriesSplit[i]); + } } - sleep(POST_LAUNCH_IDLE_TIMEOUT); - closeApp(app, true); - sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); + } + + // Get the app launch order based on launch order, trial launch, + // launch iterations and trace iterations + setLaunchOrder(); + + for (LaunchOrder launch : mLaunchOrderList) { + + // App launch times for trial launch will not be used for final + // launch time calculations. + if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) { + // In the "applaunch.txt" file, trail launches is referenced using + // "TRIAL_LAUNCH" + long launchTime = startApp(launch.getApp(), true, launch.getLaunchReason()); + if (launchTime < 0) { + List appLaunchList = new ArrayList(); + appLaunchList.add(-1L); + mNameToLaunchTime.put(launch.getApp(), appLaunchList); + // simply pass the app if launch isn't successful + // error should have already been logged by startApp + continue; + } + sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); + closeApp(launch.getApp(), true); + dropCache(); + sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); + } + + // App launch times used for final calculation + if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) { + long launchTime = -1; + if (null != mNameToLaunchTime.get(launch.getApp())) { + long firstLaunchTime = mNameToLaunchTime.get(launch.getApp()).get(0); + if (firstLaunchTime < 0) { + // skip if the app has failures while launched first + continue; + } + } + // In the "applaunch.txt" file app launches are referenced using + // "LAUNCH_ITERATION - ITERATION NUM" + launchTime = startApp(launch.getApp(), true, launch.getLaunchReason()); + if (launchTime < 0) { + // if it fails once, skip the rest of the launches + List appLaunchList = new ArrayList(); + appLaunchList.add(-1L); + mNameToLaunchTime.put(launch.getApp(), appLaunchList); + continue; + } else { + if (null != mNameToLaunchTime.get(launch.getApp())) { + mNameToLaunchTime.get(launch.getApp()).add(launchTime); + } else { + List appLaunchList = new ArrayList(); + appLaunchList.add(launchTime); + mNameToLaunchTime.put(launch.getApp(), appLaunchList); + } + } + sleep(POST_LAUNCH_IDLE_TIMEOUT); + closeApp(launch.getApp(), true); + dropCache(); + sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); + } + + // App launch times for trace launch will not be used for final + // launch time calculations. + if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) { + AtraceLogger atraceLogger = AtraceLogger + .getAtraceLoggerInstance(getInstrumentation()); + // Start the trace + try { + atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, + traceDumpInterval, rootTraceSubDir, + String.format("%s-%s", launch.getApp(), launch.getLaunchReason())); + startApp(launch.getApp(), true, launch.getLaunchReason()); + sleep(POST_LAUNCH_IDLE_TIMEOUT); + } finally { + // Stop the trace + atraceLogger.atraceStop(); + closeApp(launch.getApp(), true); + dropCache(); + sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); + } + } + } + } finally { + if (null != mBufferedWriter) { + mBufferedWriter.close(); } } + for (String app : mNameToResultKey.keySet()) { - long launchTime = mNameToLaunchTime.get(app); - if (launchTime != -1) { - mResult.putLong(mNameToResultKey.get(app), launchTime); + StringBuilder launchTimes = new StringBuilder(); + for (Long launch : mNameToLaunchTime.get(app)) { + launchTimes.append(launch); + launchTimes.append(","); } + mResult.putString(mNameToResultKey.get(app), launchTimes.toString()); } instrumentation.sendStatus(0, mResult); } + /** + * If launch order is "cyclic" then apps will be launched one after the + * other for each iteration count. + * If launch order is "sequential" then each app will be launched for given number + * iterations at once before launching the other apps. + */ + private void setLaunchOrder() { + if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) { + if (mTrailLaunch) { + for (String app : mNameToResultKey.keySet()) { + mLaunchOrderList.add(new LaunchOrder(app, TRIAL_LAUNCH)); + } + } + for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { + for (String app : mNameToResultKey.keySet()) { + mLaunchOrderList.add(new LaunchOrder(app, + String.format(LAUNCH_ITERATION, launchCount))); + } + } + if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { + for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { + for (String app : mNameToResultKey.keySet()) { + mLaunchOrderList.add(new LaunchOrder(app, + String.format(TRACE_ITERATION, traceCount))); + } + } + } + } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) { + for (String app : mNameToResultKey.keySet()) { + if (mTrailLaunch) { + mLaunchOrderList.add(new LaunchOrder(app, TRIAL_LAUNCH)); + } + for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { + mLaunchOrderList.add(new LaunchOrder(app, + String.format(LAUNCH_ITERATION, launchCount))); + } + if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { + for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { + mLaunchOrderList.add(new LaunchOrder(app, + String.format(TRACE_ITERATION, traceCount))); + } + } + } + } else { + assertTrue("Launch order is not valid parameter", false); + } + } + + private void dropCache() { + if (true == mDropCache) { + assertNotNull("Issue in dropping the cache", + getInstrumentation().getUiAutomation() + .executeShellCommand(DROP_CACHE_SCRIPT)); + } + } + private void parseArgs(Bundle args) { mNameToResultKey = new LinkedHashMap(); - mNameToLaunchTime = new HashMap(); + mNameToLaunchTime = new HashMap>(); String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); if (launchIterations != null) { mLaunchIterations = Integer.parseInt(launchIterations); @@ -169,7 +382,7 @@ public class AppLaunch extends InstrumentationTestCase { } mNameToResultKey.put(parts[0], parts[1]); - mNameToLaunchTime.put(parts[0], 0L); + mNameToLaunchTime.put(parts[0], null); } String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS); if (requiredAccounts != null) { @@ -178,7 +391,7 @@ public class AppLaunch extends InstrumentationTestCase { mRequiredAccounts.add(accountType); } } - mSkipInitialLaunch = "true".equals(args.getString(KEY_SKIP_INITIAL_LAUNCH)); + mTrailLaunch = "true".equals(args.getString(KEY_TRIAL_LAUNCH)); } private boolean hasLeanback(Context context) { @@ -222,7 +435,7 @@ public class AppLaunch extends InstrumentationTestCase { } } - private long startApp(String appName, boolean forceStopBeforeLaunch) + private long startApp(String appName, boolean forceStopBeforeLaunch, String launchReason) throws NameNotFoundException, RemoteException { Log.i(TAG, "Starting " + appName); @@ -230,9 +443,10 @@ public class AppLaunch extends InstrumentationTestCase { if (startIntent == null) { Log.w(TAG, "App does not exist: " + appName); mResult.putString(mNameToResultKey.get(appName), "App does not exist"); - return -1; + return -1L; } - AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch); + AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch , + launchReason); Thread t = new Thread(runnable); t.start(); try { @@ -240,21 +454,7 @@ public class AppLaunch extends InstrumentationTestCase { } catch (InterruptedException e) { // ignore } - WaitResult result = runnable.getResult(); - // report error if any of the following is true: - // * launch thread is alive - // * result is not null, but: - // * result is not START_SUCCESS - // * or in case of no force stop, result is not TASK_TO_FRONT either - if (t.isAlive() || (result != null - && ((result.result != ActivityManager.START_SUCCESS) - && (!forceStopBeforeLaunch - && result.result != ActivityManager.START_TASK_TO_FRONT)))) { - Log.w(TAG, "Assuming app " + appName + " crashed."); - reportError(appName, mNameToProcess.get(appName)); - return -1; - } - return result.thisTime; + return runnable.getResult(); } private void checkAccountSignIn() { @@ -337,39 +537,117 @@ public class AppLaunch extends InstrumentationTestCase { + " not found in process list, most likely it is crashed"); } - private class AppLaunchRunnable implements Runnable { - private Intent mLaunchIntent; - private IActivityManager.WaitResult mResult; - private boolean mForceStopBeforeLaunch; + private class LaunchOrder { + private String mApp; + private String mLaunchReason; - public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch) { - mLaunchIntent = intent; - mForceStopBeforeLaunch = forceStopBeforeLaunch; + LaunchOrder(String app,String launchReason){ + mApp = app; + mLaunchReason = launchReason; } - public IActivityManager.WaitResult getResult() { + public String getApp() { + return mApp; + } + + public void setApp(String app) { + mApp = app; + } + + public String getLaunchReason() { + return mLaunchReason; + } + + public void setLaunchReason(String launchReason) { + mLaunchReason = launchReason; + } + } + + private class AppLaunchRunnable implements Runnable { + private Intent mLaunchIntent; + private Long mResult; + private boolean mForceStopBeforeLaunch; + private String mLaunchReason; + + public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch, + String launchReason) { + mLaunchIntent = intent; + mForceStopBeforeLaunch = forceStopBeforeLaunch; + mLaunchReason = launchReason; + } + + public Long getResult() { return mResult; } public void run() { try { String packageName = mLaunchIntent.getComponent().getPackageName(); + String componentName = mLaunchIntent.getComponent().flattenToShortString(); if (mForceStopBeforeLaunch) { mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); } - String mimeType = mLaunchIntent.getType(); - if (mimeType == null && mLaunchIntent.getData() != null - && "content".equals(mLaunchIntent.getData().getScheme())) { - mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(), - UserHandle.USER_CURRENT); + String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName); + if (null != mSimplePerfCmd) { + launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd); } - - mResult = mAm.startActivityAndWait(null, null, mLaunchIntent, mimeType, - null, null, 0, mLaunchIntent.getFlags(), null, null, - UserHandle.USER_CURRENT); + Log.v(TAG, "Final launch cmd:" + launchCmd); + ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation() + .executeShellCommand(launchCmd); + mResult = Long.parseLong(parseLaunchTimeAndWrite(parcelDesc, String.format + ("App Launch :%s %s", + componentName, mLaunchReason)), 10); } catch (RemoteException e) { Log.w(TAG, "Error launching app", e); } } + + /** + * Method to parse the launch time info and write the result to file + * + * @param parcelDesc + * @return + */ + private String parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, String headerInfo) { + String launchTime = "-1"; + boolean launchSuccess = false; + try { + InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor()); + StringBuilder appLaunchOuput = new StringBuilder(); + /* SAMPLE OUTPUT : + Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } + Status: ok + Activity: com.google.android.calculator/com.android.calculator2.Calculator + ThisTime: 357 + TotalTime: 357 + WaitTime: 377 + Complete*/ + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( + inputStream)); + String line = null; + int lineCount = 1; + mBufferedWriter.newLine(); + mBufferedWriter.write(headerInfo); + mBufferedWriter.newLine(); + while ((line = bufferedReader.readLine()) != null) { + if (lineCount == 2 && line.contains(SUCCESS_MESSAGE)) { + launchSuccess = true; + } + if (launchSuccess && lineCount == 4) { + String launchSplit[] = line.split(":"); + launchTime = launchSplit[1].trim(); + } + mBufferedWriter.write(line); + mBufferedWriter.newLine(); + lineCount++; + } + mBufferedWriter.flush(); + inputStream.close(); + } catch (IOException e) { + Log.w(TAG, "Error writing the launch file", e); + } + return launchTime; + } + } }