diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 06f9a0eae..c178f1b5b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -168,8 +168,8 @@ android:windowSoftInputMode="stateUnchanged" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - diff --git a/build.gradle b/build.gradle index dc6d16cec..c5c1595e9 100644 --- a/build.gradle +++ b/build.gradle @@ -74,9 +74,6 @@ dependencies { compile 'com.google.android.exoplayer:exoplayer:r2.3.1' - compile('org.whispersystems:libpastelog:1.1.2') { - exclude group: 'com.squareup.okhttp3', module: 'okhttp' - } compile 'org.whispersystems:signal-service-android:2.7.6' compile 'com.journeyapps:zxing-android-embedded:3.4.0' @@ -162,7 +159,6 @@ dependencyVerification { 'com.google.android.gms:play-services-places:abf3a4a3b146ec7e6e753be62775e512868cf37d6f88ffe2d81167b33b57132b', 'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e', 'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718', - 'org.whispersystems:libpastelog:fe56b4db9ec743c8b565e3e4caa9228fafe132dc0bf82000d6e359b97a81177c', 'org.whispersystems:signal-service-android:823eed29e64fb0aa30d2078cb5ec0245e2a0713a4028121329c5c28788ef27f8', 'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', diff --git a/res/layout/fragment_view_log.xml b/res/layout/fragment_view_log.xml new file mode 100644 index 000000000..6bbd9a197 --- /dev/null +++ b/res/layout/fragment_view_log.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/log_submit_activity.xml b/res/layout/log_view_activity.xml similarity index 100% rename from res/layout/log_submit_activity.xml rename to res/layout/log_view_activity.xml diff --git a/res/menu/view_log.xml b/res/menu/view_log.xml new file mode 100644 index 000000000..0fab81912 --- /dev/null +++ b/res/menu/view_log.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 45537b12e..1d48a2072 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1526,5 +1526,7 @@ Contact Encryption Edit name + View log + View log diff --git a/res/xml/preferences_advanced.xml b/res/xml/preferences_advanced.xml index 4a70e139d..43d1f5983 100644 --- a/res/xml/preferences_advanced.xml +++ b/res/xml/preferences_advanced.xml @@ -20,7 +20,7 @@ - + diff --git a/src/org/thoughtcrime/securesms/LogSubmitActivity.java b/src/org/thoughtcrime/securesms/LogSubmitActivity.java deleted file mode 100644 index e7d1a4d7d..000000000 --- a/src/org/thoughtcrime/securesms/LogSubmitActivity.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.thoughtcrime.securesms; - -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.FragmentTransaction; -import android.util.Log; -import android.view.MenuItem; -import android.widget.Toast; - -import org.thoughtcrime.securesms.util.DynamicTheme; -import org.whispersystems.libpastelog.SubmitLogFragment; - -/** - * Activity for submitting logcat logs to a pastebin service. - */ -public class LogSubmitActivity extends BaseActionBarActivity implements SubmitLogFragment.OnLogSubmittedListener { - - private static final String TAG = LogSubmitActivity.class.getSimpleName(); - private DynamicTheme dynamicTheme = new DynamicTheme(); - - @Override - protected void onCreate(Bundle icicle) { - dynamicTheme.onCreate(this); - super.onCreate(icicle); - setContentView(R.layout.log_submit_activity); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - SubmitLogFragment fragment = SubmitLogFragment.newInstance(); - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.fragment_container, fragment); - transaction.commit(); - } - - @Override - protected void onResume() { - dynamicTheme.onResume(this); - super.onResume(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - } - - return false; - } - - @Override - public void onSuccess() { - Toast.makeText(getApplicationContext(), R.string.log_submit_activity__thanks, Toast.LENGTH_LONG).show(); - finish(); - } - - @Override - public void onFailure() { - Toast.makeText(getApplicationContext(), R.string.log_submit_activity__log_fetch_failed, Toast.LENGTH_LONG).show(); - finish(); - } - - @Override - public void onCancel() { - finish(); - } - - @Override - public void startActivity(Intent intent) { - try { - super.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(TAG, e); - Toast.makeText(this, R.string.log_submit_activity__no_browser_installed, Toast.LENGTH_LONG).show(); - } - } -} diff --git a/src/org/thoughtcrime/securesms/LogViewActivity.java b/src/org/thoughtcrime/securesms/LogViewActivity.java new file mode 100644 index 000000000..bf50d8f63 --- /dev/null +++ b/src/org/thoughtcrime/securesms/LogViewActivity.java @@ -0,0 +1,74 @@ +package org.thoughtcrime.securesms; + +import android.os.Bundle; +import android.support.v4.app.FragmentTransaction; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.Toast; + +import org.thoughtcrime.securesms.util.DynamicLanguage; +import org.thoughtcrime.securesms.util.DynamicTheme; +import org.thoughtcrime.securesms.util.Util; + +public class LogViewActivity extends PassphraseRequiredActionBarActivity { + + private static final String TAG = LogViewActivity.class.getSimpleName(); + + private final DynamicTheme dynamicTheme = new DynamicTheme(); + private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); + + LogViewFragment logViewFragment; + + @Override + protected void onPreCreate() { + dynamicTheme.onCreate(this); + dynamicLanguage.onCreate(this); + } + + @Override + protected void onCreate(Bundle icicle, boolean ready) { + setContentView(R.layout.log_view_activity); + logViewFragment = LogViewFragment.newInstance(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment_container, logViewFragment); + transaction.commit(); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + protected void onResume() { + super.onResume(); + dynamicTheme.onResume(this); + dynamicLanguage.onResume(this); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuInflater inflater = this.getMenuInflater(); + menu.clear(); + + inflater.inflate(R.menu.view_log, menu); + + super.onPrepareOptionsMenu(menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + super.onOptionsItemSelected(item); + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + + case R.id.copy_log_to_clipboard: + Util.writeTextToClipboard(this, logViewFragment.getLogText()); + Toast.makeText(getApplicationContext(), R.string.done, Toast.LENGTH_SHORT).show(); + return true; + } + + return false; + } +} diff --git a/src/org/thoughtcrime/securesms/LogViewFragment.java b/src/org/thoughtcrime/securesms/LogViewFragment.java new file mode 100644 index 000000000..7a8088d3c --- /dev/null +++ b/src/org/thoughtcrime/securesms/LogViewFragment.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2014 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.thoughtcrime.securesms; + +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.thoughtcrime.securesms.util.Scrubber; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.util.Locale; + +public class LogViewFragment extends Fragment { + private static final String TAG = LogViewFragment.class.getSimpleName(); + + private EditText logPreview; + + public static LogViewFragment newInstance() + { + LogViewFragment fragment = new LogViewFragment(); + + return fragment; + } + + public LogViewFragment() { } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_view_log, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + logPreview = (EditText) getView().findViewById(R.id.log_preview); + new PopulateLogcatAsyncTask(getActivity()).execute(); + } + + public String getLogText() { + return logPreview==null? "null" : logPreview.getText().toString(); + } + + private static String grabLogcat() { + try { + final Process process = Runtime.getRuntime().exec("logcat -d"); + final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); + final StringBuilder log = new StringBuilder(); + final String separator = System.getProperty("line.separator"); + + String line; + while ((line = bufferedReader.readLine()) != null) { + log.append(line); + log.append(separator); + } + return log.toString(); + } catch (IOException ioe) { + Log.w(TAG, "IOException when trying to read logcat.", ioe); + return null; + } + } + + private class PopulateLogcatAsyncTask extends AsyncTask { + private WeakReference weakContext; + + public PopulateLogcatAsyncTask(Context context) { + this.weakContext = new WeakReference<>(context); + } + + @Override + protected String doInBackground(Void... voids) { + Context context = weakContext.get(); + if (context == null) return null; + + return buildDescription(context) + "\n" + new Scrubber().scrub(grabLogcat()); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + logPreview.setText(R.string.one_moment); + } + + @Override + protected void onPostExecute(String logcat) { + super.onPostExecute(logcat); + if (TextUtils.isEmpty(logcat)) { + // the log is in english, so it is fine if some of explaining strings are in english as well + logPreview.setText("Could not read the log on your device. You can still use ADB to get a debug log instead."); + return; + } + logPreview.setText(logcat); + } + } + + private static long asMegs(long bytes) { + return bytes / 1048576L; + } + + public static String getMemoryUsage(Context context) { + Runtime info = Runtime.getRuntime(); + info.totalMemory(); + return String.format(Locale.ENGLISH, "%dM (%.2f%% free, %dM max)", + asMegs(info.totalMemory()), + (float)info.freeMemory() / info.totalMemory() * 100f, + asMegs(info.maxMemory())); + } + + @TargetApi(VERSION_CODES.KITKAT) + public static String getMemoryClass(Context context) { + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + String lowMem = ""; + + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice()) { + lowMem = ", low-mem device"; + } + return activityManager.getMemoryClass() + lowMem; + } + + private static String buildDescription(Context context) { + final PackageManager pm = context.getPackageManager(); + final StringBuilder builder = new StringBuilder(); + + + builder.append("Device : ") + .append(Build.MANUFACTURER).append(" ") + .append(Build.MODEL).append(" (") + .append(Build.PRODUCT).append(")\n"); + builder.append("Android : ").append(VERSION.RELEASE).append(" (") + .append(VERSION.INCREMENTAL).append(", ") + .append(Build.DISPLAY).append(")\n"); + builder.append("Memory : ").append(getMemoryUsage(context)).append("\n"); + builder.append("Memclass: ").append(getMemoryClass(context)).append("\n"); + builder.append("OS Host : ").append(Build.HOST).append("\n"); + builder.append("App : "); + try { + builder.append(pm.getApplicationLabel(pm.getApplicationInfo(context.getPackageName(), 0))) + .append(" ") + .append(pm.getPackageInfo(context.getPackageName(), 0).versionName) + .append("\n"); + } catch (PackageManager.NameNotFoundException nnfe) { + builder.append("Unknown\n"); + } + + return builder.toString(); + } +} diff --git a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index 429f31a01..396e234df 100644 --- a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -19,7 +19,7 @@ import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEventCenter; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; -import org.thoughtcrime.securesms.LogSubmitActivity; +import org.thoughtcrime.securesms.LogViewActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.ApplicationDcContext; import org.thoughtcrime.securesms.connect.DcHelper; @@ -62,8 +62,8 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment Preference manageKeys = this.findPreference("pref_manage_keys"); manageKeys.setOnPreferenceClickListener(new ManageKeysListener()); - Preference submitDebugLog = this.findPreference("pref_submit_debug_logs"); - submitDebugLog.setOnPreferenceClickListener(new SubmitDebugLogListener()); + Preference submitDebugLog = this.findPreference("pref_view_log"); + submitDebugLog.setOnPreferenceClickListener(new ViewLogListener()); submitDebugLog.setSummary(getVersion(getActivity())); } @@ -114,10 +114,10 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment } } - private class SubmitDebugLogListener implements Preference.OnPreferenceClickListener { + private class ViewLogListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { - final Intent intent = new Intent(getActivity(), LogSubmitActivity.class); + final Intent intent = new Intent(getActivity(), LogViewActivity.class); startActivity(intent); return true; } diff --git a/src/org/thoughtcrime/securesms/util/Scrubber.java b/src/org/thoughtcrime/securesms/util/Scrubber.java new file mode 100644 index 000000000..ce85f1402 --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/Scrubber.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.thoughtcrime.securesms.util; + +import android.util.Log; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Scrub data for possibly sensitive information + */ +public class Scrubber { + private static final String TAG = Scrubber.class.getSimpleName(); + private static final Pattern E164_PATTERN = Pattern.compile("\\+\\d{10,15}"); + + private static final Pattern[] DEFAULTS = new Pattern[] { + E164_PATTERN + }; + + private final Pattern[] patterns; + public Scrubber(Pattern... patterns) { + this.patterns = patterns; + } + + public Scrubber() { + this(DEFAULTS); + } + + public String scrub(final String in) { + Log.d(TAG, "scrubbing input"); + String out = in; + for (Pattern pattern : patterns) { + Matcher matcher = pattern.matcher(out); + while (matcher.find()) { + + StringBuilder builder = new StringBuilder(out.substring(0, matcher.start())); + final String censored = matcher.group().substring(0,1) + + new String(new char[matcher.group().length()-3]).replace("\0", "*") + + matcher.group().substring(matcher.group().length()-2); + builder.append(censored); + builder.append(out.substring(matcher.end())); + Log.i(TAG, "replacing a match on /" + pattern.toString() + "/ => " + censored); + out = builder.toString(); + } + } + return out; + } +} diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index e0c076365..ada510fd9 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -36,10 +36,12 @@ import android.text.SpannableString; import android.text.TextUtils; import android.text.style.StyleSpan; import android.util.Log; +import android.widget.Toast; import com.google.android.mms.pdu_alt.EncodedStringValue; import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.ComposeText; import org.thoughtcrime.securesms.database.Address; @@ -332,9 +334,12 @@ public class Util { } public static void writeTextToClipboard(@NonNull Context context, @NonNull String text) { - { + try { ClipboardManager clipboardManager = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE); - clipboardManager.setPrimaryClip(ClipData.newPlainText("Safety numbers", text)); + clipboardManager.setPrimaryClip(ClipData.newPlainText(context.getString(R.string.app_name), text)); + } + catch(Exception e) { + e.printStackTrace(); } }