• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 org.chromium.latency.walt;
18 
19 import static org.chromium.latency.walt.Utils.getBooleanPreference;
20 
21 import android.Manifest;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.content.SharedPreferences;
25 import android.content.pm.PackageManager;
26 import android.hardware.usb.UsbDevice;
27 import android.hardware.usb.UsbManager;
28 import android.media.AudioManager;
29 import android.net.Uri;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.os.Environment;
33 import android.os.Handler;
34 import android.os.StrictMode;
35 import android.util.Log;
36 import android.view.Menu;
37 import android.view.MenuItem;
38 import android.view.View;
39 import android.widget.EditText;
40 import android.widget.Toast;
41 
42 import androidx.annotation.NonNull;
43 import androidx.appcompat.app.AlertDialog;
44 import androidx.appcompat.app.AppCompatActivity;
45 import androidx.appcompat.widget.Toolbar;
46 import androidx.core.app.ActivityCompat;
47 import androidx.core.content.ContextCompat;
48 import androidx.fragment.app.Fragment;
49 import androidx.fragment.app.FragmentManager;
50 import androidx.fragment.app.FragmentTransaction;
51 import androidx.loader.content.Loader;
52 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
53 import androidx.preference.PreferenceManager;
54 
55 import java.io.File;
56 import java.io.FileOutputStream;
57 import java.io.IOException;
58 import java.io.PrintWriter;
59 import java.io.StringWriter;
60 import java.util.Date;
61 import java.util.Locale;
62 
63 import org.chromium.latency.walt.programmer.Programmer;
64 
65 public class MainActivity extends AppCompatActivity {
66     private static final String TAG = "WALT";
67     private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG = 2;
68     private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SYSTRACE = 3;
69     private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG = 4;
70     private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG = 5;
71 
72     private static final String LOG_FILENAME = "qstep_log.txt";
73 
74     private Toolbar toolbar;
75     LocalBroadcastManager broadcastManager;
76     private SimpleLogger logger;
77     private WaltDevice waltDevice;
78     public Menu menu;
79 
80     public Handler handler = new Handler();
81 
82     private Fragment mRobotAutomationFragment;
83 
84 
85     /**
86      * A method to display exceptions on screen. This is very useful because our USB port is taken
87      * and we often need to debug without adb.
88      * Based on this article:
89      * https://trivedihardik.wordpress.com/2011/08/20/how-to-avoid-force-close-error-in-android/
90      */
91     public class LoggingExceptionHandler implements java.lang.Thread.UncaughtExceptionHandler {
92 
93         @Override
uncaughtException(Thread thread, Throwable ex)94         public void uncaughtException(Thread thread, Throwable ex) {
95             StringWriter stackTrace = new StringWriter();
96             ex.printStackTrace(new PrintWriter(stackTrace));
97             String msg = "WALT crashed with the following exception:\n" + stackTrace;
98 
99             // Fire a new activity showing the stack trace
100             Intent intent = new Intent(MainActivity.this, CrashLogActivity.class);
101             intent.putExtra("crash_log", msg);
102             MainActivity.this.startActivity(intent);
103 
104             // Terminate this process
105             android.os.Process.killProcess(android.os.Process.myPid());
106             System.exit(10);
107         }
108     }
109 
110     @Override
onResume()111     protected void onResume() {
112         super.onResume();
113 
114         final UsbDevice usbDevice;
115         Intent intent = getIntent();
116         if (intent != null && intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
117             setIntent(null); // done with the intent
118             usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
119         } else {
120             usbDevice = null;
121         }
122 
123         // Connect and sync clocks, but a bit later as it takes time
124         handler.postDelayed(new Runnable() {
125             @Override
126             public void run() {
127                 if (usbDevice == null) {
128                     waltDevice.connect();
129                 } else {
130                     waltDevice.connect(usbDevice);
131                 }
132             }
133         }, 1000);
134 
135         if (intent != null && AutoRunFragment.TEST_ACTION.equals(intent.getAction())) {
136             getSupportFragmentManager().popBackStack("Automated Test",
137                     FragmentManager.POP_BACK_STACK_INCLUSIVE);
138             Fragment autoRunFragment = new AutoRunFragment();
139             autoRunFragment.setArguments(intent.getExtras());
140             switchScreen(autoRunFragment, "Automated Test");
141         }
142 
143         // Handle robot automation originating from adb shell am
144         if (intent != null && Intent.ACTION_SEND.equals(intent.getAction())) {
145             Log.e(TAG, "Received Intent: " + intent.toString());
146             String test = intent.getStringExtra("StartTest");
147             if (test != null) {
148                 Log.e(TAG, "Extras \"StartTest\" = " + test);
149                 if ("TapLatencyTest".equals(test)) {
150                     mRobotAutomationFragment = new TapLatencyFragment();
151                     switchScreen(mRobotAutomationFragment, "Tap Latency");
152                 } else if ("ScreenResponseTest".equals(test)) {
153                     mRobotAutomationFragment = new ScreenResponseFragment();
154                     switchScreen(mRobotAutomationFragment, "Screen Response");
155                 } else if ("DragLatencyTest".equals(test)) {
156                     mRobotAutomationFragment = new DragLatencyFragment();
157                     switchScreen(mRobotAutomationFragment, "Drag Latency");
158                 }
159             }
160 
161             String robotEvent = intent.getStringExtra("RobotAutomationEvent");
162             if (robotEvent != null && mRobotAutomationFragment != null) {
163                 Log.e(TAG, "Received robot automation event=\"" + robotEvent + "\", Fragment = " +
164                         mRobotAutomationFragment);
165                 // Writing and clearing the log is not fragment-specific, so handle them here.
166                 if (robotEvent.equals(RobotAutomationListener.WRITE_LOG_EVENT)) {
167                     attemptSaveLog();
168                 } else if (robotEvent.equals(RobotAutomationListener.CLEAR_LOG_EVENT)) {
169                     attemptClearLog();
170                 } else {
171                     // All other robot automation events are forwarded to the current fragment.
172                     ((RobotAutomationListener) mRobotAutomationFragment)
173                             .onRobotAutomationEvent(robotEvent);
174                 }
175             }
176         }
177     }
178 
179     @Override
onNewIntent(Intent intent)180     protected void onNewIntent(Intent intent) {
181         super.onNewIntent(intent);
182         setIntent(intent);
183     }
184 
185     @Override
onCreate(Bundle savedInstanceState)186     protected void onCreate(Bundle savedInstanceState) {
187         super.onCreate(savedInstanceState);
188         Thread.setDefaultUncaughtExceptionHandler(new LoggingExceptionHandler());
189         setContentView(R.layout.activity_main);
190 
191         // App bar
192         toolbar = (Toolbar) findViewById(R.id.toolbar_main);
193         setSupportActionBar(toolbar);
194         getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
195             @Override
196             public void onBackStackChanged() {
197                 int stackTopIndex = getSupportFragmentManager().getBackStackEntryCount() - 1;
198                 if (stackTopIndex >= 0) {
199                     toolbar.setTitle(getSupportFragmentManager().getBackStackEntryAt(stackTopIndex).getName());
200                 } else {
201                     toolbar.setTitle(R.string.app_name);
202                     getSupportActionBar().setDisplayHomeAsUpEnabled(false);
203                     // Disable fullscreen mode
204                     getSupportActionBar().show();
205                     getWindow().getDecorView().setSystemUiVisibility(0);
206                 }
207             }
208         });
209 
210         waltDevice = WaltDevice.getInstance(this);
211 
212         // Create front page fragment
213         FrontPageFragment frontPageFragment = new FrontPageFragment();
214         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
215         transaction.add(R.id.fragment_container, frontPageFragment);
216         transaction.commit();
217 
218         logger = SimpleLogger.getInstance(this);
219         broadcastManager = LocalBroadcastManager.getInstance(this);
220 
221         // Add basic version and device info to the log
222         logger.log(String.format(Locale.US, "WALT v%s  (versionCode=%d)",
223                 BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
224         logger.log("WALT protocol version " + WaltDevice.PROTOCOL_VERSION);
225         logger.log("DEVICE INFO:");
226         logger.log("  " + Build.FINGERPRINT);
227         logger.log("  Build.SDK_INT=" + Build.VERSION.SDK_INT);
228         logger.log("  os.version=" + System.getProperty("os.version"));
229 
230         // Set volume buttons to control media volume
231         setVolumeControlStream(AudioManager.STREAM_MUSIC);
232         requestSystraceWritePermission();
233         // Allow network operations on the main thread
234         StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
235         StrictMode.setThreadPolicy(policy);
236     }
237 
238     @Override
onCreateOptionsMenu(Menu menu)239     public boolean onCreateOptionsMenu(Menu menu) {
240         // Inflate the menu; this adds items to the action bar if it is present.
241         getMenuInflater().inflate(R.menu.menu_main, menu);
242         this.menu = menu;
243         return true;
244     }
245 
toast(String msg)246     public void toast(String msg) {
247         logger.log(msg);
248         Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
249     }
250 
251     @Override
onSupportNavigateUp()252     public boolean onSupportNavigateUp() {
253         // Go back when the back or up button on toolbar is clicked
254         getSupportFragmentManager().popBackStack();
255         return true;
256     }
257 
258     @Override
onOptionsItemSelected(MenuItem item)259     public boolean onOptionsItemSelected(MenuItem item) {
260         // Handle action bar item clicks here. The action bar will
261         // automatically handle clicks on the Home/Up button, so long
262         // as you specify a parent activity in AndroidManifest.xml.
263 
264         Log.i(TAG, "Toolbar button: " + item.getTitle());
265 
266         switch (item.getItemId()) {
267             case R.id.action_help:
268                 return true;
269             case R.id.action_share:
270                 attemptSaveAndShareLog();
271                 return true;
272             case R.id.action_upload:
273                 showUploadLogDialog();
274                 return true;
275             default:
276                 return super.onOptionsItemSelected(item);
277         }
278     }
279 
280     ////////////////////////////////////////////////////////////////////////////////////////////////
281     // Handlers for main menu clicks
282     ////////////////////////////////////////////////////////////////////////////////////////////////
283 
switchScreen(Fragment newFragment, String title)284     private void switchScreen(Fragment newFragment, String title) {
285         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
286         toolbar.setTitle(title);
287         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
288         transaction.replace(R.id.fragment_container, newFragment);
289         transaction.addToBackStack(title);
290         transaction.commit();
291     }
292 
onClickClockSync(View view)293     public void onClickClockSync(View view) {
294         DiagnosticsFragment diagnosticsFragment = new DiagnosticsFragment();
295         switchScreen(diagnosticsFragment, "Diagnostics");
296     }
297 
onClickTapLatency(View view)298     public void onClickTapLatency(View view) {
299         TapLatencyFragment newFragment = new TapLatencyFragment();
300         requestSystraceWritePermission();
301         switchScreen(newFragment, "Tap Latency");
302     }
303 
onClickScreenResponse(View view)304     public void onClickScreenResponse(View view) {
305         ScreenResponseFragment newFragment = new ScreenResponseFragment();
306         requestSystraceWritePermission();
307         switchScreen(newFragment, "Screen Response");
308     }
309 
onClickAudio(View view)310     public void onClickAudio(View view) {
311         AudioFragment newFragment = new AudioFragment();
312         switchScreen(newFragment, "Audio Latency");
313     }
314 
onClickMIDI(View view)315     public void onClickMIDI(View view) {
316         if (MidiFragment.hasMidi(this)) {
317             MidiFragment newFragment = new MidiFragment();
318             switchScreen(newFragment, "MIDI Latency");
319         } else {
320             toast("This device does not support MIDI");
321         }
322     }
323 
onClickDragLatency(View view)324     public void onClickDragLatency(View view) {
325         DragLatencyFragment newFragment = new DragLatencyFragment();
326         switchScreen(newFragment, "Drag Latency");
327     }
328 
onClickAccelerometer(View view)329     public void onClickAccelerometer(View view) {
330         AccelerometerFragment newFragment = new AccelerometerFragment();
331         switchScreen(newFragment, "Accelerometer Latency");
332     }
333 
onClickOpenLog(View view)334     public void onClickOpenLog(View view) {
335         LogFragment logFragment = new LogFragment();
336         // menu.findItem(R.id.action_help).setVisible(false);
337         switchScreen(logFragment, "Log");
338     }
339 
onClickOpenAbout(View view)340     public void onClickOpenAbout(View view) {
341         AboutFragment aboutFragment = new AboutFragment();
342         switchScreen(aboutFragment, "About");
343     }
344 
onClickOpenSettings(View view)345     public void onClickOpenSettings(View view) {
346         SettingsFragment settingsFragment = new SettingsFragment();
347         switchScreen(settingsFragment, "Settings");
348     }
349 
350     ////////////////////////////////////////////////////////////////////////////////////////////////
351     // Handlers for diagnostics menu clicks
352     ////////////////////////////////////////////////////////////////////////////////////////////////
onClickReconnect(View view)353     public void onClickReconnect(View view) {
354         waltDevice.connect();
355     }
356 
onClickPing(View view)357     public void onClickPing(View view) {
358         try {
359             waltDevice.ping();
360         } catch (IOException e) {
361             logger.log("Error sending ping: " + e.getMessage());
362         }
363     }
364 
onClickStartListener(View view)365     public void onClickStartListener(View view) {
366         if (waltDevice.isListenerStopped()) {
367             try {
368                 waltDevice.startListener();
369             } catch (IOException e) {
370                 logger.log("Error starting USB listener: " + e.getMessage());
371             }
372         } else {
373             waltDevice.stopListener();
374         }
375     }
376 
onClickSync(View view)377     public void onClickSync(View view) {
378         try {
379             waltDevice.syncClock();
380         } catch (IOException e) {
381             logger.log("Error syncing clocks: " + e.getMessage());
382         }
383     }
384 
onClickCheckDrift(View view)385     public void onClickCheckDrift(View view) {
386         waltDevice.checkDrift();
387     }
388 
onClickProgram(View view)389     public void onClickProgram(View view) {
390         if (waltDevice.isConnected()) {
391             // show dialog telling user to first press white button
392             final AlertDialog dialog = new AlertDialog.Builder(this)
393                 .setTitle("Press white button")
394                 .setMessage("Please press the white button on the WALT device.")
395                 .setCancelable(false)
396                 .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
397                     @Override
398                     public void onClick(DialogInterface dialog, int which) {}
399                 }).show();
400 
401             waltDevice.setConnectionStateListener(new WaltConnection.ConnectionStateListener() {
402                 @Override
403                 public void onConnect() {}
404 
405                 @Override
406                 public void onDisconnect() {
407                     dialog.cancel();
408                     handler.postDelayed(new Runnable() {
409                         @Override
410                         public void run() {
411                             new Programmer(MainActivity.this).program();
412                         }
413                     }, 1000);
414                 }
415             });
416         } else {
417             new Programmer(this).program();
418         }
419     }
420 
attemptSaveAndShareLog()421     private void attemptSaveAndShareLog() {
422         int currentPermission = ContextCompat.checkSelfPermission(this,
423                 Manifest.permission.WRITE_EXTERNAL_STORAGE);
424         if (currentPermission == PackageManager.PERMISSION_GRANTED) {
425             String filePath = saveLogToFile();
426             shareLogFile(filePath);
427         } else {
428             ActivityCompat.requestPermissions(this,
429                     new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
430                     PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG);
431         }
432     }
433 
attemptSaveLog()434     private void attemptSaveLog() {
435         int currentPermission = ContextCompat.checkSelfPermission(this,
436                 Manifest.permission.WRITE_EXTERNAL_STORAGE);
437         if (currentPermission == PackageManager.PERMISSION_GRANTED) {
438             saveLogToFile();
439         } else {
440             ActivityCompat.requestPermissions(this,
441                     new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
442                     PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG);
443         }
444     }
445 
attemptClearLog()446     private void attemptClearLog() {
447         int currentPermission = ContextCompat.checkSelfPermission(this,
448                 Manifest.permission.WRITE_EXTERNAL_STORAGE);
449         if (currentPermission == PackageManager.PERMISSION_GRANTED) {
450             clearLogFile();
451         } else {
452             ActivityCompat.requestPermissions(this,
453                     new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
454                     PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG);
455         }
456     }
457 
458     @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)459     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
460         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
461         final boolean isPermissionGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
462         if (!isPermissionGranted) {
463             logger.log("Could not get permission to write file to storage");
464             return;
465         }
466         switch (requestCode) {
467             case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG:
468                 attemptSaveAndShareLog();
469                 break;
470             case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG:
471                 attemptSaveLog();
472                 break;
473             case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG:
474                 attemptClearLog();
475                 break;
476         }
477     }
478 
saveLogToFile()479     public String saveLogToFile() {
480 
481         // Save to file to later fire an Intent.ACTION_SEND
482         // This allows to either send the file as email attachment
483         // or upload it to Drive.
484 
485         // The permissions for attachments are a mess, writing world readable files
486         // is frowned upon, but deliberately giving permissions as part of the intent is
487         // way too cumbersome.
488 
489         // A reasonable world readable location,on many phones it's /storage/emulated/Documents
490         // TODO: make this location configurable?
491         File path = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
492         File file = null;
493         FileOutputStream outStream = null;
494 
495         try {
496             if (!path.exists()) {
497                 path.mkdirs();
498             }
499             file = new File(path, LOG_FILENAME);
500             logger.log("Saving log to: " + file + " at " + new Date());
501 
502             outStream = new FileOutputStream(file);
503             outStream.write(logger.getLogText().getBytes());
504 
505             outStream.close();
506             logger.log("Log saved");
507         } catch (Exception e) {
508             e.printStackTrace();
509             logger.log("Failed to write log: " + e.getMessage());
510         }
511         return file.getPath();
512     }
513 
clearLogFile()514     public void clearLogFile() {
515         File path = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
516         try {
517             File file = new File(path, LOG_FILENAME);
518             file.delete();
519         } catch (Exception e) {
520             e.printStackTrace();
521             logger.log("Failed to clear log: " + e.getMessage());
522         }
523     }
524 
shareLogFile(String filepath)525     public void shareLogFile(String filepath) {
526         File file = new File(filepath);
527         logger.log("Firing Intent.ACTION_SEND for file:");
528         logger.log(file.getPath());
529 
530         Intent i = new Intent(Intent.ACTION_SEND);
531         i.setType("text/plain");
532 
533         i.putExtra(Intent.EXTRA_SUBJECT, "WALT log");
534         i.putExtra(Intent.EXTRA_TEXT, "Attaching log file " + file.getPath());
535         i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
536 
537         try {
538             startActivity(Intent.createChooser(i, "Send mail..."));
539         } catch (android.content.ActivityNotFoundException ex) {
540             toast("There are no email clients installed.");
541         }
542     }
543 
startsWithHttp(String url)544     private static boolean startsWithHttp(String url) {
545         return url.toLowerCase(Locale.getDefault()).startsWith("http://") ||
546             url.toLowerCase(Locale.getDefault()).startsWith("https://");
547     }
548 
showUploadLogDialog()549     private void showUploadLogDialog() {
550         final AlertDialog dialog = new AlertDialog.Builder(this)
551                 .setTitle("Upload log to URL")
552                 .setView(R.layout.dialog_upload)
553                 .setPositiveButton("Upload", new DialogInterface.OnClickListener() {
554                     @Override
555                     public void onClick(DialogInterface dialog, int which) {}
556                 })
557                 .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
558                     @Override
559                     public void onClick(DialogInterface dialog, int which) {}
560                 })
561                 .show();
562         final EditText editText = (EditText) dialog.findViewById(R.id.edit_text);
563         editText.setText(Utils.getStringPreference(
564                 MainActivity.this, R.string.preference_log_url, ""));
565         dialog.getButton(AlertDialog.BUTTON_POSITIVE).
566                 setOnClickListener(new View.OnClickListener() {
567             @Override
568             public void onClick(View v) {
569                 View progress = dialog.findViewById(R.id.progress_bar);
570                 String urlString = editText.getText().toString();
571                 if (!startsWithHttp(urlString)) {
572                     urlString = "http://" + urlString;
573                 }
574                 editText.setVisibility(View.GONE);
575                 progress.setVisibility(View.VISIBLE);
576                 LogUploader uploader = new LogUploader(MainActivity.this, urlString);
577                 final String finalUrlString = urlString;
578                 uploader.registerListener(1, new Loader.OnLoadCompleteListener<Integer>() {
579                     @Override
580                     public void onLoadComplete(Loader<Integer> loader, Integer data) {
581                         dialog.cancel();
582                         if (data == -1) {
583                             Toast.makeText(MainActivity.this,
584                                     "Failed to upload log", Toast.LENGTH_SHORT).show();
585                             return;
586                         } else if (data / 100 == 2) {
587                             Toast.makeText(MainActivity.this,
588                                     "Log successfully uploaded", Toast.LENGTH_SHORT).show();
589                         } else {
590                             Toast.makeText(MainActivity.this,
591                                     "Failed to upload log. Server returned status code " + data,
592                                     Toast.LENGTH_SHORT).show();
593                         }
594                         SharedPreferences preferences = PreferenceManager
595                                 .getDefaultSharedPreferences(MainActivity.this);
596                         preferences.edit().putString(
597                                 getString(R.string.preference_log_url), finalUrlString).apply();
598                     }
599                 });
600                 uploader.startUpload();
601             }
602         });
603     }
604 
requestSystraceWritePermission()605     private void requestSystraceWritePermission() {
606         if (getBooleanPreference(this, R.string.preference_systrace, true)) {
607             int currentPermission = ContextCompat.checkSelfPermission(this,
608                     Manifest.permission.WRITE_EXTERNAL_STORAGE);
609             if (currentPermission != PackageManager.PERMISSION_GRANTED) {
610                 ActivityCompat.requestPermissions(this,
611                         new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
612                         PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SYSTRACE);
613             }
614         }
615     }
616 
617 }
618