Move from arbitrary resource filters to fix ones

Resource filters are used when generating additional APK containing only
specific resources.
The previous UI allowed for any type of filters, but we are moving to a
simpler way with fixed filters.
The first one is the density. Selecting the filter will generate 4 APKs per
application: default (all resources), hdpi (only hdpi/nodpi and default
resources), mdpi, ldpi.
This commit is contained in:
Xavier Ducrohet
2009-08-24 11:07:51 -07:00
parent 68a7a11d24
commit 338bc1cc45
11 changed files with 246 additions and 633 deletions

View File

@@ -17,6 +17,7 @@
package com.android.ant; package com.android.ant;
import com.android.sdklib.internal.project.ApkConfigurationHelper; import com.android.sdklib.internal.project.ApkConfigurationHelper;
import com.android.sdklib.internal.project.ApkSettings;
import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType; import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
@@ -28,7 +29,6 @@ import org.apache.tools.ant.types.Path;
import java.io.File; import java.io.File;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.Map.Entry; import java.util.Map.Entry;
/** /**
@@ -38,7 +38,7 @@ import java.util.Map.Entry;
* *
*/ */
public final class AaptExecLoopTask extends Task { public final class AaptExecLoopTask extends Task {
private String mExecutable; private String mExecutable;
private String mCommand; private String mCommand;
private String mManifest; private String mManifest;
@@ -55,7 +55,7 @@ public final class AaptExecLoopTask extends Task {
public void setExecutable(String executable) { public void setExecutable(String executable) {
mExecutable = executable; mExecutable = executable;
} }
/** /**
* Sets the value of the "command" attribute. * Sets the value of the "command" attribute.
* @param command the value. * @param command the value.
@@ -63,7 +63,7 @@ public final class AaptExecLoopTask extends Task {
public void setCommand(String command) { public void setCommand(String command) {
mCommand = command; mCommand = command;
} }
/** /**
* Sets the value of the "manifest" attribute. * Sets the value of the "manifest" attribute.
* @param manifest the value. * @param manifest the value.
@@ -71,7 +71,7 @@ public final class AaptExecLoopTask extends Task {
public void setManifest(Path manifest) { public void setManifest(Path manifest) {
mManifest = manifest.toString(); mManifest = manifest.toString();
} }
/** /**
* Sets the value of the "resources" attribute. * Sets the value of the "resources" attribute.
* @param resources the value. * @param resources the value.
@@ -79,7 +79,7 @@ public final class AaptExecLoopTask extends Task {
public void setResources(Path resources) { public void setResources(Path resources) {
mResources = resources.toString(); mResources = resources.toString();
} }
/** /**
* Sets the value of the "assets" attribute. * Sets the value of the "assets" attribute.
* @param assets the value. * @param assets the value.
@@ -87,7 +87,7 @@ public final class AaptExecLoopTask extends Task {
public void setAssets(Path assets) { public void setAssets(Path assets) {
mAssets = assets.toString(); mAssets = assets.toString();
} }
/** /**
* Sets the value of the "androidjar" attribute. * Sets the value of the "androidjar" attribute.
* @param androidJar the value. * @param androidJar the value.
@@ -95,7 +95,7 @@ public final class AaptExecLoopTask extends Task {
public void setAndroidjar(Path androidJar) { public void setAndroidjar(Path androidJar) {
mAndroidJar = androidJar.toString(); mAndroidJar = androidJar.toString();
} }
/** /**
* Sets the value of the "outfolder" attribute. * Sets the value of the "outfolder" attribute.
* @param outFolder the value. * @param outFolder the value.
@@ -103,7 +103,7 @@ public final class AaptExecLoopTask extends Task {
public void setOutfolder(Path outFolder) { public void setOutfolder(Path outFolder) {
mOutFolder = outFolder.toString(); mOutFolder = outFolder.toString();
} }
/** /**
* Sets the value of the "basename" attribute. * Sets the value of the "basename" attribute.
* @param baseName the value. * @param baseName the value.
@@ -111,19 +111,19 @@ public final class AaptExecLoopTask extends Task {
public void setBasename(String baseName) { public void setBasename(String baseName) {
mBaseName = baseName; mBaseName = baseName;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* Executes the loop. Based on the values inside default.properties, this will * Executes the loop. Based on the values inside default.properties, this will
* create alternate temporary ap_ files. * create alternate temporary ap_ files.
* *
* @see org.apache.tools.ant.Task#execute() * @see org.apache.tools.ant.Task#execute()
*/ */
@Override @Override
public void execute() throws BuildException { public void execute() throws BuildException {
Project taskProject = getProject(); Project taskProject = getProject();
// first do a full resource package // first do a full resource package
createPackage(null /*configName*/, null /*resourceFilter*/); createPackage(null /*configName*/, null /*resourceFilter*/);
@@ -132,12 +132,15 @@ public final class AaptExecLoopTask extends Task {
File baseDir = taskProject.getBaseDir(); File baseDir = taskProject.getBaseDir();
ProjectProperties properties = ProjectProperties.load(baseDir.getAbsolutePath(), ProjectProperties properties = ProjectProperties.load(baseDir.getAbsolutePath(),
PropertyType.DEFAULT); PropertyType.DEFAULT);
Map<String, String> apkConfigs = ApkConfigurationHelper.getConfigs(properties);
if (apkConfigs.size() > 0) { ApkSettings apkSettings = ApkConfigurationHelper.getSettings(properties);
Set<Entry<String, String>> entrySet = apkConfigs.entrySet(); if (apkSettings != null) {
for (Entry<String, String> entry : entrySet) { Map<String, String> apkFilters = apkSettings.getResourceFilters();
createPackage(entry.getKey(), entry.getValue()); if (apkFilters.size() > 0) {
for (Entry<String, String> entry : apkFilters.entrySet()) {
createPackage(entry.getKey(), entry.getValue());
}
} }
} }
} }
@@ -164,19 +167,19 @@ public final class AaptExecLoopTask extends Task {
ExecTask task = new ExecTask(); ExecTask task = new ExecTask();
task.setExecutable(mExecutable); task.setExecutable(mExecutable);
task.setFailonerror(true); task.setFailonerror(true);
// aapt command. Only "package" is supported at this time really. // aapt command. Only "package" is supported at this time really.
task.createArg().setValue(mCommand); task.createArg().setValue(mCommand);
// filters if needed // filters if needed
if (configName != null && resourceFilter != null) { if (configName != null && resourceFilter != null) {
task.createArg().setValue("-c"); task.createArg().setValue("-c");
task.createArg().setValue(resourceFilter); task.createArg().setValue(resourceFilter);
} }
// force flag // force flag
task.createArg().setValue("-f"); task.createArg().setValue("-f");
// manifest location // manifest location
task.createArg().setValue("-M"); task.createArg().setValue("-M");
task.createArg().setValue(mManifest); task.createArg().setValue(mManifest);
@@ -187,18 +190,18 @@ public final class AaptExecLoopTask extends Task {
task.createArg().setValue("-S"); task.createArg().setValue("-S");
task.createArg().setValue(mResources); task.createArg().setValue(mResources);
} }
// assets location. This may not exists, and aapt doesn't like it, so we check first. // assets location. This may not exists, and aapt doesn't like it, so we check first.
File assets = new File(mAssets); File assets = new File(mAssets);
if (assets.isDirectory()) { if (assets.isDirectory()) {
task.createArg().setValue("-A"); task.createArg().setValue("-A");
task.createArg().setValue(mAssets); task.createArg().setValue(mAssets);
} }
// android.jar // android.jar
task.createArg().setValue("-I"); task.createArg().setValue("-I");
task.createArg().setValue(mAndroidJar); task.createArg().setValue(mAndroidJar);
// out file. This is based on the outFolder, baseName, and the configName (if applicable) // out file. This is based on the outFolder, baseName, and the configName (if applicable)
String filename; String filename;
if (configName != null && resourceFilter != null) { if (configName != null && resourceFilter != null) {
@@ -206,15 +209,15 @@ public final class AaptExecLoopTask extends Task {
} else { } else {
filename = mBaseName + ".ap_"; filename = mBaseName + ".ap_";
} }
File file = new File(mOutFolder, filename); File file = new File(mOutFolder, filename);
task.createArg().setValue("-F"); task.createArg().setValue("-F");
task.createArg().setValue(file.getAbsolutePath()); task.createArg().setValue(file.getAbsolutePath());
// final setup of the task // final setup of the task
task.setProject(taskProject); task.setProject(taskProject);
task.setOwningTarget(getOwningTarget()); task.setOwningTarget(getOwningTarget());
// execute it. // execute it.
task.execute(); task.execute();
} }

View File

@@ -20,6 +20,7 @@ import com.android.apkbuilder.ApkBuilder.ApkCreationException;
import com.android.apkbuilder.internal.ApkBuilderImpl; import com.android.apkbuilder.internal.ApkBuilderImpl;
import com.android.apkbuilder.internal.ApkBuilderImpl.ApkFile; import com.android.apkbuilder.internal.ApkBuilderImpl.ApkFile;
import com.android.sdklib.internal.project.ApkConfigurationHelper; import com.android.sdklib.internal.project.ApkConfigurationHelper;
import com.android.sdklib.internal.project.ApkSettings;
import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType; import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
@@ -35,7 +36,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.Map.Entry; import java.util.Map.Entry;
public class ApkBuilderTask extends Task { public class ApkBuilderTask extends Task {
@@ -214,11 +214,13 @@ public class ApkBuilderTask extends Task {
ProjectProperties properties = ProjectProperties.load(baseDir.getAbsolutePath(), ProjectProperties properties = ProjectProperties.load(baseDir.getAbsolutePath(),
PropertyType.DEFAULT); PropertyType.DEFAULT);
Map<String, String> apkConfigs = ApkConfigurationHelper.getConfigs(properties); ApkSettings apkSettings = ApkConfigurationHelper.getSettings(properties);
if (apkConfigs.size() > 0) { if (apkSettings != null) {
Set<Entry<String, String>> entrySet = apkConfigs.entrySet(); Map<String, String> apkFilters = apkSettings.getResourceFilters();
for (Entry<String, String> entry : entrySet) { if (apkFilters.size() > 0) {
createApk(apkBuilder, entry.getKey(), entry.getValue(), path); for (Entry<String, String> entry : apkFilters.entrySet()) {
createApk(apkBuilder, entry.getKey(), entry.getValue(), path);
}
} }
} }

View File

@@ -34,6 +34,7 @@ import com.android.jarutils.SignedJarBuilder.IZipEntryFilter;
import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.project.ApkSettings;
import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFile;
@@ -303,11 +304,15 @@ public class ApkBuilder extends BaseBuilder {
return referencedProjects; return referencedProjects;
} }
// get the extra configs for the project. // get the APK configs for the project.
// The map contains (name, filter) where 'name' is a name to be used in the apk filename, ApkSettings apkSettings = Sdk.getCurrent().getApkSettings(project);
// and filter is the resource filter to be used in the aapt -c parameters to restrict Set<Entry<String, String>> apkfilters = null;
// which resource configurations to package in the apk. if (apkSettings != null) {
Map<String, String> configs = Sdk.getCurrent().getProjectApkConfigs(project); Map<String, String> filterMap = apkSettings.getResourceFilters();
if (filterMap != null && filterMap.size() > 0) {
apkfilters = filterMap.entrySet();
}
}
// do some extra check, in case the output files are not present. This // do some extra check, in case the output files are not present. This
// will force to recreate them. // will force to recreate them.
@@ -320,19 +325,20 @@ public class ApkBuilder extends BaseBuilder {
mPackageResources = true; mPackageResources = true;
mBuildFinalPackage = true; mBuildFinalPackage = true;
} else { } else {
// if the full package is present, we check the filtered resource packages as well // if the full package is present, we check the filtered resource packages
if (configs != null) { // as well
Set<Entry<String, String>> entrySet = configs.entrySet(); if (apkfilters != null) {
for (Entry<String, String> entry : apkfilters) {
for (Entry<String, String> entry : entrySet) {
String filename = String.format(AndroidConstants.FN_RESOURCES_S_AP_, String filename = String.format(AndroidConstants.FN_RESOURCES_S_AP_,
entry.getKey()); entry.getKey());
tmp = outputFolder.findMember(filename); tmp = outputFolder.findMember(filename);
if (tmp == null || (tmp instanceof IFile && if (tmp == null || (tmp instanceof IFile &&
tmp.exists() == false)) { tmp.exists() == false)) {
String msg = String.format(Messages.s_Missing_Repackaging, filename); String msg = String.format(Messages.s_Missing_Repackaging,
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); filename);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
project, msg);
mPackageResources = true; mPackageResources = true;
mBuildFinalPackage = true; mBuildFinalPackage = true;
break; break;
@@ -360,11 +366,9 @@ public class ApkBuilder extends BaseBuilder {
String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName); String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
mBuildFinalPackage = true; mBuildFinalPackage = true;
} else if (configs != null) { } else if (apkfilters != null) {
// if the full apk is present, we check the filtered apk as well // if the full apk is present, we check the filtered apk as well
Set<Entry<String, String>> entrySet = configs.entrySet(); for (Entry<String, String> entry : apkfilters) {
for (Entry<String, String> entry : entrySet) {
String filename = ProjectHelper.getApkFilename(project, entry.getKey()); String filename = ProjectHelper.getApkFilename(project, entry.getKey());
tmp = outputFolder.findMember(filename); tmp = outputFolder.findMember(filename);
@@ -411,9 +415,8 @@ public class ApkBuilder extends BaseBuilder {
// notified. // notified.
finalPackage.delete(); finalPackage.delete();
if (configs != null) { if (apkfilters != null) {
Set<Entry<String, String>> entrySet = configs.entrySet(); for (Entry<String, String> entry : apkfilters) {
for (Entry<String, String> entry : entrySet) {
String packageFilepath = osBinPath + File.separator + String packageFilepath = osBinPath + File.separator +
ProjectHelper.getApkFilename(project, entry.getKey()); ProjectHelper.getApkFilename(project, entry.getKey());
@@ -477,9 +480,8 @@ public class ApkBuilder extends BaseBuilder {
} }
// now do the same thing for all the configured resource packages. // now do the same thing for all the configured resource packages.
if (configs != null) { if (apkfilters != null) {
Set<Entry<String, String>> entrySet = configs.entrySet(); for (Entry<String, String> entry : apkfilters) {
for (Entry<String, String> entry : entrySet) {
String outPathFormat = osBinPath + File.separator + String outPathFormat = osBinPath + File.separator +
AndroidConstants.FN_RESOURCES_S_AP_; AndroidConstants.FN_RESOURCES_S_AP_;
String outPath = String.format(outPathFormat, entry.getKey()); String outPath = String.format(outPathFormat, entry.getKey());
@@ -527,12 +529,11 @@ public class ApkBuilder extends BaseBuilder {
} }
// now do the same thing for all the configured resource packages. // now do the same thing for all the configured resource packages.
if (configs != null) { if (apkfilters != null) {
String resPathFormat = osBinPath + File.separator + String resPathFormat = osBinPath + File.separator +
AndroidConstants.FN_RESOURCES_S_AP_; AndroidConstants.FN_RESOURCES_S_AP_;
Set<Entry<String, String>> entrySet = configs.entrySet(); for (Entry<String, String> entry : apkfilters) {
for (Entry<String, String> entry : entrySet) {
// make the filename for the resource package. // make the filename for the resource package.
String resPath = String.format(resPathFormat, entry.getKey()); String resPath = String.format(resPathFormat, entry.getKey());

View File

@@ -18,7 +18,7 @@ package com.android.ide.eclipse.adt.internal.properties;
import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
import com.android.sdkuilib.internal.widgets.ApkConfigWidget; import com.android.sdklib.internal.project.ApkSettings;
import com.android.sdkuilib.internal.widgets.SdkTargetSelector; import com.android.sdkuilib.internal.widgets.SdkTargetSelector;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
@@ -27,14 +27,14 @@ import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbenchPropertyPage; import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.dialogs.PropertyPage; import org.eclipse.ui.dialogs.PropertyPage;
import java.util.Map;
/** /**
* Property page for "Android" project. * Property page for "Android" project.
* This is accessible from the Package Explorer when right clicking a project and choosing * This is accessible from the Package Explorer when right clicking a project and choosing
@@ -45,7 +45,7 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
private IProject mProject; private IProject mProject;
private SdkTargetSelector mSelector; private SdkTargetSelector mSelector;
private ApkConfigWidget mApkConfigWidget; private Button mSplitByDensity;
public AndroidPropertyPage() { public AndroidPropertyPage() {
// pass // pass
@@ -72,13 +72,13 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
mSelector = new SdkTargetSelector(top, targets); mSelector = new SdkTargetSelector(top, targets);
l = new Label(top, SWT.SEPARATOR | SWT.HORIZONTAL); Group g = new Group(top, SWT.NONE);
l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); g.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
g.setLayout(new GridLayout(1, false));
g.setText("APK Generation");
l = new Label(top, SWT.NONE); mSplitByDensity = new Button(g, SWT.CHECK);
l.setText("Project APK Configurations"); mSplitByDensity.setText("One APK per density");
mApkConfigWidget = new ApkConfigWidget(top);
// fill the ui // fill the ui
Sdk currentSdk = Sdk.getCurrent(); Sdk currentSdk = Sdk.getCurrent();
@@ -89,9 +89,9 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
mSelector.setSelection(target); mSelector.setSelection(target);
} }
// get the apk configurations // get the project settings
Map<String, String> configs = currentSdk.getProjectApkConfigs(mProject); ApkSettings settings = currentSdk.getApkSettings(mProject);
mApkConfigWidget.fillTable(configs); mSplitByDensity.setSelection(settings.isSplitByDpi());
} }
mSelector.setSelectionListener(new SelectionAdapter() { mSelector.setSelectionListener(new SelectionAdapter() {
@@ -114,8 +114,10 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
public boolean performOk() { public boolean performOk() {
Sdk currentSdk = Sdk.getCurrent(); Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) { if (currentSdk != null) {
currentSdk.setProject(mProject, mSelector.getSelected(), ApkSettings apkSettings = new ApkSettings();
mApkConfigWidget.getApkConfigs()); apkSettings.setSplitByDensity(mSplitByDensity.getSelection());
currentSdk.setProject(mProject, mSelector.getSelected(), apkSettings);
} }
return true; return true;

View File

@@ -30,6 +30,7 @@ import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager; import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.avd.AvdManager; import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.project.ApkConfigurationHelper; import com.android.sdklib.internal.project.ApkConfigurationHelper;
import com.android.sdklib.internal.project.ApkSettings;
import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType; import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
@@ -68,8 +69,8 @@ public class Sdk implements IProjectListener {
new HashMap<IProject, IAndroidTarget>(); new HashMap<IProject, IAndroidTarget>();
private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap = private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap =
new HashMap<IAndroidTarget, AndroidTargetData>(); new HashMap<IAndroidTarget, AndroidTargetData>();
private final HashMap<IProject, Map<String, String>> mProjectApkConfigMap = private final HashMap<IProject, ApkSettings> mApkSettingsMap =
new HashMap<IProject, Map<String, String>>(); new HashMap<IProject, ApkSettings>();
private final String mDocBaseUrl; private final String mDocBaseUrl;
/** /**
@@ -193,11 +194,9 @@ public class Sdk implements IProjectListener {
* apk configurations should not be updated. * apk configurations should not be updated.
*/ */
public void setProject(IProject project, IAndroidTarget target, public void setProject(IProject project, IAndroidTarget target,
Map<String, String> apkConfigMap) { ApkSettings settings) {
synchronized (AdtPlugin.getDefault().getSdkLockObject()) { synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
boolean resolveProject = false; boolean resolveProject = false;
boolean compileProject = false;
boolean cleanProject = false;
ProjectProperties properties = ProjectProperties.load( ProjectProperties properties = ProjectProperties.load(
project.getLocation().toOSString(), PropertyType.DEFAULT); project.getLocation().toOSString(), PropertyType.DEFAULT);
@@ -222,16 +221,18 @@ public class Sdk implements IProjectListener {
} }
} }
if (apkConfigMap != null) { // if there's no settings, force default values (to reset possibly changed
// save the apk configs in the project persistent property // values in a previous call.
cleanProject = ApkConfigurationHelper.setConfigs(properties, apkConfigMap); if (settings == null) {
settings = new ApkSettings();
// put it in a local map for easy access.
mProjectApkConfigMap.put(project, apkConfigMap);
compileProject = true;
} }
// save the project settings into the project persistent property
ApkConfigurationHelper.setProperties(properties, settings);
// put it in a local map for easy access.
mApkSettingsMap.put(project, settings);
// we are done with the modification. Save the property file. // we are done with the modification. Save the property file.
try { try {
properties.save(); properties.save();
@@ -242,17 +243,14 @@ public class Sdk implements IProjectListener {
if (resolveProject) { if (resolveProject) {
// force a resolve of the project by updating the classpath container. // force a resolve of the project by updating the classpath container.
// This will also force a recompile.
IJavaProject javaProject = JavaCore.create(project); IJavaProject javaProject = JavaCore.create(project);
AndroidClasspathContainerInitializer.updateProjects( AndroidClasspathContainerInitializer.updateProjects(
new IJavaProject[] { javaProject }); new IJavaProject[] { javaProject });
} else if (compileProject) { } else {
// If there was removed configs, we clean instead of build // always do a full clean/build.
// (to remove the obsolete ap_ and apk file from removed configs).
try { try {
project.build(cleanProject ? project.build(IncrementalProjectBuilder.CLEAN_BUILD, null);
IncrementalProjectBuilder.CLEAN_BUILD :
IncrementalProjectBuilder.FULL_BUILD,
null);
} catch (CoreException e) { } catch (CoreException e) {
// failed to build? force resolve instead. // failed to build? force resolve instead.
IJavaProject javaProject = JavaCore.create(project); IJavaProject javaProject = JavaCore.create(project);
@@ -316,10 +314,10 @@ public class Sdk implements IProjectListener {
if (sdkStorage != null) { if (sdkStorage != null) {
synchronized (AdtPlugin.getDefault().getSdkLockObject()) { synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties); ApkSettings settings = ApkConfigurationHelper.getSettings(properties);
if (configMap != null) { if (settings != null) {
sdkStorage.mProjectApkConfigMap.put(project, configMap); sdkStorage.mApkSettingsMap.put(project, settings);
} }
} }
} }
@@ -392,13 +390,11 @@ public class Sdk implements IProjectListener {
} }
/** /**
* Returns the configuration map for a given project. * Returns the APK settings for a given project.
* <p/>The Map key are name to be used in the apk filename, while the values are comma separated
* config values. The config value can be passed directly to aapt through the -c option.
*/ */
public Map<String, String> getProjectApkConfigs(IProject project) { public ApkSettings getApkSettings(IProject project) {
synchronized (AdtPlugin.getDefault().getSdkLockObject()) { synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
return mProjectApkConfigMap.get(project); return mApkSettingsMap.get(project);
} }
} }
@@ -505,7 +501,7 @@ public class Sdk implements IProjectListener {
// now remove the project for the maps. // now remove the project for the maps.
mProjectTargetMap.remove(project); mProjectTargetMap.remove(project);
mProjectApkConfigMap.remove(project); mApkSettingsMap.remove(project);
} }
} }

View File

@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.wizards.export;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard.ExportWizardPage; import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard.ExportWizardPage;
import com.android.sdklib.internal.project.ApkSettings;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
@@ -68,17 +69,17 @@ final class KeyCheckPage extends ExportWizardPage {
private Text mDestination; private Text mDestination;
private boolean mFatalSigningError; private boolean mFatalSigningError;
private FormText mDetailText; private FormText mDetailText;
/** The Apk Config map for the current project */
private Map<String, String> mApkConfig;
private ScrolledComposite mScrolledComposite; private ScrolledComposite mScrolledComposite;
private ApkSettings mApkSettings;
private String mKeyDetails; private String mKeyDetails;
private String mDestinationDetails; private String mDestinationDetails;
protected KeyCheckPage(ExportWizard wizard, String pageName) { protected KeyCheckPage(ExportWizard wizard, String pageName) {
super(pageName); super(pageName);
mWizard = wizard; mWizard = wizard;
setTitle("Destination and key/certificate checks"); setTitle("Destination and key/certificate checks");
setDescription(""); // TODO setDescription(""); // TODO
} }
@@ -93,7 +94,7 @@ final class KeyCheckPage extends ExportWizardPage {
GridLayout gl = new GridLayout(3, false); GridLayout gl = new GridLayout(3, false);
gl.verticalSpacing *= 3; gl.verticalSpacing *= 3;
composite.setLayout(gl); composite.setLayout(gl);
GridData gd; GridData gd;
new Label(composite, SWT.NONE).setText("Destination APK file:"); new Label(composite, SWT.NONE).setText("Destination APK file:");
@@ -110,26 +111,26 @@ final class KeyCheckPage extends ExportWizardPage {
@Override @Override
public void widgetSelected(SelectionEvent e) { public void widgetSelected(SelectionEvent e) {
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE); FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
fileDialog.setText("Destination file name"); fileDialog.setText("Destination file name");
// get a default apk name based on the project // get a default apk name based on the project
String filename = ProjectHelper.getApkFilename(mWizard.getProject(), String filename = ProjectHelper.getApkFilename(mWizard.getProject(),
null /*config*/); null /*config*/);
fileDialog.setFileName(filename); fileDialog.setFileName(filename);
String saveLocation = fileDialog.open(); String saveLocation = fileDialog.open();
if (saveLocation != null) { if (saveLocation != null) {
mDestination.setText(saveLocation); mDestination.setText(saveLocation);
} }
} }
}); });
mScrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL); mScrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL);
mScrolledComposite.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); mScrolledComposite.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.horizontalSpan = 3; gd.horizontalSpan = 3;
mScrolledComposite.setExpandHorizontal(true); mScrolledComposite.setExpandHorizontal(true);
mScrolledComposite.setExpandVertical(true); mScrolledComposite.setExpandVertical(true);
mDetailText = new FormText(mScrolledComposite, SWT.NONE); mDetailText = new FormText(mScrolledComposite, SWT.NONE);
mScrolledComposite.setContent(mDetailText); mScrolledComposite.setContent(mDetailText);
@@ -139,18 +140,18 @@ final class KeyCheckPage extends ExportWizardPage {
updateScrolling(); updateScrolling();
} }
}); });
setControl(composite); setControl(composite);
} }
@Override @Override
void onShow() { void onShow() {
// fill the texts with information loaded from the project. // fill the texts with information loaded from the project.
if ((mProjectDataChanged & DATA_PROJECT) != 0) { if ((mProjectDataChanged & DATA_PROJECT) != 0) {
// reset the destination from the content of the project // reset the destination from the content of the project
IProject project = mWizard.getProject(); IProject project = mWizard.getProject();
mApkConfig = Sdk.getCurrent().getProjectApkConfigs(project); mApkSettings = Sdk.getCurrent().getApkSettings(project);
String destination = ProjectHelper.loadStringProperty(project, String destination = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_DESTINATION); ExportWizard.PROPERTY_DESTINATION);
String filename = ProjectHelper.loadStringProperty(project, String filename = ProjectHelper.loadStringProperty(project,
@@ -159,7 +160,7 @@ final class KeyCheckPage extends ExportWizardPage {
mDestination.setText(destination + File.separator + filename); mDestination.setText(destination + File.separator + filename);
} }
} }
// if anything change we basically reload the data. // if anything change we basically reload the data.
if (mProjectDataChanged != 0) { if (mProjectDataChanged != 0) {
mFatalSigningError = false; mFatalSigningError = false;
@@ -170,7 +171,7 @@ final class KeyCheckPage extends ExportWizardPage {
mPrivateKey = null; mPrivateKey = null;
mCertificate = null; mCertificate = null;
mKeyDetails = null; mKeyDetails = null;
if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) { if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
int validity = mWizard.getValidity(); int validity = mWizard.getValidity();
StringBuilder sb = new StringBuilder( StringBuilder sb = new StringBuilder(
@@ -196,13 +197,13 @@ final class KeyCheckPage extends ExportWizardPage {
mWizard.getKeyAlias(), mWizard.getKeyAlias(),
new KeyStore.PasswordProtection( new KeyStore.PasswordProtection(
mWizard.getKeyPassword().toCharArray())); mWizard.getKeyPassword().toCharArray()));
if (entry != null) { if (entry != null) {
mPrivateKey = entry.getPrivateKey(); mPrivateKey = entry.getPrivateKey();
mCertificate = (X509Certificate)entry.getCertificate(); mCertificate = (X509Certificate)entry.getCertificate();
} else { } else {
setErrorMessage("Unable to find key."); setErrorMessage("Unable to find key.");
setPageComplete(false); setPageComplete(false);
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
@@ -220,33 +221,33 @@ final class KeyCheckPage extends ExportWizardPage {
} catch (IOException e) { } catch (IOException e) {
onException(e); onException(e);
} }
if (mPrivateKey != null && mCertificate != null) { if (mPrivateKey != null && mCertificate != null) {
Calendar expirationCalendar = Calendar.getInstance(); Calendar expirationCalendar = Calendar.getInstance();
expirationCalendar.setTime(mCertificate.getNotAfter()); expirationCalendar.setTime(mCertificate.getNotAfter());
Calendar today = Calendar.getInstance(); Calendar today = Calendar.getInstance();
if (expirationCalendar.before(today)) { if (expirationCalendar.before(today)) {
mKeyDetails = String.format( mKeyDetails = String.format(
"<p>Certificate expired on %s</p>", "<p>Certificate expired on %s</p>",
mCertificate.getNotAfter().toString()); mCertificate.getNotAfter().toString());
// fatal error = nothing can make the page complete. // fatal error = nothing can make the page complete.
mFatalSigningError = true; mFatalSigningError = true;
setErrorMessage("Certificate is expired."); setErrorMessage("Certificate is expired.");
setPageComplete(false); setPageComplete(false);
} else { } else {
// valid, key/cert: put it in the wizard so that it can be finished // valid, key/cert: put it in the wizard so that it can be finished
mWizard.setSigningInfo(mPrivateKey, mCertificate); mWizard.setSigningInfo(mPrivateKey, mCertificate);
StringBuilder sb = new StringBuilder(String.format( StringBuilder sb = new StringBuilder(String.format(
"<p>Certificate expires on %s.</p>", "<p>Certificate expires on %s.</p>",
mCertificate.getNotAfter().toString())); mCertificate.getNotAfter().toString()));
int expirationYear = expirationCalendar.get(Calendar.YEAR); int expirationYear = expirationCalendar.get(Calendar.YEAR);
int thisYear = today.get(Calendar.YEAR); int thisYear = today.get(Calendar.YEAR);
if (thisYear + 25 < expirationYear) { if (thisYear + 25 < expirationYear) {
// do nothing // do nothing
} else { } else {
@@ -258,14 +259,14 @@ final class KeyCheckPage extends ExportWizardPage {
"<p>The Certificate expires in %1$s %2$s.</p>", "<p>The Certificate expires in %1$s %2$s.</p>",
count, count == 1 ? "year" : "years")); count, count == 1 ? "year" : "years"));
} }
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>"); sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>"); sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, "); sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>"); sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>"); sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
} }
mKeyDetails = sb.toString(); mKeyDetails = sb.toString();
} }
} else { } else {
@@ -277,7 +278,7 @@ final class KeyCheckPage extends ExportWizardPage {
onDestinationChange(true /*forceDetailUpdate*/); onDestinationChange(true /*forceDetailUpdate*/);
} }
/** /**
* Callback for destination field edition * Callback for destination field edition
* @param forceDetailUpdate if true, the detail {@link FormText} is updated even if a fatal * @param forceDetailUpdate if true, the detail {@link FormText} is updated even if a fatal
@@ -319,12 +320,12 @@ final class KeyCheckPage extends ExportWizardPage {
// display the list of files that will actually be created // display the list of files that will actually be created
Map<String, String[]> apkFileMap = getApkFileMap(file); Map<String, String[]> apkFileMap = getApkFileMap(file);
// display them // display them
boolean fileExists = false; boolean fileExists = false;
StringBuilder sb = new StringBuilder(String.format( StringBuilder sb = new StringBuilder(String.format(
"<p>This will create the following files:</p>")); "<p>This will create the following files:</p>"));
Set<Entry<String, String[]>> set = apkFileMap.entrySet(); Set<Entry<String, String[]>> set = apkFileMap.entrySet();
for (Entry<String, String[]> entry : set) { for (Entry<String, String[]> entry : set) {
String[] apkArray = entry.getValue(); String[] apkArray = entry.getValue();
@@ -360,7 +361,7 @@ final class KeyCheckPage extends ExportWizardPage {
updateDetailText(); updateDetailText();
} }
} }
/** /**
* Updates the scrollbar to match the content of the {@link FormText} or the new size * Updates the scrollbar to match the content of the {@link FormText} or the new size
* of the {@link ScrolledComposite}. * of the {@link ScrolledComposite}.
@@ -372,41 +373,40 @@ final class KeyCheckPage extends ExportWizardPage {
mScrolledComposite.layout(); mScrolledComposite.layout();
} }
} }
private void updateDetailText() { private void updateDetailText() {
StringBuilder sb = new StringBuilder("<form>"); StringBuilder sb = new StringBuilder("<form>");
if (mKeyDetails != null) { if (mKeyDetails != null) {
sb.append(mKeyDetails); sb.append(mKeyDetails);
} }
if (mDestinationDetails != null && mFatalSigningError == false) { if (mDestinationDetails != null && mFatalSigningError == false) {
sb.append(mDestinationDetails); sb.append(mDestinationDetails);
} }
sb.append("</form>"); sb.append("</form>");
mDetailText.setText(sb.toString(), true /* parseTags */, mDetailText.setText(sb.toString(), true /* parseTags */,
true /* expandURLs */); true /* expandURLs */);
mDetailText.getParent().layout(); mDetailText.getParent().layout();
updateScrolling(); updateScrolling();
} }
/** /**
* Creates the list of destination filenames based on the content of the destination field * Creates the list of destination filenames based on the content of the destination field
* and the list of APK configurations for the project. * and the list of APK configurations for the project.
* *
* @param file File name from the destination field * @param file File name from the destination field
* @return A list of destination filenames based <code>file</code> and the list of APK * @return A list of destination filenames based <code>file</code> and the list of APK
* configurations for the project. * configurations for the project.
*/ */
private Map<String, String[]> getApkFileMap(File file) { private Map<String, String[]> getApkFileMap(File file) {
String filename = file.getName(); String filename = file.getName();
HashMap<String, String[]> map = new HashMap<String, String[]>(); HashMap<String, String[]> map = new HashMap<String, String[]>();
// add the default APK filename // add the default APK filename
String[] apkArray = new String[ExportWizard.APK_COUNT]; String[] apkArray = new String[ExportWizard.APK_COUNT];
apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename( apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename(
@@ -415,29 +415,32 @@ final class KeyCheckPage extends ExportWizardPage {
map.put(null, apkArray); map.put(null, apkArray);
// add the APKs for each APK configuration. // add the APKs for each APK configuration.
if (mApkConfig != null && mApkConfig.size() > 0) { if (mApkSettings != null) {
// remove the extension. Map<String, String> apkFilters = mApkSettings.getResourceFilters();
int index = filename.lastIndexOf('.'); if (apkFilters.size() > 0) {
String base = filename.substring(0, index); // remove the extension from the user-chosen filename
String extension = filename.substring(index); int index = filename.lastIndexOf('.');
String base = filename.substring(0, index);
Set<Entry<String, String>> set = mApkConfig.entrySet(); String extension = filename.substring(index);
for (Entry<String, String> entry : set) {
apkArray = new String[ExportWizard.APK_COUNT]; for (Entry<String, String> entry : apkFilters.entrySet()) {
apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename( apkArray = new String[ExportWizard.APK_COUNT];
mWizard.getProject(), entry.getKey()); apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename(
apkArray[ExportWizard.APK_FILE_DEST] = base + "-" + entry.getKey() + extension; mWizard.getProject(), entry.getKey());
map.put(entry.getKey(), apkArray); apkArray[ExportWizard.APK_FILE_DEST] = base + "-" + //$NON-NLS-1$
entry.getKey() + extension;
map.put(entry.getKey(), apkArray);
}
} }
} }
return map; return map;
} }
@Override @Override
protected void onException(Throwable t) { protected void onException(Throwable t) {
super.onException(t); super.onException(t);
mKeyDetails = String.format("ERROR: %1$s", ExportWizard.getExceptionMessage(t)); mKeyDetails = String.format("ERROR: %1$s", ExportWizard.getExceptionMessage(t));
} }
} }

View File

@@ -16,91 +16,33 @@
package com.android.sdklib.internal.project; package com.android.sdklib.internal.project;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
/** /**
* Helper class to read and write Apk Configuration into a {@link ProjectProperties} file. * Helper class to read and write Apk Configuration into a {@link ProjectProperties} file.
*/ */
public class ApkConfigurationHelper { public class ApkConfigurationHelper {
/** Prefix for property names for config definition. This prevents having config named
* after other valid properties such as "target". */
final static String CONFIG_PREFIX = "apk-config-";
/** /**
* Reads the Apk Configurations from a {@link ProjectProperties} file and returns them as a map. * Reads the project settings from a {@link ProjectProperties} file and returns them as a
* <p/>If there are no defined configurations, the returned map will be empty. * {@link ApkSettings} object.
* @return a map of apk configurations. The map contains (name, filter) where name is
* the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
* resource configuration to include in the apk (see aapt -c)
*/ */
public static Map<String, String> getConfigs(ProjectProperties properties) { public static ApkSettings getSettings(ProjectProperties properties) {
HashMap<String, String> configMap = new HashMap<String, String>(); ApkSettings apkSettings = new ApkSettings();
// get the list of configs. boolean splitByDensity = Boolean.parseBoolean(properties.getProperty(
String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS); ProjectProperties.PROPERTY_SPLIT_BY_DENSITY));
if (configList != null) { apkSettings.setSplitByDensity(splitByDensity);
// this is a comma separated list
String[] configs = configList.split(","); //$NON-NLS-1$
return apkSettings;
// read the value of each config and store it in a map
for (String config : configs) {
config = config.trim();
String configValue = properties.getProperty(CONFIG_PREFIX + config);
if (configValue != null) {
configMap.put(config, configValue);
}
}
}
return configMap;
} }
/** /**
* Writes the Apk Configurations from a given map into a {@link ProjectProperties}. * Sets the content of a {@link ApkSettings} into a {@link ProjectProperties}.
* @param properties the {@link ProjectProperties} in which to store the apk configurations. * @param properties the {@link ProjectProperties} in which to store the settings.
* @param configMap a map of apk configurations. The map contains (name, filter) where name is * @param settings the project settings to store.
* the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
* resource configuration to include in the apk (see aapt -c)
* @return true if the {@link ProjectProperties} contained Apk Configuration that were not
* present in the map.
*/ */
public static boolean setConfigs(ProjectProperties properties, Map<String, String> configMap) { public static void setProperties(ProjectProperties properties, ApkSettings settings) {
// load the current configs, in order to remove the value properties for each of them properties.setProperty(ProjectProperties.PROPERTY_SPLIT_BY_DENSITY,
// in case a config was removed. Boolean.toString(settings.isSplitByDpi()));
// get the list of configs.
String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
boolean hasRemovedConfig = false;
if (configList != null) {
// this is a comma separated list
String[] configs = configList.split(","); //$NON-NLS-1$
for (String config : configs) {
config = config.trim();
if (configMap.containsKey(config) == false) {
hasRemovedConfig = true;
properties.removeProperty(CONFIG_PREFIX + config);
}
}
}
// now add the properties.
Set<Entry<String, String>> entrySet = configMap.entrySet();
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : entrySet) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(entry.getKey());
properties.setProperty(CONFIG_PREFIX + entry.getKey(), entry.getValue());
}
properties.setProperty(ProjectProperties.PROPERTY_APK_CONFIGS, sb.toString());
return hasRemovedConfig;
} }
} }

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2009 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.sdklib.internal.project;
import java.util.HashMap;
import java.util.Map;
/**
* Settings for multiple APK generation.
*/
public class ApkSettings {
private boolean mSplitByDpi = false;
public ApkSettings() {
}
/**
* Returns a map of configuration filters to be used by the -c option of aapt.
* <p/>The map stores (key, value) pairs where the keys is a filename modifier and the value
* is the parameter to pass to aapt through the -c option.
* @return a map, always. It can however be empty.
*/
public Map<String, String> getResourceFilters() {
Map<String, String> map = new HashMap<String, String>();
if (mSplitByDpi) {
map.put("hdpi", "hdpi,nodpi");
map.put("mdpi", "mdpi,nodpi");
map.put("ldpi", "ldpi,nodpi");
}
return map;
}
/**
* Indicates whether APKs should be generate for each dpi level.
*/
public boolean isSplitByDpi() {
return mSplitByDpi;
}
public void setSplitByDensity(boolean split) {
mSplitByDpi = split;
}
}

View File

@@ -35,14 +35,17 @@ import java.util.Map.Entry;
public final class ProjectProperties { public final class ProjectProperties {
/** The property name for the project target */ /** The property name for the project target */
public final static String PROPERTY_TARGET = "target"; public final static String PROPERTY_TARGET = "target";
public final static String PROPERTY_APK_CONFIGS = "apk.configurations";
public final static String PROPERTY_SDK = "sdk.dir"; public final static String PROPERTY_SDK = "sdk.dir";
// LEGACY - compatibility with 1.6 and before // LEGACY - compatibility with 1.6 and before
public final static String PROPERTY_SDK_LEGACY = "sdk-location"; public final static String PROPERTY_SDK_LEGACY = "sdk-location";
public final static String PROPERTY_APP_PACKAGE = "application.package"; public final static String PROPERTY_APP_PACKAGE = "application.package";
// LEGACY - compatibility with 1.6 and before // LEGACY - compatibility with 1.6 and before
public final static String PROPERTY_APP_PACKAGE_LEGACY = "application-package"; public final static String PROPERTY_APP_PACKAGE_LEGACY = "application-package";
public final static String PROPERTY_SPLIT_BY_DENSITY = "split.density";
public static enum PropertyType { public static enum PropertyType {
BUILD("build.properties", BUILD_HEADER), BUILD("build.properties", BUILD_HEADER),
DEFAULT("default.properties", DEFAULT_HEADER), DEFAULT("default.properties", DEFAULT_HEADER),
@@ -107,17 +110,8 @@ public final class ProjectProperties {
// 1-------10--------20--------30--------40--------50--------60--------70--------80 // 1-------10--------20--------30--------40--------50--------60--------70--------80
COMMENT_MAP.put(PROPERTY_TARGET, COMMENT_MAP.put(PROPERTY_TARGET,
"# Project target.\n"); "# Project target.\n");
COMMENT_MAP.put(PROPERTY_APK_CONFIGS, COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY,
"# apk configurations. This property allows creation of APK files with limited\n" + "# Indicates whether an apk should be generated for each density.\n");
"# resources. For example, if your application contains many locales and\n" +
"# you wish to release multiple smaller apks instead of a large one, you can\n" +
"# define configuration to create apks with limited language sets.\n" +
"# Format is a comma separated list of configuration names. For each\n" +
"# configuration, a property will declare the resource configurations to\n" +
"# include. Example:\n" +
"# " + PROPERTY_APK_CONFIGS +"=european,northamerica\n" +
"# " + ApkConfigurationHelper.CONFIG_PREFIX + "european=en,fr,it,de,es\n" +
"# " + ApkConfigurationHelper.CONFIG_PREFIX + "northamerica=en,es\n");
COMMENT_MAP.put(PROPERTY_SDK, COMMENT_MAP.put(PROPERTY_SDK,
"# location of the SDK. This is only used by Ant\n" + "# location of the SDK. This is only used by Ant\n" +
"# For customization when using a Version Control System, please read the\n" + "# For customization when using a Version Control System, please read the\n" +

View File

@@ -1,177 +0,0 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.widgets;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
/**
* Edit dialog to create/edit APK configuration. The dialog displays 2 text fields for the config
* name and its filter.
*/
class ApkConfigEditDialog extends Dialog implements ModifyListener, VerifyListener {
private String mName;
private String mFilter;
private Text mNameField;
private Text mFilterField;
private Button mOkButton;
/**
* Creates an edit dialog with optional initial values for the name and filter.
* @param name optional value for the name. Can be null.
* @param filter optional value for the filter. Can be null.
* @param parentShell the parent shell.
*/
protected ApkConfigEditDialog(String name, String filter, Shell parentShell) {
super(parentShell);
mName = name;
mFilter = filter;
}
/**
* Returns the name of the config. This is only valid if the user clicked OK and {@link #open()}
* returned {@link Window#OK}
*/
public String getName() {
return mName;
}
/**
* Returns the filter for the config. This is only valid if the user clicked OK and
* {@link #open()} returned {@link Window#OK}
*/
public String getFilter() {
return mFilter;
}
@Override
protected Control createContents(Composite parent) {
Control control = super.createContents(parent);
mOkButton = getButton(IDialogConstants.OK_ID);
validateButtons();
return control;
}
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout;
composite.setLayout(layout = new GridLayout(2, false));
layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(
IDialogConstants.HORIZONTAL_SPACING);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
Label l = new Label(composite, SWT.NONE);
l.setText("Name");
mNameField = new Text(composite, SWT.BORDER);
mNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mNameField.addVerifyListener(this);
if (mName != null) {
mNameField.setText(mName);
}
mNameField.addModifyListener(this);
l = new Label(composite, SWT.NONE);
l.setText("Filter");
mFilterField = new Text(composite, SWT.BORDER);
mFilterField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
if (mFilter != null) {
mFilterField.setText(mFilter);
}
mFilterField.addVerifyListener(this);
mFilterField.addModifyListener(this);
applyDialogFont(composite);
return composite;
}
/**
* Validates the OK button based on the content of the 2 text fields.
*/
private void validateButtons() {
mOkButton.setEnabled(mNameField.getText().trim().length() > 0 &&
mFilterField.getText().trim().length() > 0);
}
@Override
protected void okPressed() {
mName = mNameField.getText();
mFilter = mFilterField.getText().trim();
super.okPressed();
}
/**
* Callback for text modification in the 2 text fields.
*/
public void modifyText(ModifyEvent e) {
validateButtons();
}
/**
* Callback to ensure the content of the text field are proper.
*/
public void verifyText(VerifyEvent e) {
Text source = ((Text)e.getSource());
if (source == mNameField) {
// check for a-zA-Z0-9.
final String text = e.text;
final int len = text.length();
for (int i = 0 ; i < len; i++) {
char letter = text.charAt(i);
if (letter > 255 || Character.isLetterOrDigit(letter) == false) {
e.doit = false;
return;
}
}
} else if (source == mFilterField) {
// we can't validate the content as its typed, but we can at least ensure the characters
// are valid. Same as mNameFiled + the comma.
final String text = e.text;
final int len = text.length();
for (int i = 0 ; i < len; i++) {
char letter = text.charAt(i);
if (letter > 255 || (Character.isLetterOrDigit(letter) == false && letter != ',')) {
e.doit = false;
return;
}
}
}
}
}

View File

@@ -1,211 +0,0 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.widgets;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* The APK Configuration widget is a table that is added to the given parent composite.
* <p/>
* To use, create it using {@link #ApkConfigWidget(Composite)} then
* call {@link #fillTable(Map)} to set the initial list of configurations.
*/
public class ApkConfigWidget {
private final static int INDEX_NAME = 0;
private final static int INDEX_FILTER = 1;
private Table mApkConfigTable;
private Button mEditButton;
private Button mDelButton;
public ApkConfigWidget(final Composite parent) {
final Composite apkConfigComp = new Composite(parent, SWT.NONE);
apkConfigComp.setLayoutData(new GridData(GridData.FILL_BOTH));
apkConfigComp.setLayout(new GridLayout(2, false));
mApkConfigTable = new Table(apkConfigComp, SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);
mApkConfigTable.setHeaderVisible(true);
mApkConfigTable.setLinesVisible(true);
GridData data = new GridData();
data.grabExcessVerticalSpace = true;
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.FILL;
mApkConfigTable.setLayoutData(data);
// create the table columns
final TableColumn column0 = new TableColumn(mApkConfigTable, SWT.NONE);
column0.setText("Name");
column0.setWidth(100);
final TableColumn column1 = new TableColumn(mApkConfigTable, SWT.NONE);
column1.setText("Configuration");
column1.setWidth(100);
Composite buttonComp = new Composite(apkConfigComp, SWT.NONE);
buttonComp.setLayoutData(new GridData(GridData.FILL_VERTICAL));
GridLayout gl;
buttonComp.setLayout(gl = new GridLayout(1, false));
gl.marginHeight = gl.marginWidth = 0;
Button newButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
newButton.setText("New...");
newButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mEditButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
mEditButton.setText("Edit...");
mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mDelButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
mDelButton.setText("Delete");
mDelButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
newButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
ApkConfigEditDialog dlg = new ApkConfigEditDialog(null /*name*/, null /*filter*/,
apkConfigComp.getShell());
if (dlg.open() == Dialog.OK) {
TableItem item = new TableItem(mApkConfigTable, SWT.NONE);
item.setText(INDEX_NAME, dlg.getName());
item.setText(INDEX_FILTER, dlg.getFilter());
onSelectionChanged();
}
}
});
mEditButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// get the current selection (single mode so we don't care about any item beyond
// index 0).
TableItem[] items = mApkConfigTable.getSelection();
if (items.length != 0) {
ApkConfigEditDialog dlg = new ApkConfigEditDialog(
items[0].getText(INDEX_NAME), items[0].getText(INDEX_FILTER),
apkConfigComp.getShell());
if (dlg.open() == Dialog.OK) {
items[0].setText(INDEX_NAME, dlg.getName());
items[0].setText(INDEX_FILTER, dlg.getFilter());
}
}
}
});
mDelButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// get the current selection (single mode so we don't care about any item beyond
// index 0).
int[] indices = mApkConfigTable.getSelectionIndices();
if (indices.length != 0) {
TableItem item = mApkConfigTable.getItem(indices[0]);
if (MessageDialog.openQuestion(parent.getShell(),
"Apk Configuration deletion",
String.format(
"Are you sure you want to delete configuration '%1$s'?",
item.getText(INDEX_NAME)))) {
// delete the item.
mApkConfigTable.remove(indices[0]);
onSelectionChanged();
}
}
}
});
// Add a listener to resize the column to the full width of the table
mApkConfigTable.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Rectangle r = mApkConfigTable.getClientArea();
column0.setWidth(r.width * 30 / 100); // 30%
column1.setWidth(r.width * 70 / 100); // 70%
}
});
// add a selection listener on the table, to enable/disable buttons.
mApkConfigTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onSelectionChanged();
}
});
}
public void fillTable(Map<String, String> apkConfigMap) {
// get the names in a list so that we can sort them.
if (apkConfigMap != null) {
Set<String> keys = apkConfigMap.keySet();
String[] keyArray = keys.toArray(new String[keys.size()]);
Arrays.sort(keyArray);
for (String key : keyArray) {
TableItem item = new TableItem(mApkConfigTable, SWT.NONE);
item.setText(INDEX_NAME, key);
item.setText(INDEX_FILTER, apkConfigMap.get(key));
}
}
onSelectionChanged();
}
public Map<String, String> getApkConfigs() {
// go through all the items from the table and fill a new map
HashMap<String, String> map = new HashMap<String, String>();
TableItem[] items = mApkConfigTable.getItems();
for (TableItem item : items) {
map.put(item.getText(INDEX_NAME), item.getText(INDEX_FILTER));
}
return map;
}
/**
* Handles table selection changes.
*/
private void onSelectionChanged() {
if (mApkConfigTable.getSelectionCount() > 0) {
mEditButton.setEnabled(true);
mDelButton.setEnabled(true);
} else {
mEditButton.setEnabled(false);
mDelButton.setEnabled(false);
}
}
}