blob: 759c805bbcc2dacda7848fd3dd8570df99bed7d8 [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* 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 com.google.android.libraries.mobiledatadownload.downloader.offroad;
import com.google.android.downloader.RequestException;
import com.google.android.libraries.mobiledatadownload.DownloadException;
import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
/**
* Handles mapping exceptions from Downloader2 into the equivalent MDD {@link DownloadException}.
*
* <p>Common exceptions are parsed and handled by default, but the underlying network stack may
* include special Exceptions and/or error codes that need to be parsed. If this is the case, a
* {@link NetworkStackExceptionHandler} can be provided to perform this parsing.
*/
public final class ExceptionHandler {
/**
* The maximum amount of attempts we recurse before stopping in {@link
* #mapExceptionToDownloadResultCode}.
*/
private static final int EXCEPTION_TO_CODE_RECURSION_LIMIT = 5;
/** The handler of underlying network stack failures. */
private final NetworkStackExceptionHandler internalExceptionHandler;
private ExceptionHandler(NetworkStackExceptionHandler internalExceptionHandler) {
this.internalExceptionHandler = internalExceptionHandler;
}
/** Convenience method to return a new ExceptionHandler with default handling. */
public static ExceptionHandler withDefaultHandling() {
return new ExceptionHandler(new NetworkStackExceptionHandler() {});
}
/** Return a new instance with specific handling for a network stack. */
public static ExceptionHandler withNetworkStackHandling(
NetworkStackExceptionHandler internalExceptionHandler) {
return new ExceptionHandler(internalExceptionHandler);
}
/**
* Map given failure to a {@link DownloadException}.
*
* <p>For most cases, this method does not need to be overridden.
*
* <p><em>NOTE:</em> If the given throwable is already a {@link DownloadException}, it is returned
* immediately. In this case, the given message will <b>not</b> be used (the message from the
* given throwable will be used instead).
*
* @param message top-level message that should be used for the returned {@link DownloadException}
* @param throwable generic throwable that should be mapped to {@link DownloadException}
* @return {@link DownloadException} that wraps around given throwable with appropriate {@link
* DownloadResultCode}
*/
public DownloadException mapToDownloadException(String message, Throwable throwable) {
if (throwable instanceof DownloadException) {
// Exception is already an MDD DownloadException -- return it.
return (DownloadException) throwable;
}
DownloadResultCode code = mapExceptionToDownloadResultCode(throwable, /* iteration = */ 0);
return DownloadException.builder()
.setMessage(message)
.setDownloadResultCode(code)
.setCause(throwable)
.build();
}
/**
* Map exception to {@link DownloadResultCode}.
*
* @param throwable the exception to map to a {@link DownloadResultCode}
*/
private DownloadResultCode mapExceptionToDownloadResultCode(Throwable throwable, int iteration) {
// Check recursion limit and return unknown error if it is hit.
if (iteration >= EXCEPTION_TO_CODE_RECURSION_LIMIT) {
return DownloadResultCode.UNKNOWN_ERROR;
}
DownloadResultCode networkStackMapperResult =
internalExceptionHandler.mapFromNetworkStackException(throwable);
if (!networkStackMapperResult.equals(DownloadResultCode.UNKNOWN_ERROR)) {
// network stack mapper returned known result code -- return it instead of performing common
// mapping.
return networkStackMapperResult;
}
if (throwable instanceof DownloadException) {
// exception in the chain is already an MDD DownloadException -- use its code
return ((DownloadException) throwable).getDownloadResultCode();
}
if (throwable instanceof RequestException) {
// Check error details for http status code error.
if (((RequestException) throwable).getErrorDetails().getHttpStatusCode() != -1) {
// error code has an associated http status code, mark it as HTTP_ERROR
return DownloadResultCode.ANDROID_DOWNLOADER_HTTP_ERROR;
}
}
if (throwable.getCause() != null) {
// Exception has an underlying cause -- attempt mapping on that cause.
return mapExceptionToDownloadResultCode(throwable.getCause(), iteration + 1);
}
if (throwable instanceof com.google.android.downloader.DownloadException) {
// If DownloadException is not wrapping anything, we can't determine the error further -- mark
// it as a general Downloader2 error.
return DownloadResultCode.ANDROID_DOWNLOADER2_ERROR;
}
// We couldn't parse any common errors, return an unknown error
return DownloadResultCode.UNKNOWN_ERROR;
}
/**
* Interface to handle parsing exceptions from an underlying network stack.
*
* <p>If an underlying network stack is used which can throw special exceptions or has an error
* code map, consider implementing this to provide better handling of exceptions.
*/
public static interface NetworkStackExceptionHandler {
/**
* Map Custom Exception to {@link DownloadResultCode}.
*
* <p>Underlying network stacks may have specific exceptions that can be used to determine the
* best DownloadResultCode. This method should be overridden to check for such exceptions.
*
* <p>If a known {@link DownloadResultCode} is returned (i.e not UNKNOWN_ERROR), it will be
* used.
*
* <p>By default, an UNKNOWN_ERROR is returned.
*/
default DownloadResultCode mapFromNetworkStackException(Throwable throwable) {
return DownloadResultCode.UNKNOWN_ERROR;
}
}
}