| /* Copyright (C) 2011 The Android Open Source Project |
| ** |
| ** This software is licensed under the terms of the GNU General Public |
| ** License version 2, as published by the Free Software Foundation, and |
| ** may be copied, distributed, and modified under those terms. |
| ** |
| ** 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. |
| */ |
| |
| #include "android/crashreport/ui/ConfirmDialog.h" |
| |
| #include "android/android.h" |
| #include "android/base/files/IniFile.h" |
| #include "android/base/files/PathUtils.h" |
| #include "android/crashreport/CrashReporter.h" |
| #include "android/globals.h" |
| |
| #include <QEventLoop> |
| #include <QFutureWatcher> |
| #include <QScrollBar> |
| #include <QSettings> |
| #include "QtConcurrent/qtconcurrentrun.h" |
| |
| static const char kMessageBoxTitle[] = "Android Emulator"; |
| static const char kMessageBoxMessageInternalError[] = |
| "<p>Android Emulator closed because of an internal error:</p>"; |
| static const char kMessageBoxMessage[] = |
| "<p>Android Emulator closed unexpectedly.</p>"; |
| static const char kMessageBoxMessageFooter[] = |
| "<p>Do you want to send a crash report about the problem?</p>"; |
| static const char kMessageBoxMessageDetailHW[] = |
| "An error report containing the information shown below, " |
| "including system-specific information, " |
| "will be sent to Google's Android team to help identify " |
| "and fix the problem. " |
| "<a href=\"https://www.google.com/policies/privacy/\">Privacy " |
| "Policy</a>."; |
| |
| static const char kIconFile[] = "emulator_icon_128.png"; |
| |
| extern "C" const unsigned char* android_emulator_icon_find(const char* name, |
| size_t* psize); |
| |
| using android::base::IniFile; |
| using android::base::PathUtils; |
| using android::base::StringView; |
| using android::crashreport::CrashService; |
| |
| ConfirmDialog::ConfirmDialog(QWidget* parent, |
| CrashService* crashservice, |
| Ui::Settings::CRASHREPORT_PREFERENCE_VALUE reportPreference, |
| const char* reportingDir) |
| : QDialog(parent), |
| mCrashService(crashservice), |
| mReportPreference(reportPreference), |
| mDetailsHidden(true), |
| mDidGetSysInfo(false), |
| mDidUpdateDetails(false), |
| mReportingDir(reportingDir) { |
| |
| mSendButton = new QPushButton(tr("Send report")); |
| mDontSendButton = new QPushButton(tr("Don't send")); |
| mDetailsButton = new QPushButton(tr("")); |
| mLabelText = new QLabel(QString::fromStdString(constructDumpMessage())); |
| mInfoText = new QLabel(kMessageBoxMessageDetailHW); |
| mIcon = new QLabel(); |
| mCommentsText = new QTextEdit(); |
| mDetailsText = new QPlainTextEdit(); |
| mProgressText = new QLabel(tr("Working...")); |
| mProgress = new QProgressBar; |
| mSavePreference = |
| new QCheckBox(tr("Automatically send future crash reports " |
| "(Re-configure in Emulator settings menu)")); |
| |
| // Create a checkbox, but don't use it unless the GPU caused the crash |
| mSoftwareGPU = new QCheckBox(tr("Use software rendering for this device")); |
| mSoftwareGPU->setChecked(false); |
| |
| QSettings settings; |
| bool save_preference_checked = |
| settings.value(Ui::Settings::CRASHREPORT_SAVEPREFERENCE_CHECKED, 1).toInt(); |
| mSavePreference->setChecked(save_preference_checked); |
| mSavePreference->show(); |
| |
| mSuggestionText = new QLabel(tr("Suggestion(s) based on crash info:\n\n")); |
| mSuggestionText->setTextInteractionFlags(Qt::TextSelectableByMouse); |
| |
| mExtension = new QWidget; |
| mYesNoButtonBox = new QDialogButtonBox(Qt::Horizontal); |
| mDetailsButtonBox = new QDialogButtonBox(Qt::Horizontal); |
| mComment = new QWidget; |
| |
| size_t icon_size; |
| QPixmap icon; |
| |
| const unsigned char* icon_data = |
| android_emulator_icon_find(kIconFile, &icon_size); |
| |
| icon.loadFromData(icon_data, icon_size); |
| |
| mIcon->setPixmap(icon); |
| mSendButton->setDefault(true); |
| mInfoText->setWordWrap(true); |
| mInfoText->setOpenExternalLinks(true); |
| mCommentsText->setPlaceholderText( |
| tr("(Optional) Please describe what you were doing when the crash " |
| "occured.")); |
| mDetailsText->setReadOnly(true); |
| mProgressText->hide(); |
| mProgress->setRange(0, 0); |
| mProgress->hide(); |
| |
| crashservice->processCrash(); |
| auto suggestions = crashservice->getSuggestions().suggestions; |
| bool haveGfxFailure = false; |
| if (!suggestions.empty()) { |
| if (suggestions.find( |
| android::crashreport::Suggestion::UpdateGfxDrivers) != |
| suggestions.end()) { |
| haveGfxFailure = true; |
| addSuggestion(tr("It appears that your computer's OpenGL graphics " |
| "driver crashed. This was\n" |
| "probably caused by a bug in the driver or by a " |
| "bug in your app's OpenGL code.\n\n" |
| "You should check your manufacturer's website for " |
| "an updated graphics driver.\n\n" |
| "You can also tell the Emulator to use software " |
| "rendering for this device. This\n" |
| "could avoid driver problems and make it easier " |
| "to debug the OpenGL code in\n" |
| "your app." |
| #ifdef __APPLE__ |
| " (You may need to reduce your screen " |
| "resolution to run this way.)" |
| #endif |
| )); |
| } |
| mSuggestionText->show(); |
| } else { |
| mSuggestionText->hide(); |
| } |
| |
| mYesNoButtonBox->addButton(mSendButton, QDialogButtonBox::AcceptRole); |
| mYesNoButtonBox->addButton(mDontSendButton, QDialogButtonBox::RejectRole); |
| mDetailsButtonBox->addButton(mDetailsButton, QDialogButtonBox::ActionRole); |
| |
| setWindowIcon(icon); |
| connect(mSendButton, SIGNAL(clicked()), this, SLOT(sendReport())); |
| connect(mDontSendButton, SIGNAL(clicked()), this, SLOT(dontSendReport())); |
| connect(mDetailsButton, SIGNAL(clicked()), this, SLOT(detailtoggle())); |
| |
| QVBoxLayout* commentLayout = new QVBoxLayout; |
| commentLayout->setMargin(0); |
| commentLayout->addWidget(mCommentsText); |
| mComment->setLayout(commentLayout); |
| mComment->setMaximumHeight( |
| QFontMetrics(mCommentsText->currentFont()).height() * 7); |
| |
| QVBoxLayout* extensionLayout = new QVBoxLayout; |
| extensionLayout->setMargin(0); |
| extensionLayout->addWidget(mDetailsText); |
| |
| mExtension->setLayout(extensionLayout); |
| |
| QGridLayout* mainLayout = new QGridLayout; |
| |
| QFrame* hLineFrame = new QFrame(); |
| hLineFrame->setFrameShape(QFrame::HLine); |
| |
| int row = 0; |
| mainLayout->addWidget(mIcon, row, 0); |
| mainLayout->addWidget(mLabelText, row, 1, 1, 2); |
| |
| row++; |
| mainLayout->addWidget(mSuggestionText, row, 0, 1, 3); |
| |
| if (haveGfxFailure) { |
| row++; |
| mainLayout->addWidget(mSoftwareGPU, row, 0, 1, 3); |
| } |
| |
| row++; |
| mainLayout->addWidget(hLineFrame, row, 0, 1, 3); |
| |
| row++; |
| mainLayout->addWidget(mInfoText, row, 0, 1, 3); |
| |
| row++; |
| mainLayout->addWidget(mComment, row, 0, 1, 3); |
| |
| row++; |
| mainLayout->addWidget(mSavePreference, row, 0, 1, 3); |
| |
| row++; |
| mainLayout->addWidget(mDetailsButtonBox, row, 0, Qt::AlignLeft); |
| mainLayout->addWidget(mYesNoButtonBox, row, 1, 1, 2); |
| |
| row++; |
| mainLayout->addWidget(mExtension, row, 0, 1, 3); |
| |
| row++; |
| mainLayout->addWidget(mProgressText, row, 0, 1, 3); |
| row++; |
| mainLayout->addWidget(mProgress, row, 0, 1, 3); |
| |
| mainLayout->setSizeConstraint(QLayout::SetFixedSize); |
| setLayout(mainLayout); |
| setWindowTitle(tr(kMessageBoxTitle)); |
| hideDetails(); |
| } |
| |
| void ConfirmDialog::hideDetails() { |
| mDetailsButton->setText(tr("Show details")); |
| mDetailsText->hide(); |
| mDetailsHidden = true; |
| } |
| |
| void ConfirmDialog::disableInput() { |
| mSendButton->setEnabled(false); |
| mDontSendButton->setEnabled(false); |
| mDetailsButton->setEnabled(false); |
| mCommentsText->setEnabled(false); |
| mSavePreference->setEnabled(false); |
| } |
| |
| void ConfirmDialog::enableInput() { |
| mSendButton->setEnabled(true); |
| mDontSendButton->setEnabled(true); |
| mDetailsButton->setEnabled(true); |
| mCommentsText->setEnabled(true); |
| mSavePreference->setEnabled(true); |
| } |
| |
| void ConfirmDialog::getDetails() { |
| if (!mDidGetSysInfo) { |
| disableInput(); |
| |
| showProgressBar("Collecting crash info... this may take a minute."); |
| QEventLoop eventloop; |
| |
| QFutureWatcher<bool> watcher; |
| connect(&watcher, SIGNAL(finished()), &eventloop, SLOT(quit())); |
| |
| // Start the computation. |
| QFuture<bool> future = QtConcurrent::run( |
| mCrashService, |
| &::android::crashreport::CrashService::collectSysInfo); |
| watcher.setFuture(future); |
| |
| eventloop.exec(); |
| |
| hideProgressBar(); |
| enableInput(); |
| } |
| mDidGetSysInfo = true; |
| } |
| |
| void ConfirmDialog::showDetails() { |
| getDetails(); |
| if (!mDidUpdateDetails) { |
| QString details = QString::fromStdString(mCrashService->getReport()); |
| details += QString::fromStdString(mCrashService->getSysInfo()); |
| |
| mDetailsText->document()->setPlainText(details); |
| mDidUpdateDetails = true; |
| } |
| |
| mDetailsButton->setText(tr("Hide details")); |
| mDetailsText->show(); |
| mDetailsText->verticalScrollBar()->setValue( |
| mDetailsText->verticalScrollBar()->minimum()); |
| mDetailsHidden = false; |
| } |
| |
| void ConfirmDialog::addSuggestion(const QString& str) { |
| QString next_text = mSuggestionText->text() + str + "\n"; |
| mSuggestionText->setText(next_text); |
| } |
| |
| bool ConfirmDialog::didGetSysInfo() const { |
| return mDidGetSysInfo; |
| } |
| |
| QString ConfirmDialog::getUserComments() { |
| return mCommentsText->toPlainText(); |
| } |
| |
| void ConfirmDialog::showProgressBar(const std::string& msg) { |
| mProgressText->setText(msg.c_str()); |
| mProgressText->show(); |
| mProgress->show(); |
| } |
| |
| void ConfirmDialog::hideProgressBar() { |
| mProgressText->hide(); |
| mProgress->hide(); |
| } |
| |
| bool ConfirmDialog::uploadCrash() { |
| disableInput(); |
| showProgressBar("Sending crash report..."); |
| QEventLoop eventloop; |
| |
| QFutureWatcher<bool> watcher; |
| connect(&watcher, SIGNAL(finished()), &eventloop, SLOT(quit())); |
| |
| // Start the computation. |
| QFuture<bool> future = QtConcurrent::run( |
| mCrashService, &::android::crashreport::CrashService::uploadCrash); |
| watcher.setFuture(future); |
| |
| eventloop.exec(); |
| |
| hideProgressBar(); |
| |
| return watcher.result(); |
| } |
| |
| static void savePref(bool checked, Ui::Settings::CRASHREPORT_PREFERENCE_VALUE v) { |
| QSettings settings; |
| settings.setValue(Ui::Settings::CRASHREPORT_PREFERENCE, |
| checked ? v : Ui::Settings::CRASHREPORT_PREFERENCE_ASK); |
| settings.setValue(Ui::Settings::CRASHREPORT_SAVEPREFERENCE_CHECKED, |
| checked); |
| } |
| |
| void ConfirmDialog::sendReport() { |
| getDetails(); |
| mCrashService->addUserComments(mCommentsText->toPlainText().toStdString()); |
| bool upload_success = uploadCrash(); |
| |
| if (upload_success && |
| (mReportPreference == Ui::Settings::CRASHREPORT_PREFERENCE_ASK)) { |
| QMessageBox msgbox(this); |
| msgbox.setWindowTitle(tr("Crash Report Submitted")); |
| msgbox.setText(tr("<p>Thank you for submitting a crash report!</p>" |
| "<p>If you would like to contact us for further information, " |
| "use the following Crash Report ID:</p>")); |
| QString msg = QString::fromStdString(mCrashService->getReportId()); |
| msgbox.setInformativeText(msg); |
| msgbox.setTextInteractionFlags(Qt::TextSelectableByMouse); |
| msgbox.exec(); |
| } |
| |
| setSwGpu(); |
| |
| savePref(mSavePreference->isChecked(), Ui::Settings::CRASHREPORT_PREFERENCE_ALWAYS); |
| accept(); |
| } |
| |
| void ConfirmDialog::dontSendReport() { |
| setSwGpu(); |
| reject(); |
| } |
| |
| // If the user requests a switch to software GPU, modify |
| // config.ini |
| void ConfirmDialog::setSwGpu() { |
| if ( mSoftwareGPU->isChecked() && |
| mReportingDir ) |
| { |
| // The user wants the switch and we have the |
| // path to the avd_info.txt file |
| std::string avdInfoPath = |
| PathUtils::join(mReportingDir, CRASH_AVD_HARDWARE_INFO).c_str(); |
| |
| IniFile iniF(avdInfoPath); |
| iniF.read(); |
| |
| // Get the path to config.ini |
| std::string diskPartDir = iniF.getString("disk.dataPartition.path", ""); |
| if ( !diskPartDir.empty() ) { |
| // Keep the path; discard the file name |
| std::string outputDir; |
| std::string unused; |
| bool isOK = PathUtils::split(diskPartDir, &outputDir, &unused); |
| if (isOK) { |
| std::string hwQemuPath = PathUtils:: |
| join(outputDir, CORE_CONFIG_INI).c_str(); |
| // We have the path to config.ini |
| // Read that |
| IniFile hwQemuIniF(hwQemuPath); |
| hwQemuIniF.read(); |
| |
| // Set hw.gpu.enabled=no and hw.gpu.mode=guest |
| hwQemuIniF.setString("hw.gpu.enabled", "no"); |
| hwQemuIniF.setString("hw.gpu.mode", "guest"); |
| |
| // Write the modified configuration back |
| hwQemuIniF.write(); |
| } |
| } |
| } |
| } |
| |
| void ConfirmDialog::detailtoggle() { |
| if (mDetailsHidden) { |
| showDetails(); |
| } else { |
| hideDetails(); |
| } |
| } |
| |
| std::string ConfirmDialog::constructDumpMessage() const { |
| std::string dumpMessage = mCrashService->getDumpMessage(); |
| if (dumpMessage.empty()) { |
| dumpMessage = kMessageBoxMessage; |
| } else { |
| dumpMessage = std::string(kMessageBoxMessageInternalError) + "<p>" + |
| dumpMessage + "</p>"; |
| } |
| dumpMessage += kMessageBoxMessageFooter; |
| return dumpMessage; |
| } |