mobile/android/base/CrashReporter.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: Java; tab-width: 20; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko;
michael@0 7
michael@0 8 import java.util.HashMap;
michael@0 9 import java.util.Map;
michael@0 10 import java.io.BufferedReader;
michael@0 11 import java.io.File;
michael@0 12 import java.io.FileInputStream;
michael@0 13 import java.io.FileOutputStream;
michael@0 14 import java.io.FileReader;
michael@0 15 import java.io.InputStreamReader;
michael@0 16 import java.io.IOException;
michael@0 17 import java.io.OutputStream;
michael@0 18 import java.net.HttpURLConnection;
michael@0 19 import java.net.URL;
michael@0 20 import java.nio.channels.Channels;
michael@0 21 import java.nio.channels.FileChannel;
michael@0 22 import java.util.zip.GZIPOutputStream;
michael@0 23
michael@0 24 import android.app.Activity;
michael@0 25 import android.app.AlertDialog;
michael@0 26 import android.app.ProgressDialog;
michael@0 27 import android.content.DialogInterface;
michael@0 28 import android.content.Intent;
michael@0 29 import android.content.SharedPreferences;
michael@0 30 import android.os.Build;
michael@0 31 import android.os.Bundle;
michael@0 32 import android.os.Handler;
michael@0 33 import android.text.TextUtils;
michael@0 34 import android.util.Log;
michael@0 35 import android.view.View;
michael@0 36 import android.widget.CheckBox;
michael@0 37 import android.widget.CompoundButton;
michael@0 38 import android.widget.EditText;
michael@0 39
michael@0 40 public class CrashReporter extends Activity
michael@0 41 {
michael@0 42 private static final String LOGTAG = "GeckoCrashReporter";
michael@0 43
michael@0 44 private static final String PASSED_MINI_DUMP_KEY = "minidumpPath";
michael@0 45 private static final String MINI_DUMP_PATH_KEY = "upload_file_minidump";
michael@0 46 private static final String PAGE_URL_KEY = "URL";
michael@0 47 private static final String NOTES_KEY = "Notes";
michael@0 48 private static final String SERVER_URL_KEY = "ServerURL";
michael@0 49
michael@0 50 private static final String CRASH_REPORT_SUFFIX = "/mozilla/Crash Reports/";
michael@0 51 private static final String PENDING_SUFFIX = CRASH_REPORT_SUFFIX + "pending";
michael@0 52 private static final String SUBMITTED_SUFFIX = CRASH_REPORT_SUFFIX + "submitted";
michael@0 53
michael@0 54 private static final String PREFS_SEND_REPORT = "sendReport";
michael@0 55 private static final String PREFS_INCLUDE_URL = "includeUrl";
michael@0 56 private static final String PREFS_ALLOW_CONTACT = "allowContact";
michael@0 57 private static final String PREFS_CONTACT_EMAIL = "contactEmail";
michael@0 58
michael@0 59 private Handler mHandler;
michael@0 60 private ProgressDialog mProgressDialog;
michael@0 61 private File mPendingMinidumpFile;
michael@0 62 private File mPendingExtrasFile;
michael@0 63 private HashMap<String, String> mExtrasStringMap;
michael@0 64
michael@0 65 private boolean moveFile(File inFile, File outFile) {
michael@0 66 Log.i(LOGTAG, "moving " + inFile + " to " + outFile);
michael@0 67 if (inFile.renameTo(outFile))
michael@0 68 return true;
michael@0 69 try {
michael@0 70 outFile.createNewFile();
michael@0 71 Log.i(LOGTAG, "couldn't rename minidump file");
michael@0 72 // so copy it instead
michael@0 73 FileChannel inChannel = new FileInputStream(inFile).getChannel();
michael@0 74 FileChannel outChannel = new FileOutputStream(outFile).getChannel();
michael@0 75 long transferred = inChannel.transferTo(0, inChannel.size(), outChannel);
michael@0 76 inChannel.close();
michael@0 77 outChannel.close();
michael@0 78
michael@0 79 if (transferred > 0)
michael@0 80 inFile.delete();
michael@0 81 } catch (Exception e) {
michael@0 82 Log.e(LOGTAG, "exception while copying minidump file: ", e);
michael@0 83 return false;
michael@0 84 }
michael@0 85 return true;
michael@0 86 }
michael@0 87
michael@0 88 private void doFinish() {
michael@0 89 if (mHandler != null) {
michael@0 90 mHandler.post(new Runnable() {
michael@0 91 @Override
michael@0 92 public void run() {
michael@0 93 finish();
michael@0 94 }
michael@0 95 });
michael@0 96 }
michael@0 97 }
michael@0 98
michael@0 99 @Override
michael@0 100 public void finish() {
michael@0 101 try {
michael@0 102 if (mProgressDialog.isShowing()) {
michael@0 103 mProgressDialog.dismiss();
michael@0 104 }
michael@0 105 } catch (Exception e) {
michael@0 106 Log.e(LOGTAG, "exception while closing progress dialog: ", e);
michael@0 107 }
michael@0 108 super.finish();
michael@0 109 }
michael@0 110
michael@0 111 @Override
michael@0 112 public void onCreate(Bundle savedInstanceState) {
michael@0 113 super.onCreate(savedInstanceState);
michael@0 114 // mHandler is created here so runnables can be run on the main thread
michael@0 115 mHandler = new Handler();
michael@0 116 setContentView(R.layout.crash_reporter);
michael@0 117 mProgressDialog = new ProgressDialog(this);
michael@0 118 mProgressDialog.setMessage(getString(R.string.sending_crash_report));
michael@0 119
michael@0 120 String passedMinidumpPath = getIntent().getStringExtra(PASSED_MINI_DUMP_KEY);
michael@0 121 File passedMinidumpFile = new File(passedMinidumpPath);
michael@0 122 File pendingDir = new File(getFilesDir(), PENDING_SUFFIX);
michael@0 123 pendingDir.mkdirs();
michael@0 124 mPendingMinidumpFile = new File(pendingDir, passedMinidumpFile.getName());
michael@0 125 moveFile(passedMinidumpFile, mPendingMinidumpFile);
michael@0 126
michael@0 127 File extrasFile = new File(passedMinidumpPath.replaceAll(".dmp", ".extra"));
michael@0 128 mPendingExtrasFile = new File(pendingDir, extrasFile.getName());
michael@0 129 moveFile(extrasFile, mPendingExtrasFile);
michael@0 130
michael@0 131 mExtrasStringMap = new HashMap<String, String>();
michael@0 132 readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
michael@0 133
michael@0 134 // Set the flag that indicates we were stopped as expected, as
michael@0 135 // we will send a crash report, so it is not a silent OOM crash.
michael@0 136 SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
michael@0 137 SharedPreferences.Editor editor = prefs.edit();
michael@0 138 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
michael@0 139 editor.putBoolean(GeckoApp.PREFS_CRASHED, true);
michael@0 140 editor.commit();
michael@0 141
michael@0 142 final CheckBox allowContactCheckBox = (CheckBox) findViewById(R.id.allow_contact);
michael@0 143 final CheckBox includeUrlCheckBox = (CheckBox) findViewById(R.id.include_url);
michael@0 144 final CheckBox sendReportCheckBox = (CheckBox) findViewById(R.id.send_report);
michael@0 145 final EditText commentsEditText = (EditText) findViewById(R.id.comment);
michael@0 146 final EditText emailEditText = (EditText) findViewById(R.id.email);
michael@0 147
michael@0 148 // Load CrashReporter preferences to avoid redundant user input.
michael@0 149 final boolean sendReport = prefs.getBoolean(PREFS_SEND_REPORT, true);
michael@0 150 final boolean includeUrl = prefs.getBoolean(PREFS_INCLUDE_URL, false);
michael@0 151 final boolean allowContact = prefs.getBoolean(PREFS_ALLOW_CONTACT, false);
michael@0 152 final String contactEmail = prefs.getString(PREFS_CONTACT_EMAIL, "");
michael@0 153
michael@0 154 allowContactCheckBox.setChecked(allowContact);
michael@0 155 includeUrlCheckBox.setChecked(includeUrl);
michael@0 156 sendReportCheckBox.setChecked(sendReport);
michael@0 157 emailEditText.setText(contactEmail);
michael@0 158
michael@0 159 sendReportCheckBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
michael@0 160 @Override
michael@0 161 public void onCheckedChanged(CompoundButton checkbox, boolean isChecked) {
michael@0 162 commentsEditText.setEnabled(isChecked);
michael@0 163 commentsEditText.requestFocus();
michael@0 164
michael@0 165 includeUrlCheckBox.setEnabled(isChecked);
michael@0 166 allowContactCheckBox.setEnabled(isChecked);
michael@0 167 emailEditText.setEnabled(isChecked && allowContactCheckBox.isChecked());
michael@0 168 }
michael@0 169 });
michael@0 170
michael@0 171 allowContactCheckBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
michael@0 172 @Override
michael@0 173 public void onCheckedChanged(CompoundButton checkbox, boolean isChecked) {
michael@0 174 // We need to check isEnabled() here because this listener is
michael@0 175 // fired on rotation -- even when the checkbox is disabled.
michael@0 176 emailEditText.setEnabled(checkbox.isEnabled() && isChecked);
michael@0 177 emailEditText.requestFocus();
michael@0 178 }
michael@0 179 });
michael@0 180
michael@0 181 emailEditText.setOnClickListener(new View.OnClickListener() {
michael@0 182 @Override
michael@0 183 public void onClick(View v) {
michael@0 184 // Even if the email EditText is disabled, allow it to be
michael@0 185 // clicked and focused.
michael@0 186 if (sendReportCheckBox.isChecked() && !v.isEnabled()) {
michael@0 187 allowContactCheckBox.setChecked(true);
michael@0 188 v.setEnabled(true);
michael@0 189 v.requestFocus();
michael@0 190 }
michael@0 191 }
michael@0 192 });
michael@0 193 }
michael@0 194
michael@0 195 @Override
michael@0 196 public void onBackPressed() {
michael@0 197 AlertDialog.Builder builder = new AlertDialog.Builder(this);
michael@0 198 builder.setMessage(R.string.crash_closing_alert);
michael@0 199 builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
michael@0 200 @Override
michael@0 201 public void onClick(DialogInterface dialog, int which) {
michael@0 202 dialog.dismiss();
michael@0 203 }
michael@0 204 });
michael@0 205 builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
michael@0 206 @Override
michael@0 207 public void onClick(DialogInterface dialog, int which) {
michael@0 208 CrashReporter.this.finish();
michael@0 209 }
michael@0 210 });
michael@0 211 builder.show();
michael@0 212 }
michael@0 213
michael@0 214 private void backgroundSendReport() {
michael@0 215 final CheckBox sendReportCheckbox = (CheckBox) findViewById(R.id.send_report);
michael@0 216 if (!sendReportCheckbox.isChecked()) {
michael@0 217 doFinish();
michael@0 218 return;
michael@0 219 }
michael@0 220
michael@0 221 // Persist settings to avoid redundant user input.
michael@0 222 savePrefs();
michael@0 223
michael@0 224 mProgressDialog.show();
michael@0 225 new Thread(new Runnable() {
michael@0 226 @Override
michael@0 227 public void run() {
michael@0 228 sendReport(mPendingMinidumpFile, mExtrasStringMap, mPendingExtrasFile);
michael@0 229 }
michael@0 230 }, "CrashReporter Thread").start();
michael@0 231 }
michael@0 232
michael@0 233 private void savePrefs() {
michael@0 234 SharedPreferences.Editor editor = GeckoSharedPrefs.forApp(this).edit();
michael@0 235
michael@0 236 final boolean allowContact = ((CheckBox) findViewById(R.id.allow_contact)).isChecked();
michael@0 237 final boolean includeUrl = ((CheckBox) findViewById(R.id.include_url)).isChecked();
michael@0 238 final boolean sendReport = ((CheckBox) findViewById(R.id.send_report)).isChecked();
michael@0 239 final String contactEmail = ((EditText) findViewById(R.id.email)).getText().toString();
michael@0 240
michael@0 241 editor.putBoolean(PREFS_ALLOW_CONTACT, allowContact);
michael@0 242 editor.putBoolean(PREFS_INCLUDE_URL, includeUrl);
michael@0 243 editor.putBoolean(PREFS_SEND_REPORT, sendReport);
michael@0 244 editor.putString(PREFS_CONTACT_EMAIL, contactEmail);
michael@0 245
michael@0 246 // A slight performance improvement via async apply() vs. blocking on commit().
michael@0 247 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
michael@0 248 editor.commit();
michael@0 249 } else {
michael@0 250 editor.apply();
michael@0 251 }
michael@0 252 }
michael@0 253
michael@0 254 public void onCloseClick(View v) { // bound via crash_reporter.xml
michael@0 255 backgroundSendReport();
michael@0 256 }
michael@0 257
michael@0 258 public void onRestartClick(View v) { // bound via crash_reporter.xml
michael@0 259 doRestart();
michael@0 260 backgroundSendReport();
michael@0 261 }
michael@0 262
michael@0 263 private boolean readStringsFromFile(String filePath, Map<String, String> stringMap) {
michael@0 264 try {
michael@0 265 BufferedReader reader = new BufferedReader(new FileReader(filePath));
michael@0 266 return readStringsFromReader(reader, stringMap);
michael@0 267 } catch (Exception e) {
michael@0 268 Log.e(LOGTAG, "exception while reading strings: ", e);
michael@0 269 return false;
michael@0 270 }
michael@0 271 }
michael@0 272
michael@0 273 private boolean readStringsFromReader(BufferedReader reader, Map<String, String> stringMap) throws IOException {
michael@0 274 String line;
michael@0 275 while ((line = reader.readLine()) != null) {
michael@0 276 int equalsPos = -1;
michael@0 277 if ((equalsPos = line.indexOf('=')) != -1) {
michael@0 278 String key = line.substring(0, equalsPos);
michael@0 279 String val = unescape(line.substring(equalsPos + 1));
michael@0 280 stringMap.put(key, val);
michael@0 281 }
michael@0 282 }
michael@0 283 reader.close();
michael@0 284 return true;
michael@0 285 }
michael@0 286
michael@0 287 private String generateBoundary() {
michael@0 288 // Generate some random numbers to fill out the boundary
michael@0 289 int r0 = (int)((double)Integer.MAX_VALUE * Math.random());
michael@0 290 int r1 = (int)((double)Integer.MAX_VALUE * Math.random());
michael@0 291 return String.format("---------------------------%08X%08X", r0, r1);
michael@0 292 }
michael@0 293
michael@0 294 private void sendPart(OutputStream os, String boundary, String name, String data) {
michael@0 295 try {
michael@0 296 os.write(("--" + boundary + "\r\n" +
michael@0 297 "Content-Disposition: form-data; name=\"" + name + "\"\r\n" +
michael@0 298 "\r\n" +
michael@0 299 data + "\r\n"
michael@0 300 ).getBytes());
michael@0 301 } catch (Exception ex) {
michael@0 302 Log.e(LOGTAG, "Exception when sending \"" + name + "\"", ex);
michael@0 303 }
michael@0 304 }
michael@0 305
michael@0 306 private void sendFile(OutputStream os, String boundary, String name, File file) throws IOException {
michael@0 307 os.write(("--" + boundary + "\r\n" +
michael@0 308 "Content-Disposition: form-data; name=\"" + name + "\"; " +
michael@0 309 "filename=\"" + file.getName() + "\"\r\n" +
michael@0 310 "Content-Type: application/octet-stream\r\n" +
michael@0 311 "\r\n"
michael@0 312 ).getBytes());
michael@0 313 FileChannel fc = new FileInputStream(file).getChannel();
michael@0 314 fc.transferTo(0, fc.size(), Channels.newChannel(os));
michael@0 315 fc.close();
michael@0 316 }
michael@0 317
michael@0 318 private String readLogcat() {
michael@0 319 BufferedReader br = null;
michael@0 320 try {
michael@0 321 // get the last 200 lines of logcat
michael@0 322 Process proc = Runtime.getRuntime().exec(new String[] {
michael@0 323 "logcat", "-v", "threadtime", "-t", "200", "-d", "*:D"
michael@0 324 });
michael@0 325 StringBuilder sb = new StringBuilder();
michael@0 326 br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
michael@0 327 for (String s = br.readLine(); s != null; s = br.readLine()) {
michael@0 328 sb.append(s).append('\n');
michael@0 329 }
michael@0 330 return sb.toString();
michael@0 331 } catch (Exception e) {
michael@0 332 return "Unable to get logcat: " + e.toString();
michael@0 333 } finally {
michael@0 334 if (br != null) {
michael@0 335 try {
michael@0 336 br.close();
michael@0 337 } catch (Exception e) {
michael@0 338 // ignore
michael@0 339 }
michael@0 340 }
michael@0 341 }
michael@0 342 }
michael@0 343
michael@0 344 private void sendReport(File minidumpFile, Map<String, String> extras, File extrasFile) {
michael@0 345 Log.i(LOGTAG, "sendReport: " + minidumpFile.getPath());
michael@0 346 final CheckBox includeURLCheckbox = (CheckBox) findViewById(R.id.include_url);
michael@0 347
michael@0 348 String spec = extras.get(SERVER_URL_KEY);
michael@0 349 if (spec == null) {
michael@0 350 doFinish();
michael@0 351 return;
michael@0 352 }
michael@0 353
michael@0 354 Log.i(LOGTAG, "server url: " + spec);
michael@0 355 try {
michael@0 356 URL url = new URL(spec);
michael@0 357 HttpURLConnection conn = (HttpURLConnection)url.openConnection();
michael@0 358 conn.setRequestMethod("POST");
michael@0 359 String boundary = generateBoundary();
michael@0 360 conn.setDoOutput(true);
michael@0 361 conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
michael@0 362 conn.setRequestProperty("Content-Encoding", "gzip");
michael@0 363
michael@0 364 OutputStream os = new GZIPOutputStream(conn.getOutputStream());
michael@0 365 for (String key : extras.keySet()) {
michael@0 366 if (key.equals(PAGE_URL_KEY)) {
michael@0 367 if (includeURLCheckbox.isChecked())
michael@0 368 sendPart(os, boundary, key, extras.get(key));
michael@0 369 } else if (!key.equals(SERVER_URL_KEY) && !key.equals(NOTES_KEY)) {
michael@0 370 sendPart(os, boundary, key, extras.get(key));
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374 // Add some extra information to notes so its displayed by
michael@0 375 // crash-stats.mozilla.org. Remove this when bug 607942 is fixed.
michael@0 376 StringBuilder sb = new StringBuilder();
michael@0 377 sb.append(extras.containsKey(NOTES_KEY) ? extras.get(NOTES_KEY) + "\n" : "");
michael@0 378 if (AppConstants.MOZ_MIN_CPU_VERSION < 7) {
michael@0 379 sb.append("nothumb Build\n");
michael@0 380 }
michael@0 381 sb.append(Build.MANUFACTURER).append(' ')
michael@0 382 .append(Build.MODEL).append('\n')
michael@0 383 .append(Build.FINGERPRINT);
michael@0 384 sendPart(os, boundary, NOTES_KEY, sb.toString());
michael@0 385
michael@0 386 sendPart(os, boundary, "Min_ARM_Version", Integer.toString(AppConstants.MOZ_MIN_CPU_VERSION));
michael@0 387 sendPart(os, boundary, "Android_Manufacturer", Build.MANUFACTURER);
michael@0 388 sendPart(os, boundary, "Android_Model", Build.MODEL);
michael@0 389 sendPart(os, boundary, "Android_Board", Build.BOARD);
michael@0 390 sendPart(os, boundary, "Android_Brand", Build.BRAND);
michael@0 391 sendPart(os, boundary, "Android_Device", Build.DEVICE);
michael@0 392 sendPart(os, boundary, "Android_Display", Build.DISPLAY);
michael@0 393 sendPart(os, boundary, "Android_Fingerprint", Build.FINGERPRINT);
michael@0 394 sendPart(os, boundary, "Android_CPU_ABI", Build.CPU_ABI);
michael@0 395 if (Build.VERSION.SDK_INT >= 8) {
michael@0 396 try {
michael@0 397 sendPart(os, boundary, "Android_CPU_ABI2", Build.CPU_ABI2);
michael@0 398 sendPart(os, boundary, "Android_Hardware", Build.HARDWARE);
michael@0 399 } catch (Exception ex) {
michael@0 400 Log.e(LOGTAG, "Exception while sending SDK version 8 keys", ex);
michael@0 401 }
michael@0 402 }
michael@0 403 sendPart(os, boundary, "Android_Version", Build.VERSION.SDK_INT + " (" + Build.VERSION.CODENAME + ")");
michael@0 404 if (Build.VERSION.SDK_INT >= 16 && includeURLCheckbox.isChecked()) {
michael@0 405 sendPart(os, boundary, "Android_Logcat", readLogcat());
michael@0 406 }
michael@0 407
michael@0 408 String comment = ((EditText) findViewById(R.id.comment)).getText().toString();
michael@0 409 if (!TextUtils.isEmpty(comment)) {
michael@0 410 sendPart(os, boundary, "Comments", comment);
michael@0 411 }
michael@0 412
michael@0 413 if (((CheckBox) findViewById(R.id.allow_contact)).isChecked()) {
michael@0 414 String email = ((EditText) findViewById(R.id.email)).getText().toString();
michael@0 415 sendPart(os, boundary, "Email", email);
michael@0 416 }
michael@0 417
michael@0 418 sendFile(os, boundary, MINI_DUMP_PATH_KEY, minidumpFile);
michael@0 419 os.write(("\r\n--" + boundary + "--\r\n").getBytes());
michael@0 420 os.flush();
michael@0 421 os.close();
michael@0 422 BufferedReader br = new BufferedReader(
michael@0 423 new InputStreamReader(conn.getInputStream()));
michael@0 424 HashMap<String, String> responseMap = new HashMap<String, String>();
michael@0 425 readStringsFromReader(br, responseMap);
michael@0 426
michael@0 427 if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
michael@0 428 File submittedDir = new File(getFilesDir(),
michael@0 429 SUBMITTED_SUFFIX);
michael@0 430 submittedDir.mkdirs();
michael@0 431 minidumpFile.delete();
michael@0 432 extrasFile.delete();
michael@0 433 String crashid = responseMap.get("CrashID");
michael@0 434 File file = new File(submittedDir, crashid + ".txt");
michael@0 435 FileOutputStream fos = new FileOutputStream(file);
michael@0 436 fos.write("Crash ID: ".getBytes());
michael@0 437 fos.write(crashid.getBytes());
michael@0 438 fos.close();
michael@0 439 } else {
michael@0 440 Log.i(LOGTAG, "Received failure HTTP response code from server: " + conn.getResponseCode());
michael@0 441 }
michael@0 442 } catch (IOException e) {
michael@0 443 Log.e(LOGTAG, "exception during send: ", e);
michael@0 444 }
michael@0 445
michael@0 446 doFinish();
michael@0 447 }
michael@0 448
michael@0 449 private void doRestart() {
michael@0 450 try {
michael@0 451 String action = "android.intent.action.MAIN";
michael@0 452 Intent intent = new Intent(action);
michael@0 453 intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
michael@0 454 AppConstants.BROWSER_INTENT_CLASS_NAME);
michael@0 455 intent.putExtra("didRestart", true);
michael@0 456 Log.i(LOGTAG, intent.toString());
michael@0 457 startActivity(intent);
michael@0 458 } catch (Exception e) {
michael@0 459 Log.e(LOGTAG, "error while trying to restart", e);
michael@0 460 }
michael@0 461 }
michael@0 462
michael@0 463 private String unescape(String string) {
michael@0 464 return string.replaceAll("\\\\\\\\", "\\").replaceAll("\\\\n", "\n").replaceAll("\\\\t", "\t");
michael@0 465 }
michael@0 466 }

mercurial