/*
* Copyright 2011 Kevin Gaudin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.acra.config;
import android.app.Application;
import android.support.annotation.NonNull;
import org.acra.ACRA;
import org.acra.ReportField;
import org.acra.annotation.NoPropagation;
import org.acra.dialog.CrashReportDialog;
import org.acra.sender.HttpSender;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import static org.acra.ACRA.LOG_TAG;
import static org.acra.ACRAConstants.*;
/**
* Builder responsible for programmatic construction of an immutable {@link ACRAConfiguration}.
*
* @since 4.8.1
*/
@SuppressWarnings("unused")
@org.acra.annotation.ConfigurationBuilder
public final class ConfigurationBuilder extends BaseConfigurationBuilder<ConfigurationBuilder> {
private final Map<String, String> httpHeaders;
private final Map<ReportField, Boolean> reportContentChanges;
/**
* Constructs a ConfigurationBuilder that is prepopulated with any
* '@ReportCrashes' annotation declared on the Application class.
*
* @param app Current Application, from which any annotated config will be gleaned.
*/
public ConfigurationBuilder(@NonNull Application app) {
super(app);
httpHeaders = new HashMap<String, String>();
reportContentChanges = new EnumMap<ReportField, Boolean>(ReportField.class);
}
/**
* Builds the {@link ACRAConfiguration} which will be used to configure ACRA.
* <p>
* You can pass this {@link ConfigurationBuilder} to {@link ACRA#init(Application, ConfigurationBuilder)} and
* {@link ACRA#init(Application, ConfigurationBuilder)} will handle any Exception.
* </p>
*
* @return new ACRAConfiguration containing all the properties configured on this builder.
* @throws ACRAConfigurationException if the required values for the configured notification mode have not been provided.
*/
@NoPropagation
@NonNull
public ACRAConfiguration build() throws ACRAConfigurationException {
switch (reportingInteractionMode()) {
case TOAST:
if (resToastText() == DEFAULT_RES_VALUE) {
throw new ACRAConfigurationException("TOAST mode: you have to define the resToastText parameter in your application @ReportsCrashes() annotation.");
}
break;
case NOTIFICATION:
if (resNotifTickerText() == DEFAULT_RES_VALUE || resNotifTitle() == DEFAULT_RES_VALUE || resNotifText() == DEFAULT_RES_VALUE) {
throw new ACRAConfigurationException("NOTIFICATION mode: you have to define at least the resNotifTickerText, resNotifTitle, resNotifText parameters in your application @ReportsCrashes() annotation.");
}
if (CrashReportDialog.class.equals(reportDialogClass()) && resDialogText() == DEFAULT_RES_VALUE) {
throw new ACRAConfigurationException("NOTIFICATION mode: using the (default) CrashReportDialog requires you have to define the resDialogText parameter in your application @ReportsCrashes() annotation.");
}
break;
case DIALOG:
if (CrashReportDialog.class.equals(reportDialogClass()) && resDialogText() == DEFAULT_RES_VALUE) {
throw new ACRAConfigurationException("DIALOG mode: using the (default) CrashReportDialog requires you to define the resDialogText parameter in your application @ReportsCrashes() annotation.");
}
break;
default:
break;
}
if (reportSenderFactoryClasses().length == 0) {
throw new ACRAConfigurationException("Report sender factories: using no report senders will make ACRA useless. Configure at least one ReportSenderFactory.");
}
checkValidity((Class[]) reportSenderFactoryClasses());
checkValidity(reportDialogClass(), reportPrimerClass(), retryPolicyClass(), keyStoreFactoryClass());
return new ACRAConfiguration(this);
}
private void checkValidity(Class<?>... classes) throws ACRAConfigurationException {
for (Class<?> clazz : classes) {
if (clazz.isInterface()) {
throw new ACRAConfigurationException("Expected class, but found interface " + clazz.getName() + ".");
} else if (Modifier.isAbstract(clazz.getModifiers())) {
throw new ACRAConfigurationException("Class " + clazz.getName() + " cannot be abstract.");
}
try {
clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new ACRAConfigurationException("Class " + clazz.getName() + " is missing a no-args Constructor.", e);
}
}
}
/**
* Use this if you want to keep the default configuration of reportContent, but set some fields explicitly.
*
* @param field the field to set
* @param enable if this field should be reported
* @return this instance
*/
@NonNull
public ConfigurationBuilder setReportField(@NonNull ReportField field, boolean enable) {
this.reportContentChanges.put(field, enable);
return this;
}
/**
* Set custom HTTP headers to be sent by the provided {@link HttpSender}.
* This should be used also by third party senders.
*
* @param headers A map associating HTTP header names to their values.
* @return this instance
*/
@NonNull
public ConfigurationBuilder setHttpHeaders(@NonNull Map<String, String> headers) {
this.httpHeaders.clear();
this.httpHeaders.putAll(headers);
return this;
}
@NonNull
Map<String, String> httpHeaders() {
return httpHeaders;
}
@NoPropagation
@NonNull
@Override
ReportField[] customReportContent() {
return super.customReportContent();
}
@NonNull
Set<ReportField> reportContent() {
final Set<ReportField> reportContent = new LinkedHashSet<ReportField>();
if (customReportContent().length != 0) {
if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using custom Report Fields");
reportContent.addAll(Arrays.asList(customReportContent()));
} else if (DEFAULT_STRING_VALUE.equals(mailTo())) {
if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using default Report Fields");
reportContent.addAll(Arrays.asList(DEFAULT_REPORT_FIELDS));
} else {
if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using default Mail Report Fields");
reportContent.addAll(Arrays.asList(DEFAULT_MAIL_REPORT_FIELDS));
}
// Add or remove any extra fields.
for (Map.Entry<ReportField, Boolean> entry : reportContentChanges.entrySet()) {
if (entry.getValue()) {
reportContent.add(entry.getKey());
} else {
reportContent.remove(entry.getKey());
}
}
return reportContent;
}
}