/* | |
* Copyright (C) 2012 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.motorola.studio.android.obfuscate; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStreamWriter; | |
import java.io.Writer; | |
import java.net.MalformedURLException; | |
import java.net.URL; | |
import java.util.Scanner; | |
import org.eclipse.core.resources.IFile; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
import org.eclipse.core.runtime.IStatus; | |
import org.eclipse.core.runtime.Status; | |
import org.eclipse.core.runtime.SubMonitor; | |
import org.osgi.framework.Bundle; | |
import com.motorola.studio.android.AndroidPlugin; | |
import com.motorola.studio.android.common.log.StudioLogger; | |
import com.motorola.studio.android.common.utilities.AndroidUtils; | |
/** | |
* Manager to set Proguard files inside project | |
* Manipulates the file on <project_root>/default.properties | |
* Copy proguard.cfg | |
*/ | |
public class ObfuscatorManager | |
{ | |
private static final String PROGUARD_PATH = "/files/proguard.cfg"; | |
private static final String PROGUARD_SDK_PATH = "tools/lib/proguard.cfg"; | |
private static final String PROGUARD_FILENAME = "proguard.cfg"; | |
private static final String PROGUARD_CONFIG_STATEMENT = "proguard.config=proguard.cfg"; | |
private static final String DEFAULT_PROPERTIES_FILENAME = "default.properties"; | |
private static final String PROJECT_PROPERTIES_FILENAME = "project.properties"; | |
private static final String NL = System.getProperty("line.separator"); | |
/** | |
* Prepares the project with the Proguard settings | |
* to obfuscate when the project is exported in release mode | |
* @param project | |
* @param monitor | |
* @return | |
*/ | |
public static IStatus obfuscate(IProject project, IProgressMonitor monitor) | |
{ | |
IStatus status = Status.OK_STATUS; | |
/* | |
* Project properties file is the new filename for ADT | |
*/ | |
File projectPropertiesFile = | |
project.getFile(PROJECT_PROPERTIES_FILENAME).getLocation().toFile(); | |
File defaultPropertiesFile = | |
project.getFile(DEFAULT_PROPERTIES_FILENAME).getLocation().toFile(); | |
if (projectPropertiesFile.canWrite()) | |
{ | |
defaultPropertiesFile = projectPropertiesFile; | |
} | |
File proguardFile = project.getFile(PROGUARD_FILENAME).getLocation().toFile(); | |
try | |
{ | |
addProguardLine(defaultPropertiesFile); | |
if (!fileExists(proguardFile)) | |
{ | |
copyProguardFile(project, monitor); | |
} | |
try | |
{ | |
project.refreshLocal(IResource.DEPTH_ONE, null); | |
} | |
catch (CoreException e) | |
{ | |
// Do nothing, user just have to press F5 | |
} | |
} | |
catch (Exception e) | |
{ | |
StudioLogger.error(ObfuscatorManager.class, | |
"Error while setting Proguard to obfuscate", e); | |
status = | |
new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID, | |
"Could not set Proguard to obfuscate", e); | |
} | |
StudioLogger.collectUsageData(StudioLogger.WHAT_OBFUSCATE, StudioLogger.KIND_OBFUSCATE, | |
StudioLogger.DESCRIPTION_DEFAULT, AndroidPlugin.PLUGIN_ID, AndroidPlugin | |
.getDefault().getBundle().getVersion().toString()); | |
return status; | |
} | |
/** | |
* Removes Proguard settings | |
* (Project will not be obfuscated when the is exported in release mode) | |
* @param project | |
* @return | |
*/ | |
public static IStatus unobfuscate(IProject project) | |
{ | |
IStatus status = Status.OK_STATUS; | |
try | |
{ | |
File projectPropertiesFile = | |
project.getFile(PROJECT_PROPERTIES_FILENAME).getLocation().toFile(); | |
File defaultPropertiesFile = | |
project.getFile(DEFAULT_PROPERTIES_FILENAME).getLocation().toFile(); | |
if (isProguardSet(defaultPropertiesFile)) | |
{ | |
removeProguardLine(defaultPropertiesFile); | |
} | |
if (isProguardSet(projectPropertiesFile)) | |
{ | |
removeProguardLine(projectPropertiesFile); | |
} | |
try | |
{ | |
project.refreshLocal(IResource.DEPTH_ONE, null); | |
} | |
catch (CoreException e) | |
{ | |
// Do nothing, user just have to press F5 | |
} | |
} | |
catch (Exception e) | |
{ | |
StudioLogger | |
.error(ObfuscatorManager.class, "Error while removing Proguard settings", e); | |
status = | |
new Status(IStatus.ERROR, AndroidPlugin.PLUGIN_ID, | |
"Could not remove Proguard settings", e); | |
} | |
StudioLogger.collectUsageData(StudioLogger.WHAT_OBFUSCATE, StudioLogger.KIND_DESOBFUSCATE, | |
StudioLogger.DESCRIPTION_DEFAULT, AndroidPlugin.PLUGIN_ID, AndroidPlugin | |
.getDefault().getBundle().getVersion().toString()); | |
return status; | |
} | |
/* | |
* @param propertiesFile file to write | |
* @param newContent content to write (replaces entire file) | |
* @throws IOException | |
*/ | |
private static void write(File propertiesFile, String newContent) throws IOException | |
{ | |
Writer out = new OutputStreamWriter(new FileOutputStream(propertiesFile)); | |
try | |
{ | |
out.write(newContent); | |
} | |
finally | |
{ | |
if (out != null) | |
{ | |
out.close(); | |
} | |
} | |
} | |
/* | |
* @param ignoreProguardStatement true it does not return the line with proguard.config=proguard.cfg | |
* @return | |
* @throws IOException | |
*/ | |
private static String read(File propertiesFile, boolean ignoreProguardStatement) | |
throws IOException | |
{ | |
StringBuilder text = new StringBuilder(); | |
Scanner scanner = null; | |
FileInputStream stream = null; | |
try | |
{ | |
stream = new FileInputStream(propertiesFile); | |
scanner = new Scanner(stream); | |
while (scanner.hasNextLine()) | |
{ | |
String line = scanner.nextLine(); | |
if (!ignoreProguardStatement || !line.contains(PROGUARD_CONFIG_STATEMENT)) | |
{ | |
text.append(line + NL); | |
} | |
} | |
} | |
finally | |
{ | |
try | |
{ | |
if (scanner != null) | |
{ | |
scanner.close(); | |
} | |
if (stream != null) | |
{ | |
stream.close(); | |
} | |
} | |
catch (IOException e) | |
{ | |
StudioLogger | |
.info("Could not close stream while reading obfuscator properties file. " | |
+ e.getMessage()); | |
} | |
} | |
return text.toString(); | |
} | |
private static boolean fileExists(File proguardFile) | |
{ | |
return (proguardFile != null) && proguardFile.exists(); | |
} | |
/** | |
* Checks if default.properties have Proguard statement | |
* and if the proguard.cfg exists in the project | |
* @param project | |
* @return | |
*/ | |
public static boolean isProguardSet(IProject project) | |
{ | |
File projectPropertiesFile = | |
project.getFile(PROJECT_PROPERTIES_FILENAME).getLocation().toFile(); | |
File defaultPropertiesFile = | |
project.getFile(DEFAULT_PROPERTIES_FILENAME).getLocation().toFile(); | |
File proguardFile = project.getFile(PROGUARD_FILENAME).getLocation().toFile(); | |
return (isProguardSet(projectPropertiesFile) || isProguardSet(defaultPropertiesFile)) | |
&& fileExists(proguardFile); | |
} | |
/** | |
* Checks if default.properties have the Proguard statement | |
* @param propertiesFile | |
* @return | |
*/ | |
private static boolean isProguardSet(File propertiesFile) | |
{ | |
String defaultPropertiesContent = null; | |
try | |
{ | |
defaultPropertiesContent = read(propertiesFile, false); | |
} | |
catch (IOException e) | |
{ | |
StudioLogger.error(ObfuscatorManager.class, e.getMessage(), e); | |
} | |
return ((defaultPropertiesContent != null) && defaultPropertiesContent | |
.contains(PROGUARD_CONFIG_STATEMENT)); | |
} | |
/** | |
* Add the following line to file | |
* proguard.config=proguard.cfg | |
* if it does not exist yet | |
* @param project | |
* @throws IOException | |
*/ | |
private static void addProguardLine(File propertiesFile) throws IOException | |
{ | |
String currentContent = null; | |
currentContent = read(propertiesFile, false); | |
if (!currentContent.toString().contains(PROGUARD_CONFIG_STATEMENT)) | |
{ | |
String newContent = | |
currentContent.endsWith(NL) ? currentContent + PROGUARD_CONFIG_STATEMENT | |
: currentContent + NL + PROGUARD_CONFIG_STATEMENT; | |
write(propertiesFile, newContent); | |
} | |
} | |
/** | |
* Remove the following line to file | |
* proguard.config=proguard.cfg | |
* if it exists | |
* @param project | |
* @throws IOException | |
*/ | |
private static void removeProguardLine(File propertiesFile) throws IOException | |
{ | |
String contentWithoutProguardStatement = null; | |
contentWithoutProguardStatement = read(propertiesFile, true); | |
write(propertiesFile, contentWithoutProguardStatement); | |
} | |
private static void copyProguardFile(IProject project, IProgressMonitor monitor) | |
throws IOException, CoreException | |
{ | |
SubMonitor subMonitor = SubMonitor.convert(monitor); | |
URL proguardURL = getProguardFileURL(); | |
if (proguardURL != null) | |
{ | |
InputStream is = null; | |
try | |
{ | |
is = proguardURL.openStream(); | |
IFile destFile = project.getFile(PROGUARD_FILENAME); | |
destFile.create(is, IResource.NONE, subMonitor); | |
} | |
finally | |
{ | |
if (is != null) | |
{ | |
is.close(); | |
} | |
} | |
} | |
} | |
/** | |
* Get the most suitable proguard file, either from SDK or our plug-in | |
* @return the URL of the best proguard file found | |
*/ | |
private static URL getProguardFileURL() | |
{ | |
String sdkPath = AndroidUtils.getSDKPathByPreference(); | |
File sdkPathFile = sdkPath.isEmpty() ? new File(sdkPath) : null; | |
File proguardFromSDK = null; | |
if (sdkPathFile != null) | |
{ | |
proguardFromSDK = new File(sdkPathFile, PROGUARD_SDK_PATH); | |
} | |
URL fileURL = null; | |
// copy newest proguard file from SDK | |
if ((proguardFromSDK != null) && proguardFromSDK.canRead()) | |
{ | |
try | |
{ | |
fileURL = proguardFromSDK.toURI().toURL(); | |
} | |
catch (MalformedURLException e) | |
{ | |
StudioLogger.warn(ObfuscatorManager.class, | |
"Exception converting proguard template file to URL", e); | |
} | |
} | |
//copy file bundled with android plugin | |
else | |
{ | |
Bundle bundle = AndroidPlugin.getDefault().getBundle(); | |
fileURL = bundle.getEntry(PROGUARD_PATH); | |
} | |
return fileURL; | |
} | |
} |