content: /* Android IMSI-Catcher Detector | (c) AIMSICD Privacy Project
   * -----------------------------------------------------------
   * LICENSE:  http://git.io/vki47 | TERMS:  http://git.io/vki4o
   * -----------------------------------------------------------
   */
  package zz.aimsicd.lite.ui.activities;
  
  import android.content.ClipData;
  import android.content.ClipboardManager;
  import android.content.Intent;
  import android.os.Bundle;
  import android.support.v4.app.NavUtils;
  import android.util.Log;
  import android.view.MenuItem;
  import android.view.View;
  import android.widget.Button;
  import android.widget.ScrollView;
  import android.widget.TextView;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStreamReader;
  
  import io.freefair.android.injection.annotation.InjectView;
  import io.freefair.android.injection.annotation.XmlLayout;
  import io.freefair.android.injection.annotation.XmlMenu;
  import zz.aimsicd.lite.R;
  import zz.aimsicd.lite.utils.Helpers;
  
  
  /**
   *  Description:    This class is providing for the Debug log feature in the swipe menu.
   *                  It reads the last 500 lines from the Logcat ring buffers: main and radio.
   *
   *  Dependencies:
   *                  menu/activity_debug_logs.xml **
   *                  layout/activity_debug_logs.xml
   *                  values/strings.xml
   *  Issues:
   *
   *          [ ]     Are we clearing logcat when starting it? If we are, we miss all previous errors
   *                  if any has occurred. We need to clear the logcat when app starts. Also there
   *                  is no reason to clear it if we only catch the last 500 lines anyway.
   *
   *          [ ]     Add the output of "getprop |sort". But Java CLI processes doesn't handle pipes.
   *                  Try with:  Collections.sort(list, String.CASE_INSENSITIVE_ORDER)
   *
   *          [ ]     Apparently the button for radio log has been added to the strings.xml,
   *                  but never implemented here. We need to add the buffer selector button
   *                  to the top bar, next to email icon button. **
   *
   *  TODO:   [ ]     We should add an XPrivacy button (or automatic) to add XPrivacy filters when used.
   */
  @XmlLayout(R.layout.activity_debug_logs)
  @XmlMenu(R.menu.activity_debug_logs)
  public class DebugLogs extends BaseActivity {
  
      public static final String TAG = "AICDL";
      public static final String mTAG = "XXX";
  
      private LogUpdaterThread logUpdater = null;
      private boolean updateLogs = true;
      private boolean isRadioLogs = true; // Including this, should be a toggle.
  
      @InjectView(R.id.debug_log_view)
      private TextView logView;
  
      @InjectView(R.id.btnClear)
      private Button btnClear;
  
      @InjectView(R.id.btnCopy)
      private Button btnCopy;
  
      @InjectView(zz.aimsicd.lite.R.id.btnStopLogs)
      private Button btnStop;
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          // Show the Up button in the action bar.
          getSupportActionBar().setDisplayHomeAsUpEnabled(true);
  
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  logView.setFocusable(false);
              }
          });
  
          btnClear.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  try {
                      clearLogs();
                  } catch (IOException e) {
                     Log.e(TAG, mTAG + "Error clearing logs", e);
                  }
              }
          });
  
          btnCopy.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                  ClipData cd = ClipData.newPlainText("log", logView.getText());
                  clipboard.setPrimaryClip(cd);
                  Helpers.msgShort(DebugLogs.this, getString(R.string.msg_copied_to_clipboard));
              }
          });
  
          btnStop.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if (updateLogs) {
                      updateLogs = false;
                      btnStop.setText(getString(R.string.btn_start_logs));
                  } else {
                      startLogging();
                  }
              }
          });
  
          /*
          // logcat radio buffer toggle on/off
          btnRadio.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if (isRadioLogs) {
                      isRadioLogs = false;
                      btnRadio.setText(getString(R.string.btn_radio_logs));
                  } else {
                      isRadioLogs = true;
                      btnRadio.setText(getString(R.string.btn_main_logs));
                  }
              }
          });
          */
  
      }
  
      @Override
      protected void onPause() {
          updateLogs = false; // exit the log updater
          super.onPause();
      }
  
      @Override
      protected void onResume() {
          super.onResume();
          startLogging();
      }
  
      private void startLogging() {
          updateLogs = true;
  
          logUpdater = new LogUpdaterThread();
          logUpdater.start();
  
          btnStop.setText(getString(R.string.btn_stop_logs));
      }
  
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          switch (item.getItemId()) {
              case zz.aimsicd.lite.R.id.action_send_logs:
                  sendEmail();
                  return true;
  
              case android.R.id.home:
                  // This ID represents the Home or Up button. In the case of this
                  // activity, the Up button is shown. Use NavUtils to allow users
                  // to navigate up one level in the application structure. For
                  // more details, see the Navigation pattern on Android Design:
                  //
                  // http://developer.android.com/design/patterns/navigation.html#up-vs-back
                  //
                  NavUtils.navigateUpFromSameTask(this);
                  return true;
          }
          return super.onOptionsItemSelected(item);
      }
  
      public void sendEmail() {
          new Thread() {
              @Override
              public void run() {
                  // Send Error Log
                  try {
                      String helpUs = getString(R.string.describe_the_problem_you_had);
                      String log = helpUs + "\n\n" + "GETPROP:" + "\n\n" + getProp() +
                                            "\n\n" + "LOGCAT:" + "\n\n" + getLogs() + "\n\n" + helpUs;
  
                      // show a share intent
                      Intent intent = new Intent(Intent.ACTION_SEND);
                      intent.setType("text/html");
                      // E-Mail address will ONLY be handed out when a DEVELOPER asked for the logs!
                      intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"See GitHub Issues first!"});
                      intent.putExtra(Intent.EXTRA_SUBJECT, "AIMSICD Error Log");
                      intent.putExtra(Intent.EXTRA_TEXT, log);
                      startActivity(Intent.createChooser(intent, "Send Error Log"));
                  } catch (IOException e) {
                     Log.w(TAG, mTAG + "Error reading logs", e);
                  }
              }
          }.start();
      }
  
      /**
       * Read getprop and return the sorted result as a string
       *
       * TODO: Need a way to sort properties for easy reading
       *
       * @return
       * @throws IOException
       */
      public String getProp() throws IOException {
          return runProcess("/system/bin/getprop");
      }
  
      /**
       *  Description:    Read logcat and return as a string
       *
       *  Notes:
       *
       *  1) " *:V" makes log very spammy due to verbose OemRilRequestRaw debug output (AIMSICD_Helpers).
       *          ==> Now disabled!
       *  2) Need to silent some spammy Samsung Galaxy's:     " AbsListView:S PackageInfo:S"
       *  3) Need to silent some Qualcomm QMI:                " RILQ:S"
       *  4) Need to silent some Qualcomm GPS:                " LocSvc_eng:S LocSvc_adapter:S LocSvc_afw:S"
       *  5) "-d" is not necessary when using "-t".
       *  6) Need to silent some spammy HTC's:                "QC-QMI:S AudioPolicyManager:S"
       *  7) Need to silent some spammy XPrivacy items:       "XPrivacy/XRuntime:S Xposed:S"
       *  8) Need to silent even more XPrivacy items:         "XPrivacy/XTelephonyManager:S XPrivacy/XLocationManager:S XPrivacy/XPackageManager:S"
       *
       */
      private String getLogs() throws IOException {
          return runProcess(
              "logcat -t 500 -v brief -b main" +
                      (isRadioLogs ? " -b radio RILQ:S" : "") +
                      " AbsListView:S PackageInfo:S" +
                      " LocSvc_eng:S LocSvc_adapter:S LocSvc_afw:S" +
                      " QC-QMI:S AudioPolicyManager:S" +
                      " XPrivacy/XRuntime:S Xposed:S" +
                      " XPrivacy/XTelephonyManager:S XPrivacy/XLocationManager:S XPrivacy/XPackageManager:S" + " *:D"
          );
      }
  
      /**
       * Run a shell command and return the results
       */
      private String runProcess(String command) throws IOException {
          return runProcess(new String[]{command});
      }
  
      /**
       * Run a shell command and return the results
       *
       * @param command
       * @return
       * @throws IOException
       */
      private String runProcess(String[] command) throws IOException {
          Process process = null;
          if (command.length == 1) {
              process = Runtime.getRuntime().exec(command[0]);
          } else {
              Runtime.getRuntime().exec(command);
          }
  
          BufferedReader bufferedReader = new BufferedReader(
                  new InputStreamReader(process.getInputStream()));
  
          StringBuilder log = new StringBuilder();
          String line;
          while ((line = bufferedReader.readLine()) != null) {
              log.append(line);
              log.append("\n");
          }
          bufferedReader.close();
          return log.toString();
      }
  
      /**
       * Clear logcat
       * @return
       * @throws IOException
       */
      private void clearLogs() throws IOException {
          new Thread() {
              @Override
              public void run() {
                  try {
                      Runtime.getRuntime().exec("logcat -c -b main -b system -b radio -b events");
                  } catch (IOException e) {
                     Log.e(TAG, mTAG + "Error clearing logs", e);
                  }
  
                  runOnUiThread(new Runnable() {
                      @Override
                      public void run() {
                          logView.setText("");
                      }
                  });
              }
          }.start();
      }
  
      class LogUpdaterThread extends Thread {
          @Override
          public void run() {
              while (updateLogs) {
                  try {
                      final String logs = getLogs();
                      if (!logs.equals(logView.getText().toString())) {
                          runOnUiThread(new Runnable() {
                              @Override
                              public void run() {
                                  // update log display
                                  logView.setText(logs);
  
                                  // scroll to the bottom of the log display
                                  final ScrollView scroll = ((ScrollView) logView.getParent());
                                  scroll.post(new Runnable() {
                                      @Override
                                      public void run() {
                                          scroll.fullScroll(View.FOCUS_DOWN);
                                      }
                                  });
                              }
                          });
                      }
                  } catch (IOException e) {
                     Log.w(TAG, mTAG + "Error updating logs", e);
                  }
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                     Log.w(TAG, mTAG + "Thread was interrupted", e);
                  }
              }
          }
      }
  }
  