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