blob: 4a7ec16a8a97e8f6e94215de7476d8f9d7e99754 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* 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.android.tools.deployer;
import com.android.tools.deploy.proto.Deploy;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.List;
/**
* Represents a failed deployment. When installing, apply changes or apply code changes failed, this
* will be raised containing the information needed by the UI (IDE or command line) to surface the
* error and the possible actions to the user.
*/
public class DeployerException extends Exception {
// TODO(b/117673388): Add "Learn More" hyperlink/call to action when we finally have the webpage up.
public enum ResolutionAction {
// No possible resolution action exists.
NONE,
// Install and re-run the application.
RUN_APP,
// Apply changes to the application.
APPLY_CHANGES,
// Retry the previously attempted action.
RETRY,
}
/**
* The ordinal of this enum is used as the return code for the command line runners, the first
* value NO_ERROR is not used as is zero and represents no error on the command line.
*/
public enum Error {
NO_ERROR("", "", "", ResolutionAction.NONE), // Should not be used
CANNOT_SWAP_BEFORE_API_26(
"Apply Changes is only supported on API 26 or newer",
"",
"",
ResolutionAction.NONE),
// Specific errors that can occur before the swap process.
DUMP_UNKNOWN_PACKAGE(
"Package not found on device.",
"The package '%s' was not found on the device. Is the app installed?",
"Install and run app",
ResolutionAction.RUN_APP),
DUMP_UNKNOWN_PROCESS(
"No running app process found.", "", "Run app", ResolutionAction.RUN_APP),
REMOTE_APK_NOT_FOUND_IN_DB(
"Android Studio was unable to recognize the APK(s) currently installed on the device.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
DIFFERENT_NUMBER_OF_APKS(
"A different number of APKs were found on the device than on the host.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
DIFFERENT_APK_NAMES(
"The naming scheme of APKs on the device differ from the APKs on the host.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
PROCESS_CRASHING(
"Apply Changes could not complete because an application process is crashed.",
"Process '%s' has crashed.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
PROCESS_NOT_RESPONDING(
"Apply Changes could not complete because an application process is not responding.",
"Process '%s' is not responding.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
PROCESS_TERMINATED(
"Apply Changes could not complete because one of the application's processes terminated unexpectedly.",
"PID #%s terminated before the operation could complete.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
ENTRY_NOT_FOUND(
"Apply Changes could not find an expected file in the APK.",
"'%s' was not found in APK '%s'",
"Retry",
ResolutionAction.APPLY_CHANGES),
ENTRY_UNZIP_FAILED(
"Apply Changes failed to extract a file from the APK.",
"Exception occurred: %s",
"Retry",
ResolutionAction.APPLY_CHANGES),
// Errors pertaining to un-swappable changes.
ISOLATED_SERVICE_NOT_SUPPORTED(
"Applications with services in isolated processes cannot be swapped with Apply Changes.",
"The following service(s) are set to run in an isolated process: %s",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CLASS_NOT_FOUND(
"Class not found: %s",
"Class '%s' was not found during swap.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_SWAP_STATIC_LIB(
"Modifications to shared libraries require an app restart.",
"File '%s' was modified.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_SWAP_MANIFEST(
"Modifications to AndroidManifest.xml require an app restart.",
"Manifest '%s' was modified.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_SWAP_RESOURCE(
"Modifying resources requires an activity restart.",
"Resource '%s' was modified.",
"Apply changes and restart activity",
ResolutionAction.APPLY_CHANGES),
CANNOT_SWAP_CRASHLYTICS_PROPERTY(
"Crashlytics modified your build ID, which requires an activity restart. "
+ "<a href=\"https://d.android.com/r/studio-ui/apply-changes-crashlytics-buildid\""
+ ">See here</a>",
"Resource '%s' was modified.",
"Apply changes and restart activity",
ResolutionAction.APPLY_CHANGES),
CANNOT_ADD_RESOURCE(
"Adding or renaming a resource requires an application restart.",
"Resource '%s' (%s) was added.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_REMOVE_RESOURCE(
"Removing a resource requires an application restart.",
"Resource '%s' (%s) was removed.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
// Errors that are reported to us by jvmti.
CANNOT_ADD_METHOD(
"Adding a new method requires an app restart.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_AND_OR_REMOVE_FIELDS(
"Adding or removing a field requires an app restart.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_REMOVE_FIELDS(
"Removing a field requires an app restart.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_CHANGE_INHERITANCE(
"Changes to class inheritance require an app restart.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_DELETE_METHOD(
"Removing a method requires an app restart.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_CHANGE_CLASS_MODIFIERS(
"Changing class modifiers requires an app restart.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
CANNOT_CHANGE_METHOD_MODIFIERS(
"Changing method modifiers requires an app restart.",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
VERIFICATION_ERROR(
"New code fails verification",
"",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
JVMTI_ERROR("JVMTI error: %s", "", "Reinstall and restart app", ResolutionAction.RUN_APP),
// Catch-all errors for when an arbitrary failure may occur.
DUMP_FAILED(
"We were unable to deploy your changes.", "%s", "Retry", ResolutionAction.RETRY),
DUMP_MIXED_ARCH(
"Application with process in both 32 and 64 bit mode.",
"%s",
"Retry",
ResolutionAction.RETRY),
PREINSTALL_FAILED(
"The application could not be installed.", "%s", "Retry", ResolutionAction.RETRY),
INSTALL_FAILED(
"The application could not be installed%s", "%s", "Retry", ResolutionAction.RETRY),
SWAP_FAILED(
"We were unable to deploy your changes%s", "%s", "Retry", ResolutionAction.RETRY),
PREINSTALL_APPID_CHANGED(
"Cannot preinstall: apks have different package name (%s and %s)",
"%s", "Retry", ResolutionAction.RETRY),
AGENT_FAILED(
"We were unable to deploy your changes%s", "%s", "Retry", ResolutionAction.RETRY),
AGENT_SWAP_FAILED(
"We were unable to deploy your changes%s", "%s", "Retry", ResolutionAction.RETRY),
PARSE_FAILED(
"We were unable to deploy your changes.", "%s", "Retry", ResolutionAction.RETRY),
SWAP_MULTIPLE_PACKAGES(
"Cannot swap multiple packages", "", "Retry", ResolutionAction.RETRY),
INSTALLER_IO_EXCEPTION(
"IOException occurred within Installer", "%s", "Retry", ResolutionAction.RETRY),
APP_OVERLAY_IN_UNKNOWN_STATE(
"The target app on the device is in a state unknown to Studio",
"Android Studio is unable to recognize the version of the application currently installed on the target device.",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
UNSUPPORTED_ARCH(
"The target device's architecture is not supported", "", "", ResolutionAction.NONE),
UNKNOWN_JVMTI_ERROR("Invalid error code %s", "", "Retry", ResolutionAction.RETRY),
JDWP_REDEFINE_CLASSES_EXCEPTION(
"Exception during VM RedfineClasses", "%s", "Retry", ResolutionAction.RETRY),
ABIS_FIELD_NOT_FOUND(
"android.os.Build does not contain the expected ABI fields",
"",
"Retry",
ResolutionAction.RETRY),
ATTACHAGENT_NOT_FOUND(
"dalvik.system.VMDebug does not contain proper attachAgent method",
"",
"Retry",
ResolutionAction.RETRY),
ATTACHAGENT_EXCEPTION(
"Debugger attachAgent invocation failed due to %s",
"%s", "Retry", ResolutionAction.RETRY),
NO_DEBUGGER_SESSION(
"No Debugger session found for port %s", "", "Retry", ResolutionAction.RETRY),
JDI_INVAlID_STATE("Invalid Redefinition State.", "", "", ResolutionAction.RETRY),
INTERRUPTED("Deployment was interrupted.", "%s", "Retry", ResolutionAction.RETRY),
UNSUPPORTED_REINIT(
"Added variable(s) does not support value initialization: %s",
"", "Reinstall and restart app", ResolutionAction.RUN_APP),
UNSUPPORTED_R_REASSIGNMENT(
"Existing ID values of R.class has been changed.",
"%s",
"Reinstall and restart app",
ResolutionAction.RUN_APP),
JDBC_NATIVE_LIB(
"Unable to establish JDBC connection to DEX file database",
"Verify Android Studio is able to extract executable file to %s",
"Retry",
ResolutionAction.RETRY),
UNSUPPORTED_IWI_CHANGE(
"A change was not supported by the IWI pipeline. Deployment should fall back to regular installation",
"",
"",
ResolutionAction.NONE),
UNSUPPORTED_IWI_FILE_DELETE(
"Deleting installed files is not supported by the IWI pipeline. Deployment should fall back to regular installation",
"",
"",
ResolutionAction.NONE),
IWI_RUN_TESTS_NOT_SUPPORTED(
"Running instrumented tests is not supported by the IWI pipeline. Deployment should fall back to regular installation",
"",
"",
ResolutionAction.NONE),
OPERATION_NOT_SUPPORTED("Operation not supported.", "%s", "", ResolutionAction.NONE),
RUN_TIME_EXCEPTION("Runtime Exception.", "%s", "Retry", ResolutionAction.RETRY);
private final String message;
private final String details;
private final String callToAction;
private final ResolutionAction action;
Error(String message, String details, String callToAction, ResolutionAction action) {
this.message = message;
this.details = details;
this.callToAction = callToAction;
this.action = action;
}
public String getCallToAction() {
return callToAction;
}
public ResolutionAction getResolution() {
return action;
}
}
private Error error;
private String code;
private String details;
private static String[] NO_ARGS = {};
private DeployerException(Error error) {
this(error, null, NO_ARGS, NO_ARGS);
}
private DeployerException(Error error, String[] messageArgs, String... detailArgs) {
this(error, null, messageArgs, detailArgs);
}
private DeployerException(Error error, Enum code, String[] messageArgs, String... detailArgs) {
super(String.format(error.message, (Object[]) messageArgs));
this.error = error;
this.code = code == null ? error.name() : error.name() + "." + code.name();
this.details = String.format(error.details, (Object[]) detailArgs);
}
public Error getError() {
return error;
}
public String getId() {
return code;
}
public String getDetails() {
return details;
}
public static DeployerException unknownPackage(String packageName) {
return new DeployerException(Error.DUMP_UNKNOWN_PACKAGE, NO_ARGS, packageName);
}
// TODO: Make this package-aware.
public static DeployerException unknownProcess() {
return new DeployerException(Error.DUMP_UNKNOWN_PROCESS);
}
public static DeployerException remoteApkNotFound() {
return new DeployerException(Error.REMOTE_APK_NOT_FOUND_IN_DB);
}
public static DeployerException apkCountMismatch() {
return new DeployerException(Error.DIFFERENT_NUMBER_OF_APKS);
}
public static DeployerException apkNameMismatch() {
return new DeployerException(Error.DIFFERENT_APK_NAMES);
}
public static DeployerException processCrashing(String processName) {
return new DeployerException(Error.PROCESS_CRASHING, NO_ARGS, processName);
}
public static DeployerException processNotResponding(String processName) {
return new DeployerException(Error.PROCESS_NOT_RESPONDING, NO_ARGS, processName);
}
// TODO: We can add tests for this: b/147743908
public static DeployerException processTerminated(String pid) {
return new DeployerException(Error.PROCESS_TERMINATED, NO_ARGS, pid);
}
public static DeployerException entryNotFound(String fileName, String apkName) {
return new DeployerException(Error.ENTRY_NOT_FOUND, NO_ARGS, fileName, apkName);
}
public static DeployerException isolatedServiceNotSupported(List<String> serviceNames) {
return new DeployerException(
Error.ISOLATED_SERVICE_NOT_SUPPORTED, NO_ARGS, String.join(", ", serviceNames));
}
public static DeployerException entryUnzipFailed(Throwable exception) {
return new DeployerException(Error.ENTRY_UNZIP_FAILED, NO_ARGS, exception.getMessage());
}
public static DeployerException changedSharedObject(String filePath) {
return new DeployerException(Error.CANNOT_SWAP_STATIC_LIB, NO_ARGS, filePath);
}
public static DeployerException changedManifest(String filePath) {
return new DeployerException(Error.CANNOT_SWAP_MANIFEST, NO_ARGS, filePath);
}
public static DeployerException changedResources(String filePath) {
return new DeployerException(Error.CANNOT_SWAP_RESOURCE, NO_ARGS, filePath);
}
public static DeployerException changedCrashlyticsBuildId(String filePath) {
return new DeployerException(Error.CANNOT_SWAP_CRASHLYTICS_PROPERTY, NO_ARGS, filePath);
}
public static DeployerException addedResources(String name, String type) {
return new DeployerException(Error.CANNOT_ADD_RESOURCE, NO_ARGS, name, type);
}
public static DeployerException removedResources(String name, String type) {
return new DeployerException(Error.CANNOT_REMOVE_RESOURCE, NO_ARGS, name, type);
}
public static DeployerException classNotFound(String className) {
return new DeployerException(Error.CLASS_NOT_FOUND, new String[] {className}, className);
}
public static DeployerException unsupportedVariableReinit(
Deploy.AgentSwapResponse.Status status, String msg) {
return new DeployerException(Error.UNSUPPORTED_REINIT, status, new String[] {msg}, NO_ARGS);
}
public static DeployerException unsupportedRClassReassignment(
Deploy.AgentSwapResponse.Status status, String msg) {
return new DeployerException(
Error.UNSUPPORTED_R_REASSIGNMENT, status, NO_ARGS, new String[] {msg});
}
// JVMTI error codes for which we have specific error messages.
private static final ImmutableMap<JvmtiErrorCode, Error> ERROR_CODE_TO_ERROR =
ImmutableMap.<JvmtiErrorCode, Error>builder()
.put(
JvmtiErrorCode.JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED,
Error.CANNOT_ADD_METHOD)
.put(
JvmtiErrorCode.JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED,
Error.CANNOT_AND_OR_REMOVE_FIELDS)
.put(
JvmtiErrorCode.JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED,
Error.CANNOT_CHANGE_INHERITANCE)
.put(
JvmtiErrorCode.JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED,
Error.CANNOT_DELETE_METHOD)
.put(
JvmtiErrorCode
.JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED,
Error.CANNOT_CHANGE_CLASS_MODIFIERS)
.put(
JvmtiErrorCode
.JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED,
Error.CANNOT_CHANGE_METHOD_MODIFIERS)
.put(JvmtiErrorCode.JVMTI_ERROR_FAILS_VERIFICATION, Error.VERIFICATION_ERROR)
.build();
public static DeployerException jvmtiError(JvmtiErrorCode code, boolean androidRAndUp) {
if (ERROR_CODE_TO_ERROR.containsKey(code)) {
Error error = ERROR_CODE_TO_ERROR.get(code);
if (error == Error.CANNOT_AND_OR_REMOVE_FIELDS && androidRAndUp) {
return new DeployerException(Error.CANNOT_REMOVE_FIELDS);
}
return new DeployerException(ERROR_CODE_TO_ERROR.get(code));
}
return new DeployerException(Error.JVMTI_ERROR, code, new String[] {code.name()});
}
public static DeployerException dumpFailed(String reason) {
return new DeployerException(Error.DUMP_FAILED, NO_ARGS, reason);
}
public static DeployerException dumpMixedArch(String reason) {
return new DeployerException(Error.DUMP_MIXED_ARCH, NO_ARGS, reason);
}
public static DeployerException unsupportedArch() {
return new DeployerException(Error.UNSUPPORTED_ARCH, NO_ARGS, NO_ARGS);
}
public static DeployerException parseFailed(String reason) {
return new DeployerException(Error.PARSE_FAILED, NO_ARGS, reason);
}
public static DeployerException preinstallFailed(String reason) {
return new DeployerException(Error.PREINSTALL_FAILED, NO_ARGS, reason);
}
public static DeployerException installFailed(Enum<?> code, String reason) {
String suffix = code != InstallStatus.UNKNOWN_ERROR ? ": " + code.name() : ".";
return new DeployerException(Error.INSTALL_FAILED, code, new String[] {suffix}, reason);
}
public static DeployerException swapFailed(Deploy.SwapResponse.Status code) {
String suffix = code != Deploy.SwapResponse.Status.UNKNOWN ? ": " + code.name() : ".";
return new DeployerException(Error.SWAP_FAILED, code, new String[] {suffix}, "");
}
public static DeployerException agentFailed(Deploy.AgentResponse.Status code) {
String suffix = code != Deploy.AgentResponse.Status.UNKNOWN ? ": " + code.name() : ".";
return new DeployerException(Error.AGENT_FAILED, code, new String[] {suffix}, "");
}
public static DeployerException agentSwapFailed(Deploy.AgentSwapResponse.Status code) {
String suffix = code != Deploy.AgentSwapResponse.Status.UNKNOWN ? ": " + code.name() : ".";
return new DeployerException(Error.AGENT_SWAP_FAILED, code, new String[] {suffix}, "");
}
public static DeployerException appIdChanged(String before, String after) {
return new DeployerException(
Error.PREINSTALL_APPID_CHANGED, new String[] {before, after}, "");
}
public static DeployerException swapMultiplePackages() {
return new DeployerException(Error.SWAP_MULTIPLE_PACKAGES, NO_ARGS, NO_ARGS);
}
public static DeployerException installerIoException(IOException e) {
return new DeployerException(Error.INSTALLER_IO_EXCEPTION, NO_ARGS, e.getMessage());
}
public static DeployerException overlayIdMismatch() {
return new DeployerException(Error.APP_OVERLAY_IN_UNKNOWN_STATE, NO_ARGS, NO_ARGS);
}
public static DeployerException unknownJvmtiError(String type) {
return new DeployerException(Error.UNKNOWN_JVMTI_ERROR, new String[] {type}, NO_ARGS);
}
public static DeployerException jdwpRedefineClassesException(Throwable t) {
return new DeployerException(
Error.JDWP_REDEFINE_CLASSES_EXCEPTION, NO_ARGS, t.getMessage());
}
public static DeployerException attachAgentNotFound() {
return new DeployerException(Error.ATTACHAGENT_NOT_FOUND, NO_ARGS, NO_ARGS);
}
public static DeployerException abisFieldNotFound() {
return new DeployerException(Error.ABIS_FIELD_NOT_FOUND, NO_ARGS, NO_ARGS);
}
public static DeployerException attachAgentException(Exception e) {
return new DeployerException(
Error.ATTACHAGENT_EXCEPTION,
new String[] {e.getClass().getSimpleName()},
e.getMessage());
}
public static DeployerException noDebuggerSession(int port) {
return new DeployerException(Error.NO_DEBUGGER_SESSION, new String[] {"" + port}, NO_ARGS);
}
public static DeployerException jdiInvalidState() {
return new DeployerException(Error.JDI_INVAlID_STATE, NO_ARGS, NO_ARGS);
}
public static DeployerException interrupted(String reason) {
return new DeployerException(Error.INTERRUPTED, NO_ARGS, reason);
}
public static DeployerException jdbcNativeLibError(String nativeLibLoc) {
return new DeployerException(Error.JDBC_NATIVE_LIB, NO_ARGS, nativeLibLoc);
}
public static DeployerException changeNotSupportedByIWI(ChangeType type) {
return new DeployerException(Error.UNSUPPORTED_IWI_CHANGE, type, NO_ARGS, NO_ARGS);
}
public static DeployerException deleteInstalledFileNotSupported() {
return new DeployerException(Error.UNSUPPORTED_IWI_FILE_DELETE, NO_ARGS, NO_ARGS);
}
public static DeployerException runTestsNotSupported() {
return new DeployerException(Error.IWI_RUN_TESTS_NOT_SUPPORTED, NO_ARGS, NO_ARGS);
}
public static DeployerException operationNotSupported(String reason) {
return new DeployerException(Error.OPERATION_NOT_SUPPORTED, NO_ARGS, reason);
}
public static DeployerException runtimeException(Exception e) {
DeployerException dx =
new DeployerException(Error.RUN_TIME_EXCEPTION, NO_ARGS, e.toString());
dx.initCause(e);
return dx;
}
public static DeployerException apiNotSupported() {
return new DeployerException(Error.CANNOT_SWAP_BEFORE_API_26, NO_ARGS, NO_ARGS);
}
}